feat(forum): multi-gateway cashier for pay-visibility topics

Replace balance-only AJAX topic purchase with AKSRT_Topic_Pay REST
cashier (resource orders + Payment_Gateway), reuse cms-download modal,
and unlock on aksrt_order_paid.
This commit is contained in:
kate522 committed 2026-07-15 12:27:59 +08:00
1 parent 3f06675b8c
commit cea22daae7
13 files changed
+768 -262

No files matched your search

@@ -408,121 +408,6 @@ trait AKSRT_Forum_Topic_Service_Part_3 {
return $this->forum->success(__('解锁成功', 'aksrt'));
}
/**
* Cash/balance purchase for VISIBILITY_PAY topics.
*
* Reuses AKSRT_Order product_type=resource + balance credits conversion
* (1 CNY = 100 credits, same as AKSRT_Balance_Gateway).
*
* @return array{success:bool,message:string}
*/
public function purchase_topic_with_balance(int $user_id, int $topic_id): array {
$post = get_post($topic_id);
if (!$post || $post->post_type !== 'aksrt_forum_topic') {
return $this->forum->error(__('主题不存在', 'aksrt'));
}
if ($user_id <= 0) {
return $this->forum->error(__('请先登录', 'aksrt'));
}
$visibility = (string) get_post_meta($topic_id, '_aksrt_visibility', true);
if ($visibility !== AKSRT_Forum::VISIBILITY_PAY) {
return $this->forum->error(__('该主题不是付费可见内容', 'aksrt'));
}
if ($this->has_user_paid_topic($user_id, $topic_id)) {
return $this->forum->success(__('已购买', 'aksrt'));
}
$price = $this->resolve_topic_pay_price($topic_id, $user_id);
if (!empty($price['is_free'])) {
$this->mark_topic_unlocked($user_id, $topic_id);
return $this->forum->success(__('VIP 免费解锁成功', 'aksrt'), [
'amount' => 0,
'free' => true,
]);
}
$amount = (float) ($price['amount'] ?? 0);
if ($amount <= 0) {
return $this->forum->error(__('价格未设置', 'aksrt'));
}
if (!class_exists('AKSRT_Order') || !class_exists('AKSRT_Credits')) {
return $this->forum->error(__('支付系统不可用', 'aksrt'));
}
$product_type = class_exists('AKSRT_Domain_Model')
? AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE
: 'resource';
$order_number = AKSRT_Order::get_instance()->create($user_id, $product_type, $topic_id, $amount, 'CNY');
if ($order_number === '') {
return $this->forum->error(__('创建订单失败', 'aksrt'));
}
// Align with AKSRT_Balance_Gateway: 1 CNY = 100 credits.
$credits_cost = (int) ceil($amount * 100);
$balance = AKSRT_Credits::get_balance($user_id);
if ($balance < $credits_cost) {
AKSRT_Order::get_instance()->update_status($order_number, AKSRT_Order::STATUS_CANCELLED, [
'note' => __('余额不足,自动取消', 'aksrt'),
]);
return $this->forum->error(sprintf(
__('余额不足,需要 %d 积分(约 ¥%s),当前余额 %d', 'aksrt'),
$credits_cost,
number_format($amount, 2, '.', ''),
$balance
));
}
$deducted = AKSRT_Credits::deduct(
$user_id,
$credits_cost,
'forum_topic_pay',
[
'topic_id' => $topic_id,
'order' => $order_number,
],
sprintf(__('购买帖子「%s」', 'aksrt'), get_the_title($topic_id))
);
if (is_wp_error($deducted) || $deducted === false) {
AKSRT_Order::get_instance()->update_status($order_number, AKSRT_Order::STATUS_CANCELLED, [
'note' => __('扣款失败,自动取消', 'aksrt'),
]);
$msg = is_wp_error($deducted) ? $deducted->get_error_message() : __('扣款失败', 'aksrt');
return $this->forum->error($msg ?: __('扣款失败', 'aksrt'));
}
$paid = AKSRT_Order::get_instance()->update_status($order_number, AKSRT_Order::STATUS_PAID, [
'payment_method' => 'balance',
'payment_transaction_id' => 'BAL_' . $order_number,
'note' => sprintf(__('论坛付费主题余额支付 %d 积分', 'aksrt'), $credits_cost),
]);
if (!$paid) {
// Best-effort refund so user is not charged without access.
AKSRT_Credits::add(
$user_id,
$credits_cost,
'forum_topic_pay_refund',
['topic_id' => $topic_id, 'order' => $order_number],
sprintf(__('购买失败退款:%s', 'aksrt'), get_the_title($topic_id))
);
return $this->forum->error(__('订单确认失败,已尝试退回积分', 'aksrt'));
}
$this->mark_topic_unlocked($user_id, $topic_id);
return $this->forum->success(__('购买成功', 'aksrt'), [
'order_number' => $order_number,
'amount' => $amount,
'credits_cost' => $credits_cost,
]);
}
/**
* Resolve cash price for a pay-visibility topic.
*
@@ -263,25 +263,4 @@ trait AKSRT_Forum_Topic_Service_Part_4 {
wp_send_json_error(['message' => $result['message'] ?? __('解锁失败', 'aksrt')]);
}
}
public function ajax_purchase_topic(): void {
check_ajax_referer('aksrt_forum_action', '_nonce');
$user_id = get_current_user_id();
if ($user_id <= 0) {
wp_send_json_error(['message' => __('请先登录', 'aksrt')]);
}
$topic_id = (int) ($_POST['topic_id'] ?? 0);
if ($topic_id <= 0) {
wp_send_json_error(['message' => __('主题不存在', 'aksrt')]);
}
$result = $this->purchase_topic_with_balance($user_id, $topic_id);
if (!empty($result['success'])) {
wp_send_json_success($result);
} else {
wp_send_json_error(['message' => $result['message'] ?? __('购买失败', 'aksrt')]);
}
}
}