【开发笔记】开发一个基于 wordpress 和 woocommerceRest 的接口插件

本贴最后更新于 914 天前,其中的信息可能已经斗转星移

近期使用 ReactNative 开发项目,后台用的 woocommerce 那一套,需要写 Rest 接口对接,虽然 woo 自带的有,问题都是服务端的,客户端调用用只读密钥还行,写就没办法了,只能自己开坑写服务端的 Rest 接口插件了。

目前插件已完成所需功能涉及:wp hook,wp rest,wp auth,woo hook,woo rest 等。基本的 token 认证到注册自定义产品类型,hook 已有产品定制化流程,拦截 rest 数据进行二次定制等。

1.wordpress 插件结构

下文以 booking 这个插件为例,记录一下开发要注意的地方,首先看一下插件结构。

首先你需要创建一个文件夹,名称就是你的插件名称,其次,你需要创建一个同名的 PHP 入口文件: omiBeaver_booking.php(后面会列出内容),如果你的插件不进行市场上架发布,那么只需要这一个必须的文件即可,如果需要上架,那么你需要多一个必须文件:readme.txt 用来在 wordpress 市场上发布使用。其他文件则可以根据你的项目需要自行创建,我这里是开发一个预约系统的 Rest 插件,其中涉及,登录授权,后台面板的定制功能,这里分了

1.Apis.php(对外 Rest 接口)

2.BookingInit.php(程序主类)

3.omiHooks.php(插件使用的 Hook 聚合类)

4.Views(定制后台用到的 HTML 代码)

image.png

先从入口文件看

<?php /** * Plugin Name: OmiBeaverBooking * Plugin URI: https://codecanyon.net/user/omibeaver/portfolio * Description: Virtual goods course reservations Rest API * Version: 1.0.0 * Requires at least: 5.2 * Requires PHP: 7.2 * Author: omiBeaver * Author URI: https://codecanyon.net/user/omibeaver/portfolio * License: GPL v2 or later * License URI: https://www.gnu.org/licenses/gpl-2.0.html * Update URI: https://codecanyon.net/user/omibeaver/portfolio * Text Domain: OmiBeaverBooking * Domain Path: /languages */ require_once 'BookingInit.php'; $MACRO = new BookingInit;

可以看到 Plugin name 这里是与文件同名的,这里是需要与插件名称一直,其他元信息用来展示在插件管理面板,如下所示:

image.png

TIPS:这里一定要注意,头部注释一定要和插件名称文件夹一致,否则压缩打包后,安装插件会无法识别。

第一次接触 wp 开发的小伙伴可能一下子很难接受 wp 的开发模式,你会发现有很多插件都是 PHP 与 HTML 混合编程,代码里充斥着很多:

<?php ?> <div></div> <?php ?>

了解 hook 后,就觉得很正常了,hook 就像前端写 React 或者 Vue 里的生命周期,或者安卓的 activity,在(wp)系统的启动后触发一系列的回调或者预定义的方法,wp 内置了非常多的 hook 回调,你可以在系统初始化的时候执行你的代码,在系统 CURD 的每个操作前后甚至中途插入你的代码,而你只需要使用 🌰1:

add_filter('manage_macro_booking_record_posts_columns', function ($columns) { return omiHooks::booking_admin_columns($columns); }); // omiHooks::booking_admin_columns public static function booking_admin_column($column, $post_id) { if ($column == 'booking_status') { Macro_views::booking_status_change($post_id); } if ($column == 'booking_time') { echo str_replace('T', ' ', get_post_meta($post_id, 'booking_time', true)); } if($column == 'ID'){ echo $post_id; } } //Macro_views::booking_status_change public static function booking_status_change($post_id) { $bg = get_post_meta($post_id, 'booking_status', true) == '0' ? 'tomato' : '#578fff'; echo "<span onclick='omiJS.changeBookingStatus($post_id)' style='background:$bg;padding:4px 10px;cursor: pointer;border-radius: 5px;color: #fff'>" . (get_post_meta($post_id, 'booking_status', true) == '0' ? 'wait' : 'finish') . '</span>'; }

