chore(pay): close residual zibll edge contracts for content pay

Expand coupon scope to content categories, document points_deduction as runtime-off like zibll, reject card_pass on content cashier, and pass shop/content context into coupon validate.
This commit is contained in:
2026-07-14 22:12:12 +08:00
parent dc28fafcc3
commit 2ead893923
6 changed files with 90 additions and 24 deletions
@@ -79,12 +79,12 @@ export const creditsSchema = {
},
{
key: 'deduction',
title: '积分抵扣',
title: '积分抵扣(预留)',
icon: 'fas fa-percent',
fields: [
{ key: 'points_deduction_s', label: '启用积分抵扣', type: 'switch', default: false },
{ key: 'points_deduction_rate', label: '抵扣比例(%)', type: 'number', default: 100, min: 1, max: 100, dependsOn: { key: 'points_deduction_s', value: true } },
{ key: 'points_deduction_max', label: '最大抵扣比例(%)', type: 'number', default: 50, min: 1, max: 100, dependsOn: { key: 'points_deduction_s', value: true } },
{ key: 'points_deduction_s', label: '启用积分抵扣', type: 'switch', default: false, help: '对齐 zibll:运行时暂未启用(zibpay_is_allow_points_deduction 恒为 false)。字段保留供后续接收银台现金单抵扣。' },
{ key: 'points_deduction_rate', label: '积分换算比例', type: 'number', default: 100, min: 1, max: 10000, dependsOn: { key: 'points_deduction_s', value: true }, help: 'zibll 语义:points_deduction_rate = 多少积分抵 1 元(示例 30 表示 30 积分=1 元)。当前未参与下单计算。' },
{ key: 'points_deduction_max', label: '最大抵扣比例(%)', type: 'number', default: 50, min: 1, max: 100, dependsOn: { key: 'points_deduction_s', value: true }, help: '预留:最多可抵订单金额的百分比。当前未参与下单计算。' },
],
},
{
@@ -432,10 +432,10 @@ class AKSRT_Admin_Schema_Users {
['key' => 'points_service_charge', 'label' => '转账手续费(%)', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 50],
['key' => 'points_transfer_min', 'label' => '最低转账金额', 'type' => 'number', 'default' => 1, 'min' => 1, 'max' => 9999],
]],
['key' => 'deduction', 'title' => '积分抵扣', 'fields' => [
['key' => 'points_deduction_s', 'label' => '启用积分抵扣', 'type' => 'switch', 'default' => false],
['key' => 'points_deduction_rate', 'label' => '抵扣比例(%)', 'type' => 'number', 'default' => 100, 'min' => 1, 'max' => 100],
['key' => 'points_deduction_max', 'label' => '最大抵扣比例(%)', 'type' => 'number', 'default' => 50, 'min' => 1, 'max' => 100],
['key' => 'deduction', 'title' => '积分抵扣(预留)', 'fields' => [
['key' => 'points_deduction_s', 'label' => '启用积分抵扣', 'type' => 'switch', 'default' => false, 'help' => '对齐 zibll:运行时暂未启用。字段保留供后续接收银台现金单抵扣。'],
['key' => 'points_deduction_rate', 'label' => '积分换算比例', 'type' => 'number', 'default' => 100, 'min' => 1, 'max' => 10000, 'help' => '多少积分抵 1 元(zibll points_deduction_rate)。当前未参与下单。'],
['key' => 'points_deduction_max', 'label' => '最大抵扣比例(%)', 'type' => 'number', 'default' => 50, 'min' => 1, 'max' => 100, 'help' => '预留:最多可抵订单金额百分比。当前未参与下单。'],
]],
['key' => 'card_pass', 'title' => '积分卡密兑换', 'fields' => [
['key' => 'points_pass_exchange_s', 'label' => '启用积分卡密兑换', 'type' => 'switch', 'default' => false],
+17 -2
View File
@@ -552,6 +552,13 @@ class AKSRT_Content_Pay {
'payable' => round($payable, 2),
'rebate_price' => round($rebate_price, 2),
],
// zibll: points_deduction runtime hard-off; card_pass not a content payment method.
'points_deduction_allowed' => function_exists('aksrt_is_allow_points_deduction')
? (bool) aksrt_is_allow_points_deduction('content')
: false,
'card_pass_allowed' => function_exists('aksrt_is_content_card_pass_pay_allowed')
? (bool) aksrt_is_content_card_pass_pay_allowed($post)
: false,
'price_type' => $price_type,
];
}
@@ -687,9 +694,16 @@ class AKSRT_Content_Pay {
return new WP_Error('aksrt_wrong_modo', __('该内容仅支持积分支付。', 'aksrt'), ['status' => 400]);
}
// Card pass cannot stack coupon (zibll).
// Card pass cannot stack coupon (zibll). Content cashier does not sell via card_pass.
$payment_method = sanitize_key($payment_method);
if ($payment_method === 'card_pass') {
if (!function_exists('aksrt_is_content_card_pass_pay_allowed') || !aksrt_is_content_card_pass_pay_allowed($post)) {
return new WP_Error(
'aksrt_card_pass_not_allowed',
__('内容购买不支持卡密支付,请在用户中心使用卡密兑换余额/会员/积分。', 'aksrt'),
['status' => 400]
);
}
$coupon_code = '';
}
@@ -1170,8 +1184,9 @@ class AKSRT_Content_Pay {
}
} else {
$result = AKSRT_Coupon::get_instance()->validate($coupon_code, $pay, [
'user_id' => max(0, $user_id),
'user_id' => max(0, $user_id),
'product_ids' => [(int) $post->ID],
'context' => 'content',
]);
if (empty($result['valid'])) {
$err = new WP_Error(
+23 -11
View File
@@ -161,30 +161,42 @@ class AKSRT_Coupon {
}
}
// Product scope check
// Product / content scope check.
// Supports shop product cats and content post cats.
if (!empty($coupon['product_scope']) && !empty($args['product_ids'])) {
$scope = is_array($coupon['product_scope']) ? $coupon['product_scope'] : json_decode($coupon['product_scope'], true);
if (is_array($scope) && !empty($scope)) {
$scope_type = (string) ($scope['type'] ?? '');
$scope_ids = array_map('intval', (array) ($scope['ids'] ?? []));
$scope_type = sanitize_key((string) ($scope['type'] ?? ''));
$scope_ids = array_map('intval', (array) ($scope['ids'] ?? []));
$product_ids = array_map('intval', (array) $args['product_ids']);
$taxonomies = array_values(array_filter(array_map('sanitize_key', (array) ($scope['taxonomies'] ?? []))));
if ($taxonomies === []) {
// content context prefers category; shop admin may pass aksrt_product_cat.
$context = sanitize_key((string) ($args['context'] ?? ''));
$taxonomies = ($context === 'content')
? ['category']
: ['aksrt_product_cat', 'category'];
}
if ($scope_type === 'include' && empty(array_intersect($product_ids, $scope_ids))) {
return ['valid' => false, 'message' => '该优惠码不适用于当前商品'];
return ['valid' => false, 'message' => __('该优惠码不适用于当前商品', 'aksrt')];
}
if ($scope_type === 'exclude' && !empty(array_intersect($product_ids, $scope_ids))) {
return ['valid' => false, 'message' => '该优惠码不适用于当前商品'];
return ['valid' => false, 'message' => __('该优惠码不适用于当前商品', 'aksrt')];
}
if ($scope_type === 'category') {
if (in_array($scope_type, ['category', 'content_category', 'post_category'], true)) {
$matched = false;
foreach ($product_ids as $pid) {
$cats = wp_get_post_terms($pid, 'aksrt_product_cat', ['fields' => 'ids']);
if (!is_wp_error($cats) && !empty(array_intersect($cats, $scope_ids))) {
$matched = true;
break;
foreach ($taxonomies as $tax) {
$cats = wp_get_post_terms($pid, $tax, ['fields' => 'ids']);
if (!is_wp_error($cats) && !empty(array_intersect(array_map('intval', (array) $cats), $scope_ids))) {
$matched = true;
break 2;
}
}
}
if (!$matched) {
return ['valid' => false, 'message' => '该优惠码不适用于当前商品分类'];
return ['valid' => false, 'message' => __('该优惠码不适用于当前分类', 'aksrt')];
}
}
}
@@ -73,7 +73,10 @@ class AKSRT_Shop_Checkout_Service {
$shipping_profile = $this->resolve_shipping_quote($product_views, $shipping_method);
$coupon_code = sanitize_text_field((string) ($payload['coupon_code'] ?? ''));
$coupon_result = $this->resolve_coupon_discount($coupon_code, $subtotal + $shipping_profile['fee']);
$product_ids = array_values(array_unique(array_map(static function ($row) {
return (int) ($row['product_id'] ?? $row['id'] ?? 0);
}, $product_views)));
$coupon_result = $this->resolve_coupon_discount($coupon_code, $subtotal + $shipping_profile['fee'], $product_ids);
if (!$coupon_result['success']) {
return $coupon_result;
}
@@ -306,7 +309,7 @@ class AKSRT_Shop_Checkout_Service {
return $normalized;
}
private function resolve_coupon_discount(string $coupon_code, float $order_amount): array {
private function resolve_coupon_discount(string $coupon_code, float $order_amount, array $product_ids = []): array {
if ($coupon_code === '') {
return ['success' => true, 'discount' => 0.0, 'coupon' => []];
}
@@ -315,7 +318,11 @@ class AKSRT_Shop_Checkout_Service {
return ['success' => false, 'message' => __('优惠券系统不可用。', 'aksrt')];
}
$result = AKSRT_Coupon::get_instance()->validate($coupon_code, $order_amount);
$result = AKSRT_Coupon::get_instance()->validate($coupon_code, $order_amount, [
'user_id' => get_current_user_id(),
'product_ids' => array_values(array_filter(array_map('intval', $product_ids))),
'context' => 'shop',
]);
if (empty($result['valid'])) {
return ['success' => false, 'message' => $result['message'] ?? __('优惠券不可用。', 'aksrt')];
}
+32
View File
@@ -559,6 +559,38 @@ if (!function_exists('aksrt_get_post_rebate_discount')) {
}
}
/**
* Whether cash checkout may use points deduction (zibll points_deduction).
*
* zibll: zibpay_is_allow_points_deduction() always returns false ("暂未启用").
* Keep the same contract: schema/UI may exist, runtime is hard-off unless filter overrides.
*/
if (!function_exists('aksrt_is_allow_points_deduction')) {
function aksrt_is_allow_points_deduction(string $context = 'content'): bool {
// Mirror zibll hard-off first.
$allowed = false;
if (class_exists('AKSRT_Settings') && method_exists('AKSRT_Settings', 'get_all')) {
$all = AKSRT_Settings::get_all();
// Even if switch on, default remains false like zibll until product enables it.
if (!empty($all['points_deduction_s']) && class_exists('AKSRT_Settings') && method_exists('AKSRT_Settings', 'credits_enabled') && AKSRT_Settings::credits_enabled()) {
$allowed = false; // still disabled at runtime
}
}
return (bool) apply_filters('aksrt_is_allow_points_deduction', $allowed, $context);
}
}
/**
* Content cashier does not accept card_pass payment_method.
* Card-pass for balance/VIP/points is user-center redeem (zibll order types 4/8/9),
* not content pay types (reading/download/gallery/video).
*/
if (!function_exists('aksrt_is_content_card_pass_pay_allowed')) {
function aksrt_is_content_card_pass_pay_allowed($post = null): bool {
return (bool) apply_filters('aksrt_is_content_card_pass_pay_allowed', false, $post);
}
}
// ============================================================
// 状态判断
// ============================================================