- Trait-split content-pay, vip-service, balance-service, order-repository - Procedural split of user module into user-parts/* - Split scripts enqueue_public into part-1b/1c; settings bulk trait - Bootstrap: explicit notifications, widgets glob-only, home-modules init - ADR: freeze domain placement for pay/revenue/notify boundaries
208 lines
7.8 KiB
PHP
208 lines
7.8 KiB
PHP
<?php
|
|
/**
|
|
* Extracted trait: AKSRT_Content_Pay_Rest
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
trait AKSRT_Content_Pay_Rest {
|
|
|
|
|
|
|
|
public function register_routes(): void {
|
|
register_rest_route('aksrt/v1', '/pay/content/(?P<id>\\d+)', [
|
|
[
|
|
'methods' => 'GET',
|
|
'callback' => [$this, 'rest_preview'],
|
|
'permission_callback' => [$this, 'check_rest_nonce'],
|
|
],
|
|
[
|
|
'methods' => 'POST',
|
|
'callback' => [$this, 'rest_purchase'],
|
|
'permission_callback' => [$this, 'check_rest_nonce'],
|
|
],
|
|
]);
|
|
|
|
register_rest_route('aksrt/v1', '/pay/content/order/(?P<order_number>[A-Za-z0-9_-]+)', [
|
|
'methods' => 'GET',
|
|
'callback' => [$this, 'rest_order_status'],
|
|
'permission_callback' => [$this, 'check_rest_nonce'],
|
|
]);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* REST nonce for logged-in users (wp_rest). Guests get 401 on purchase paths.
|
|
*
|
|
* @param WP_REST_Request $request
|
|
* @return true|WP_Error
|
|
*/
|
|
public function check_rest_nonce(WP_REST_Request $request) {
|
|
$nonce = $request->get_header('x-wp-nonce');
|
|
if (!$nonce) {
|
|
return new WP_Error('aksrt_rest_no_nonce', __('缺少安全令牌', 'aksrt'), ['status' => 403]);
|
|
}
|
|
if (!wp_verify_nonce($nonce, 'wp_rest')) {
|
|
return new WP_Error('aksrt_rest_bad_nonce', __('安全令牌已过期,请刷新页面', 'aksrt'), ['status' => 403]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* GET /pay/content/{id}
|
|
* query: coupon_code?, price_type?
|
|
*/
|
|
public function rest_preview(WP_REST_Request $request) {
|
|
$post_id = (int) $request['id'];
|
|
$uid = get_current_user_id();
|
|
$coupon_code = sanitize_text_field((string) ($request->get_param('coupon_code') ?? $request->get_param('coupon') ?? ''));
|
|
$price_type = sanitize_key((string) ($request->get_param('price_type') ?? ''));
|
|
if ($price_type !== 'download_limit_over') {
|
|
$price_type = '';
|
|
}
|
|
$preview = $this->preview($post_id, $uid, $coupon_code, $price_type);
|
|
if (is_wp_error($preview)) {
|
|
return $preview;
|
|
}
|
|
return new WP_REST_Response($preview, 200);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* POST /pay/content/{id}
|
|
* body: { mode?: points|cash, payment_method?: balance|alipay|..., price_type?: ''|download_limit_over, coupon_code?: string }
|
|
*/
|
|
public function rest_purchase(WP_REST_Request $request) {
|
|
$post_id = (int) $request['id'];
|
|
$uid = get_current_user_id();
|
|
|
|
$params = $request->get_json_params();
|
|
if (!is_array($params)) {
|
|
$params = $request->get_params();
|
|
}
|
|
|
|
$price_type = sanitize_key((string) ($params['price_type'] ?? ''));
|
|
if ($price_type !== 'download_limit_over') {
|
|
$price_type = '';
|
|
}
|
|
$coupon_code = sanitize_text_field((string) ($params['coupon_code'] ?? $params['coupon'] ?? ''));
|
|
|
|
// Guest allowed only for cash + pay_limit=0 + setting on.
|
|
if ($uid <= 0) {
|
|
$post = get_post($post_id);
|
|
$allow_guest = function_exists('aksrt_allow_guest_content_pay') && aksrt_allow_guest_content_pay($post);
|
|
if (!$allow_guest) {
|
|
return new WP_Error('aksrt_need_login', __('请先登录后再购买。', 'aksrt'), [
|
|
'status' => 401,
|
|
'login_url' => wp_login_url($post ? get_permalink($post) : home_url('/')),
|
|
]);
|
|
}
|
|
}
|
|
|
|
$gate = $this->assert_purchasable($post_id, $uid, $price_type);
|
|
if (is_wp_error($gate)) {
|
|
return $gate;
|
|
}
|
|
|
|
$priced = $gate['priced'];
|
|
$pay_modo = (($priced['modo'] ?? '0') === 'points') ? 'points' : '0';
|
|
|
|
$mode = sanitize_key((string) ($params['mode'] ?? ''));
|
|
if ($mode === '') {
|
|
$mode = $pay_modo === 'points' ? 'points' : 'cash';
|
|
}
|
|
if ($mode === 'points' || $pay_modo === 'points') {
|
|
if ($uid <= 0) {
|
|
return new WP_Error('aksrt_need_login', __('积分支付请先登录。', 'aksrt'), ['status' => 401]);
|
|
}
|
|
// Points path: ignore coupon (zibll parity).
|
|
return $this->rest_wrap_result($this->create_and_pay_points($post_id, $uid, $price_type));
|
|
}
|
|
|
|
$payment_method = sanitize_key((string) ($params['payment_method'] ?? 'balance'));
|
|
if ($payment_method === '' || $payment_method === 'cash') {
|
|
$payment_method = 'balance';
|
|
}
|
|
// Guests cannot use balance/points gateways.
|
|
if ($uid <= 0 && in_array($payment_method, ['balance', 'points'], true)) {
|
|
$payment_method = 'alipay';
|
|
}
|
|
return $this->rest_wrap_result($this->create_cash_order($post_id, $uid, $payment_method, $price_type, $coupon_code));
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* GET /pay/content/order/{order_number}
|
|
*/
|
|
public function rest_order_status(WP_REST_Request $request) {
|
|
$uid = get_current_user_id();
|
|
$order_number = sanitize_text_field((string) $request['order_number']);
|
|
if ($order_number === '' || !class_exists('AKSRT_Order_Repository')) {
|
|
return new WP_Error('aksrt_invalid', __('订单不存在', 'aksrt'), ['status' => 404]);
|
|
}
|
|
$repo = AKSRT_Order_Repository::get_instance();
|
|
if ($uid > 0) {
|
|
$order = $repo->get_user_order($uid, $order_number);
|
|
} else {
|
|
$order = $repo->get($order_number);
|
|
// Guest may only poll own guest cash orders (user_id=0): content/topic resource or plate_follow.
|
|
$ptype = sanitize_key((string) ($order['product_type'] ?? ''));
|
|
$guest_types = [
|
|
AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE,
|
|
AKSRT_Domain_Model::ORDER_PRODUCT_PLATE_FOLLOW,
|
|
];
|
|
if ($order && ((int) ($order['user_id'] ?? -1) !== 0 || !in_array($ptype, $guest_types, true))) {
|
|
$order = null;
|
|
}
|
|
}
|
|
if (!$order) {
|
|
return new WP_Error('aksrt_invalid', __('订单不存在', 'aksrt'), ['status' => 404]);
|
|
}
|
|
$status = (string) ($order['status'] ?? '');
|
|
$paid = class_exists('AKSRT_Domain_Model') && AKSRT_Domain_Model::is_paid_order_status($status);
|
|
if ($paid && $uid <= 0 && function_exists('aksrt_set_guest_pay_cookies')) {
|
|
// Ensure cookie set even if notify raced ahead of browser cookie.
|
|
aksrt_set_guest_pay_cookies($order);
|
|
}
|
|
return new WP_REST_Response([
|
|
'order_number' => $order_number,
|
|
'status' => $status,
|
|
'paid' => $paid,
|
|
'amount' => (float) ($order['amount'] ?? 0),
|
|
'currency' => (string) ($order['currency'] ?? ''),
|
|
'product_id' => (int) ($order['product_id'] ?? 0),
|
|
'product_type' => (string) ($order['product_type'] ?? ''),
|
|
'reload' => $paid,
|
|
], 200);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @param array|WP_Error $result
|
|
* @return WP_REST_Response|WP_Error
|
|
*/
|
|
private function rest_wrap_result($result) {
|
|
if (is_wp_error($result)) {
|
|
return $result;
|
|
}
|
|
if (empty($result['success'])) {
|
|
$code = sanitize_key((string) ($result['code'] ?? 'aksrt_pay_failed'));
|
|
$status = (int) ($result['status'] ?? 400);
|
|
return new WP_Error($code !== '' ? $code : 'aksrt_pay_failed', (string) ($result['message'] ?? __('支付失败', 'aksrt')), array_merge(
|
|
['status' => $status > 0 ? $status : 400],
|
|
is_array($result['data'] ?? null) ? $result['data'] : []
|
|
));
|
|
}
|
|
return new WP_REST_Response($result, 200);
|
|
}
|
|
|
|
}
|