示例代码修改了帖子类型在后台表格上的展示效果,这里是展示用户预约记录的表,所以需要展示用户的预约状态,我注入了一个状态变化的 HTML 文字,另外从数据库提取的自定义字段:预约时间在展示的时候进行了一些格式化拆分。

image.png

上述 eg 有一些全局方法与 hook 稍后会说到,仅作为开头的示例展示。

1.wordpress 自定义 post

有了一个 hook 案例,可以发现可以根据系统提供的各种 hook 来定制自己的 wp,wp 常见的 hook 分为两种

一种是 actionHook,一种是 filterHook,第一种是事件触发的时候,你可以注入你的自定义代码,他是如何执行的呢,举个 🌰2:假如有个 hook 在某个帖子保存前需要执行你的自定义代码: save_post 这个 hook,使用 action 的 hook 你需要使用 add_action()方法,第一个参数是需要使用的 hook,第二个是 hook 的回调,第三个是优先级,第四个是参数个数。比如我们想在预约记录编辑后可以自定义预约时间或者状态,在回调里,我们可以写下如下:

add_action('save_post', function ($post_id, $view) { omiHooks::booking_cus_save($post_id, $view); }, 10, 2); //omiHooks::booking_cus_save public static function booking_cus_save($post_id, $view) { if ($view->post_type == 'macro_booking_record') { if (isset($_POST['booking_time']) && $_POST['booking_time'] != '') { update_post_meta($post_id, 'booking_time', $_POST['booking_time']); } if (isset($_POST['booking_status']) && $_POST['booking_status'] != '') { update_post_meta($post_id, 'booking_status', $_POST['booking_status']); } } }

在 Wp 中,当系统收到 post 数据后,处理完正常逻辑后会在提交前执行指定位置预设的回调

//wp 保存post逻辑xxxxx //wp保存前最后一步 do_action(your_callback,$some_params); //wp 保存提交完成

可想而知我们在系统 HTML 页面上的时候,如果需要定制页面,那么只需要使用 HTML 页面里预设的 hook 即可,所以会出现 php 与 HTML 混合情况。

默认的帖子结构是不支持我们预约表单的,我们需要定制一下自己的字段与表单类型。

1.创建自定义的帖子类型

很多时候,默认的 post 类型不支持我们的业务,通常来说,都是倾向于写作的的字段,而预约的产品可能有次数限制,有变体选择。如果我们使用 woocommerce 插件这里就直接定制好了。但是如果我们购买了产品课程后续需要预约使用,预约记录就需要我们自己定制了。

add_action('init', function () { omiHooks::booking_record_fun(); }); //omiHooks::booking_record_fun public static function booking_record_fun() { register_post_type('macro_booking_record', array( 'label' => 'booking record', 'labels' => array( 'name' => 'course booking', 'singular_name' => 'course booking list', 'add_new' => 'add booking record', 'add_new_item' => 'add booking record', 'edit' => 'update booking record', 'edit_item' => 'update', 'new_item' => 'create', 'view' => 'detail', 'view_item' => 'to detail', 'search_items' => 'query', 'not_found' => 'not found', 'not_found_in_trash' => 'not found' ), 'show_ui' => true, 'show_in_menu' => true, 'public' => true, 'description' => 'Booking Manage', 'has_archive' => false, 'show_in_rest' => false, 'supports' => [ 'title', 'author' ] ) ); }

这里注册了一个新的 post 类型,预约类型。参数请参阅:wp 文档直达

注册以后:

image.png

正常是没有后面几个字段的,这里需要我们的自定义字段啦,请看下面!

自定义帖子类型字段

这里需要用到 Meta 数据,wp 预设留下了可扩展的 meta 类型,可以在该字段里写入新的 key=>value,顶级默认是数组类型。后续取出可以使用全局方法来指定 key 取出,无需使用 array[0]格式。根据我们预约需要自定义两个字段:booking_time,booking_status,下面是在后台表单里 hook 增加我们的字段

add_action('admin_init', function () { omiHooks::booking_view_ext(); }); //omiHooks::booking_view_ext public static function booking_view_ext() { add_meta_box('macro_review_meta_box', 'Booking', function ($view) { Macro_views::booking_cus_view($view); }, 'macro_booking_record', 'normal', 'high' ); } // Macro_views::booking_cus_view public static function booking_cus_view($view) { ?> <table> <tr> <td style="width: 100%">Booking Status</td> <td> <select name="booking_status"> <option value="0" <?php echo $view->booking_status == '0' ? 'selected' : '' ?>>Wait</option> <option value="1" <?php echo $view->booking_status == '1' ? 'selected' : '' ?>>Finish</option> </select> </td> </tr> <tr> <td style="width: 100%">Booking Date</td> <td> <input name="booking_time" value="<?php echo $view->booking_time ?>" type="datetime-local"/> </td> </tr> </table> <?php }

image.png

来读代码啦,首先这里使用了一个系统事件的 hook,后台初始化的时候,执行一下我们的回调,我们向系统注册了一个新的 post 类型。

add_action('init', function () { omiHooks::booking_record_fun(); });

接下来我们需要:

1.在编辑表单支持新的字段

booking_cus_view 方法,这是我们自定义的回调,这里我们拿到了当前表格的行数据,并且可以在指定 row 的单元格进行定制输出,这里使用了 select 和时间选择器(wp 有默认样式),在表单 name 里写入新的字段名称即可,后面你可以使用保存的 hook 来存储这个自定义表单,如果还记得开始的 🌰2,会发现:

update_post_meta(_POST['booking_time']);

这里就是保存的 hook,因为我们使用了自定义的字段,所以需要使用 meta 函数来存储我们的数据。到此 save 完成,接下来我们完成展示。

2.在后台面板展示出来

🌰1 里面使用了 filter hook:manage_macro_booking_record_posts_columns,注意 hook 的使用规范,如果是自定义 post 相关的 hook,你需要在 hook 加上你的自定义类型名称,比如本案例的

macro_booking_record

在这个 hook 的回调我们可以对表格的输出进行控制,隐藏或者临时新增。具体看 🌰1。

2.后台表格面板自定义交互事件

在上面的面板里有个状态显示,我们如果想通过后台来更改,打开编辑过于麻烦,直接点击这个按钮触发比较合适,这里就涉及到如何绑定一个 JS 点击事件以及触发一个内部的 ajax 事件。

1.自定义后台 HTML 绑定 JS 事件(载入 JS 文件)

如果我们按照 HTML 规则来写,在:

echo "<span onclick='omiJS.changeBookingStatus($post_id)' style='background:$bg;padding:4px 10px;cursor: pointer;border-radius: 5px;color: #fff'>" . (get_post_meta($post_id, 'booking_status', true) == '0' ? 'wait' : 'finish') . '</span>';

这里触发事件的 JS 怎么加载的呢,起初我是直接在次数写了一个 script 标签来实现 js 代码,结果由于这里的 hook 会根据 row 的渲染重复数次,又修改成全局只存在一个实例,总觉得不妥,最后按照 wp 的规范实现的载入外部文件。

add_action( 'admin_enqueue_scripts', function (){ omiHooks::loadJs(); } );

上面这个 hook,可以在后台初始化的时候载入我们的 JS 代码。

public static function loadJs() { wp_enqueue_script('ajax-script', plugins_url('/assets/utils.js', __FILE__)); wp_localize_script( 'ajax-script', 'winter_ajax_obj', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('omiBeaver'), ) ); }

上面的代码我们可以发现几个要注意的点,首先 enqueue 载入我们的代码文件后,还需要使用 localize 实现本地化,不是语言的本地化,是向后台进行 JS 的配置,如基础 URL 与随机数(防止跨站),当然也可以在特定页面加载只需判断当前页面是否是你要加载的 post type 类型再执行即可。

看一下这个 utils 文件

/** * * @author omibeaver * Admin Booking Manage pane JS * @type {{changeBookingStatus(*): void}} */ const omiJS = { changeBookingStatus(post_id){ if(winter_ajax_obj){ let formData = new FormData(); formData.append('_ajax_nonce',winter_ajax_obj.nonce); formData.append('action',"change_booking_status"); formData.append('post_id',post_id) fetch(winter_ajax_obj.ajax_url, { method:'POST', body:formData } ).then((body)=>{ body.json().then((data)=>{ alert(data.msg); setTimeout(()=>{location.reload()},1000) }).catch((e)=>alert(e.msg)) }) }else{ alert('init failed') } } }

可以看见在 ajax 的参数部分加入了随机数。如果只是对某个页面修改建议在后台判断页面。我这里是写的全局一个工具对象。

3.wordpress Hook 开发

到此,完成了新的 post 类型与表单定制,表格定制输出。那么代码写在哪呢,我在哪里写入我的 hook 呢。

wp 提供了几个常用的全局生命周期方法,插件激活,插件禁用,在入口文件里,还记得吗与文件夹插件名称同名的那个文件,在这里会被 wp 执行,可以在这里写入 hook,或者和我一样,自定义一个类初始化注入全局 hook 即可,生命周期方法不是必须!

入口文件:

bookingInit 类

<?php require_once 'Views.php'; require_once 'Apis.php'; require_once 'omiHooks.php'; /** * @author omibeaver * @name BookingInit WP init hooks * */ class BookingInit { function __construct() { //create booking post type. add_action('init', function () { omiHooks::booking_record_fun(); }); //init routes. add_action('rest_api_init', function () { self::register_api(); }); //custom admin booking pane btn column. add_action('admin_init', function () { omiHooks::booking_view_ext(); }); //custom admin booking pane save post. add_action('save_post', function ($post_id, $view) { omiHooks::booking_cus_save($post_id, $view); }, 10, 2); //custom admin booking pane column. add_action('manage_macro_booking_record_posts_custom_column', function ($column, $post_id) { omiHooks::booking_admin_column($column, $post_id); }, 10, 2); //custom admin booking pane columns. add_filter('manage_macro_booking_record_posts_columns', function ($columns) { return omiHooks::booking_admin_columns($columns); }); //load custom JS. add_action( 'admin_enqueue_scripts', function (){ omiHooks::loadJs(); } ); //hook admin pane booking statue change. add_action('wp_ajax_change_booking_status', function () { omiHooks::change_booking_status(); }); //hook admin pane booking statue change. add_action('wp_ajax_nopriv_change_booking_status',function (){ omiHooks::change_booking_status(); }); //custom admin booking pane row add_action( 'post_row_actions',function ($actions,$post ){ return omiHooks::removeRowBtn($actions,$post ); } ,10,2); //custom woocommerce product add_filter( 'woocommerce_rest_prepare_shop_order_object', function ($data, $post, $context){ return omiHooks::customOrderQuery($data, $post, $context); }, 12, 3 ); //close auto-update tips. omiHooks::closeUpdate(); } //start register all hooks. private static function register_api() { //rest loginIn register_rest_route('macro', '/booking_signIn', array( 'methods' => 'GET', 'callback' => function ($request) { return (new Apis($request))->bookingSignIn(); }, 'permission_callback' => '__return_true' )); //rest query booking list by user register_rest_route('macro', '/booking_list', array( 'methods' => 'GET', 'callback' => function ($request) { return (new Apis($request))->bookingListQuery(); }, 'permission_callback' => '__return_true' )); //rest signUp register_rest_route('macro', '/booking_signUp', array( 'methods' => 'POST', 'callback' => function ($request) { return (new Apis($request))->bookingSignUp(); }, 'permission_callback' => '__return_true' )); //rest create new booking register_rest_route('macro', '/booking_create', array( 'methods' => 'POST', 'callback' => function ($request) { return (new Apis($request))->bookingCreate(); }, 'permission_callback' => '__return_true' )); //rest change booking status register_rest_route('macro', '/booking_update', array( 'methods' => 'PUT', 'callback' => function ($request) { return (new Apis($request))->bookingStatusUpdate(); }, 'permission_callback' => '__return_true' )); register_rest_route('macro', '/create_order', array( 'methods' => 'POST', 'callback' => function ($request) { return (new Apis($request))->createOrder(); }, 'permission_callback' => '__return_true' )); } }

Views

<?php /** * @author omibeaver * View Templates */ class Macro_views { public static function booking_cus_view($view) { ?> <table> <tr> <td style="width: 100%">Booking Status</td> <td> <select name="booking_status"> <option value="0" <?php echo $view->booking_status == '0' ? 'selected' : '' ?>>Wait</option> <option value="1" <?php echo $view->booking_status == '1' ? 'selected' : '' ?>>Finish</option> </select> </td> </tr> <tr> <td style="width: 100%">Booking Date</td> <td> <input name="booking_time" value="<?php echo $view->booking_time ?>" type="datetime-local"/> </td> </tr> </table> <?php } public static function booking_status_change($post_id) { $bg = get_post_meta($post_id, 'booking_status', true) == '0' ? 'tomato' : '#578fff'; echo "<span onclick='omiJS.changeBookingStatus($post_id)' style='background:$bg;padding:4px 10px;cursor: pointer;border-radius: 5px;color: #fff'>" . (get_post_meta($post_id, 'booking_status', true) == '0' ? 'wait' : 'finish') . '</span>'; } }

wordpress Rest 接口开发

Apis 类:后面我们看一下代码

<?php require_once 'omiHooks.php'; /** * @author omibeaver * Rest APIs */ class Apis { private $request; private $user; private const WOO_SECRET = 'cs_xxx';//可写secret private const WOO_KEY = 'ck_xxx';//可写key private const REMOTE_URL = 'xxx'; function __construct($request) { $this->request = $request; $is_login = wp_validate_auth_cookie($request->get_param('token'), 'macro'); if ($is_login) { $userAuthInfo = wp_parse_auth_cookie($request->get_param('token'), 'macro'); $this->user = get_user_by('login', $userAuthInfo['username'])->data; } } public function bookingStatusUpdate(): array { if (!$this->user) return ['code' => -1, 'msg' => 'token invalid', 'data' => null]; $post_id = $this->request->get_param('post_id'); $user_id = self::getUserByCookie($this->request->get_param('token'))->ID; if (!$post_id) { return ['code' => -1, 'msg' => 'params error', 'data' => null]; } $args = array( 'post_type' => 'macro_booking_record', 'posts_per_page' => 10, 'p' => $post_id ); $data = (new WP_Query($args))->posts; if (count($data) != 1) { return ['code' => -1, 'msg' => 'content not found', 'data' => null]; } if ($data[0]->post_author != $user_id) { return ['code' => -1, 'msg' => 'not permission', 'data' => null]; } if (get_post_meta($post_id, 'booking_status', true)['booking_status'] == '0') { update_post_meta($post_id, 'booking_status', 1); return ['code' => 1, 'msg' => 'success', 'data' => null]; } else { return ['code' => -1, 'msg' => 'had changed', 'data' => null]; } } public function bookingCreate(): array { if (!$this->user) return ['code' => -1, 'msg' => 'login invalid', 'data' => null]; $post_name = $this->request->get_param('booking_name'); $booking_time = $this->request->get_param('booking_time'); if (empty($booking_time) || empty($post_name)) return ['code' => -1, 'msg' => 'params invalid', 'data' => null]; if (!date_create($booking_time)) return ['code' => -1, 'msg' => 'date invalid', 'data' => null]; if (strtotime($booking_time) < time()) return ['code' => -1, 'msg' => 'Can\'t make an appointment before', 'data' => null]; if (strlen($post_name) > 50) return ['code' => -1, 'msg' => 'params error', 'data' => null]; $booking_course_title = explode(':', $post_name); if (count($booking_course_title) != 2) { return ['code' => -1, 'msg' => 'title format invalid', 'data' => null]; } try { $booking_course_title = $booking_course_title[0]; $booking_course_id = $this->request->get_param('course_id'); $user_orders = (new WC_Order($booking_course_id))->get_items(); if (count($user_orders) != 1) { return ['code' => -1, 'msg' => 'course not found', 'data' => null]; } $user_orders = current($user_orders); $meta_data = current($user_orders->get_meta_data()); $user_all_booking_count = (int)$meta_data->value; //Check the available schedule of the course } catch (Exception $exception) { return ['code' => -1, 'msg' => $exception->getMessage(), 'data' => null]; } $args = array( 'post_type' => 'macro_booking_record', 'posts_per_page' => 10, 'post_status' => 'publish', 'author' => $this->user->ID, 'meta_query' => [ 'booking_id' => $booking_course_id ] ); $user_booking_count = (new WP_Query($args))->post_count; if ($user_booking_count > $user_all_booking_count+10) return ['code' => -1, 'msg' => 'The number of appointments has been used up', 'data' => null]; $res = wp_insert_post([ 'post_author' => $this->user->ID, 'post_title' => $post_name, 'post_status' => 'publish', 'post_name' => $post_name, 'post_type' => 'macro_booking_record' ]); update_post_meta($res, 'booking_status', 0); update_post_meta($res, 'booking_time', $booking_time); update_post_meta($res, 'booking_course_id', $booking_course_id); update_post_meta($res, 'booking_course_title', $booking_course_title); return ['code' => 1, 'data' => ['booking_id' => $res, 'left' => $user_all_booking_count - $user_booking_count], 'msg' => 'SUCCESS']; } public function bookingSignUp(): array { $user_name = sanitize_user($this->request->get_body_params()['user_name'] ?? ''); $password = trim($this->request->get_body_params()['password'] ?? ''); $user_email = trim($this->request->get_body_params()['user_email'] ?? ''); if (!$user_name || !$password || !$user_email) { return ['code' => -1, 'msg' => 'params error', 'data' => null]; } if (strlen($user_name) > 20) { return ['code' => -1, 'msg' => 'params error', 'data' => null]; } if (!is_email($user_email)) { return ['code' => -1, 'msg' => 'email error', 'data' => null]; } $user_id = username_exists($user_name); if (!$user_id && !email_exists($user_email)) { $user_id = wp_create_user($user_name, $password, $user_email); return ['code' => 1, 'msg' => 'SUCCESS', 'data' => ['user_id' => $user_id]]; } else { return ['code' => -1, 'msg' => 'account exist', 'data' => null]; } } private static function getUserByCookie($cookie) { $userAuthInfo = wp_parse_auth_cookie($cookie, 'macro'); return get_user_by('login', $userAuthInfo['username']); } public function bookingListQuery(): array { if (!$this->user) return ['code' => -1, 'msg' => 'login invalid', 'data' => null]; if (empty($this->request->get_param('start_booking_time')) || empty($this->request->get_param('end_booking_time'))) return ['code' => -1, 'msg' => 'params booking_time invalid', 'data' => null]; if (!date_create($this->request->get_param('start_booking_time')) || !date_create($this->request->get_param('end_booking_time'))) return ['code' => -1, 'msg' => 'params booking_time invalid', 'data' => null]; $start_booking_time = date_create($this->request->get_param('start_booking_time')); $end_booking_time = date_create($this->request->get_param('end_booking_time')); $args = array( 'post_type' => 'macro_booking_record', 'posts_per_page' => 10, 'author' => $this->user->ID, 'meta_query' => [ 'booking_time' => array( array('key' => 'booking_time', 'value' => $start_booking_time->format('Y/m/d'), 'compare' => '>=', 'type' => 'DATE'), array('key' => 'booking_time', 'value' => $end_booking_time->format('Y/m/d'), 'compare' => '<=', 'type' => 'DATE'), ) ] ); $data = (new WP_Query($args))->posts; foreach ($data as $post) { $post->booking_status = get_post_meta($post->ID, 'booking_status', true); $post->booking_time = get_post_meta($post->ID, 'booking_time', true); } return ['code' => 1, 'msg' => 'SUCCESS', 'data' => $data]; } public function bookingSignIn(): array { $username = sanitize_user($this->request->get_param('username')); $password = trim($this->request->get_param('password')); $user = wp_authenticate($username, $password); return ['code' => 1, 'msg' => 'success', 'user' => $user, 'token' => wp_generate_auth_cookie($user->ID, time() + 720000, 'macro')]; } public function createOrder(): array { //默认订单数据 $data = [ 'meta_data' => array(array( 'key' => 'pay_status', 'value' => '50%' )), 'payment_method' => 'bacs', 'payment_method_title' => 'Direct Bank Transfer', 'set_paid' => true, 'billing' => [ 'first_name' => 'testUser', 'last_name' => 'testUser', 'address_1' => '969 Market', 'address_2' => '', 'city' => 'San Francisco', 'state' => 'CA', 'postcode' => '94103', 'country' => 'US', 'email' => 'testUser@test.com', 'phone' => '(555) 555-5555' ], 'shipping' => [ 'first_name' => 'John', 'last_name' => 'Doe', 'address_1' => '969 Market', 'address_2' => '', 'city' => 'San Francisco', 'state' => 'CA', 'postcode' => '94103', 'country' => 'US' ], 'line_items' => [ [ 'product_id' => 65, 'variation_id' => 70, 'quantity' => 1 ] ], 'shipping_lines' => [ [ 'method_id' => 'flat_rate', 'method_title' => 'Flat Rate', 'total' => '0' ] ] ]; try { $data = wp_remote_post(self::REMOTE_URL ."/wp-json/wc/v3/orders?consumer_key=" . self::WOO_KEY . "&consumer_secret=" . self::WOO_SECRET, array( 'headers' => array('Content-Type' => 'application/json'), 'timeout' => 30, 'body' => json_encode($data), ) ); } catch (Exception $exception) { return ['code' => -1, 'msg' => 'SUCCESS', 'data' => $exception->getMessage()]; } return ['code' => 1, 'msg' => 'SUCCESS', 'data' => $data]; } }

Api 接口 wp 默认有提供,我们有自己逻辑需要定义,所以这里使用的自定义接口,首先我们使用一个 hook 来初始化路由:

//init routes. add_action('rest_api_init', function () { self::register_api(); }); //start register all hooks. private static function register_api() { //rest loginIn register_rest_route('macro', '/booking_signIn', array( 'methods' => 'GET', 'callback' => function ($request) { return (new Apis($request))->bookingSignIn(); }, 'permission_callback' => '__return_true' )); //rest query booking list by user register_rest_route('macro', '/booking_list', array( 'methods' => 'GET', 'callback' => function ($request) { return (new Apis($request))->bookingListQuery(); }, 'permission_callback' => '__return_true' )); //rest signUp register_rest_route('macro', '/booking_signUp', array( 'methods' => 'POST', 'callback' => function ($request) { return (new Apis($request))->bookingSignUp(); }, 'permission_callback' => '__return_true' )); //rest create new booking register_rest_route('macro', '/booking_create', array( 'methods' => 'POST', 'callback' => function ($request) { return (new Apis($request))->bookingCreate(); }, 'permission_callback' => '__return_true' )); //rest change booking status register_rest_route('macro', '/booking_update', array( 'methods' => 'PUT', 'callback' => function ($request) { return (new Apis($request))->bookingStatusUpdate(); }, 'permission_callback' => '__return_true' )); register_rest_route('macro', '/create_order', array( 'methods' => 'POST', 'callback' => function ($request) { return (new Apis($request))->createOrder(); }, 'permission_callback' => '__return_true' )); }

使用全局函数:register_rest_route 来注册自定义的路由,这里需要注意生成的链接格式为:

http(s)://your_host_url/wp-json/自定义前缀/路由

API 代码里面基本上很清楚,就不过多介绍了,里面使用的全局方法可以在 wp 文档直接找到。

woocommerce 定制接口

最后关于 woocommerce 定制的事情,这里我只演示一个接口作为开始吧,其他同理。

woo 在 wp 基础上进行了深度定制,提供了非常多的 hook,你可以在这里找到:woocommerce hooks

这里是提一下 filter hook 和 action 的区别,这个 hook 主要使用在数据获取中,下面这个 🌰,使用 woo 的数据过滤器 hook 对订单查询记录进行定制,我们这里给订单新增了一个字段:booking_left,可预约次数。

add_filter( 'woocommerce_rest_prepare_shop_order_object', function ($data, $post, $context){ return omiHooks::customOrderQuery($data, $post, $context); }, 12, 3 ); public static function customOrderQuery($data, $post, $context): array { $data->data['booking_left'] = 2; return $data; }

woocommerceRest 调用下单

有时候会发现 woo 的功能比较分散,比如下订单,如果使用内部方法流程很多,这个时候可以使用 Rest 接口直接下单:

public function createOrder(): array { //默认订单数据 $data = [ 'meta_data' => array(array( 'key' => 'pay_status', 'value' => '50%' )), 'payment_method' => 'bacs', 'payment_method_title' => 'Direct Bank Transfer', 'set_paid' => true, 'billing' => [ 'first_name' => 'testUser', 'last_name' => 'testUser', 'address_1' => '969 Market', 'address_2' => '', 'city' => 'San Francisco', 'state' => 'CA', 'postcode' => '94103', 'country' => 'US', 'email' => 'testUser@test.com', 'phone' => '(555) 555-5555' ], 'shipping' => [ 'first_name' => 'John', 'last_name' => 'Doe', 'address_1' => '969 Market', 'address_2' => '', 'city' => 'San Francisco', 'state' => 'CA', 'postcode' => '94103', 'country' => 'US' ], 'line_items' => [ [ 'product_id' => 65, 'variation_id' => 70, 'quantity' => 1 ] ], 'shipping_lines' => [ [ 'method_id' => 'flat_rate', 'method_title' => 'Flat Rate', 'total' => '0' ] ] ]; try { $data = wp_remote_post(self::REMOTE_URL ."/wp-json/wc/v3/orders?consumer_key=" . self::WOO_KEY . "&consumer_secret=" . self::WOO_SECRET, array( 'headers' => array('Content-Type' => 'application/json'), 'timeout' => 30, 'body' => json_encode($data), ) ); } catch (Exception $exception) { return ['code' => -1, 'msg' => 'SUCCESS', 'data' => $exception->getMessage()]; } return ['code' => 1, 'msg' => 'SUCCESS', 'data' => $data]; }

woocommerce 和 wp 一样,你可以自定义下单流程的 HTML 内容,当然你也可以注入 JS 开发,上文其实有使用一个哦,发现了吗!甚至你可以使用 React 来开发。

有其他疑问可以留言,我有空的时候会修正文章和回复。

本文涉及的插件 OmiBeaverBooking 已开源:OmiBeaverBooking

😋 有定制 wordpress 插件需求的可联系 winter_986@qq.com

  • WordPress

    WordPress 是一个使用 PHP 语言开发的博客平台,用户可以在支持 PHP 和 MySQL 数据库的服务器上架设自己的博客。也可以把 WordPress 当作一个内容管理系统(CMS)来使用。WordPress 是一个免费的开源项目,在 GNU 通用公共许可证(GPLv2)下授权发布。

    66 引用 • 114 回帖 • 200 关注
  • woocommerce
    1 引用 • 1 回帖
2 操作
yf98 在 2022-09-27 08:17:52 更新了该帖
yf98 在 2022-09-26 09:21:31 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • 0sheng

    期待更丰富、强大、完善得 WP 插件