Files
wordpress-theme-aksrt/src/Payment/BalanceGateway.php
T
2026-08-25 05:33:01 +08:00

289 lines
12 KiB
PHP

<?php
/**
* @package AKSRT
*/
namespace AKSRT\Payment;
defined('ABSPATH') || exit;
/**
* 站内余额支付网关(内置实现)。
*
* 仅扣统一资产账本中的现金余额。
*/
class BalanceGateway implements GatewayInterface {
public function get_id(): string {
return 'balance';
}
public function get_name(): string {
return __('站内余额', 'aksrt');
}
public function is_configured(): bool {
if (!$this->balance_enabled()) {
return false;
}
if (class_exists('\AKSRT\Settings\PaymentRuntime')) {
return \AKSRT\Settings\PaymentRuntime::is_gateway_enabled('balance');
}
return true;
}
public function create_payment(array $order): array {
$user_id = (int) ($order['user_id'] ?? 0);
$order_number = (string) ($order['order_number'] ?? '');
if (!$this->balance_enabled()) {
return ['success' => false, 'message' => __('余额功能未启用', 'aksrt')];
}
if ($user_id <= 0) {
return ['success' => false, 'message' => __('请先登录', 'aksrt')];
}
global $wpdb;
$table = \AKSRT\Core\DBMigrator::table('order');
$amount = $wpdb->get_var($wpdb->prepare(
"SELECT amount FROM {$table} WHERE order_number = %s",
$order_number
));
try {
$amount_minor = \AKSRT\Ledger\AssetAmount::balance(is_string($amount) ? $amount : '')->minor();
} catch (\InvalidArgumentException $e) {
return ['success' => false, 'message' => __('支付金额无效', 'aksrt')];
}
$transaction_code = 'BAL_' . $order_number;
$guard = static function () use ($order_number, $user_id, $amount_minor) {
global $wpdb;
$table = \AKSRT\Core\DBMigrator::table('order');
$current = $wpdb->get_row($wpdb->prepare(
"SELECT user_id, amount, status FROM {$table} WHERE order_number = %s FOR UPDATE",
$order_number
), ARRAY_A);
try {
$current_minor = is_array($current)
? \AKSRT\Ledger\AssetAmount::balance((string) ($current['amount'] ?? ''))->minor()
: -1;
} catch (\InvalidArgumentException $e) {
$current_minor = -1;
}
if (!is_array($current)
|| (int) ($current['user_id'] ?? 0) !== $user_id
|| $current_minor !== $amount_minor
|| (string) ($current['status'] ?? '') !== Order::STATUS_PENDING
) {
return new \WP_Error('aksrt_order_confirm_failed', __('订单状态或金额已变化', 'aksrt'), ['status' => 409]);
}
return true;
};
$result = \AKSRT\Ledger\LedgerService::get_instance()->debit(
$user_id,
\AKSRT\Ledger\AssetType::BALANCE,
$amount_minor,
\AKSRT\Ledger\OperationKey::deterministic('order-payment', [$order_number]),
'order_payment',
['type' => 'order', 'key' => $order_number],
sprintf(__('订单 %s 支付', 'aksrt'), $order_number),
$user_id,
['order_number' => $order_number],
static function () use ($order_number, $transaction_code, $order) {
$paid = OrderRepository::get_instance()->update_status($order_number, Order::STATUS_PAID, [
'payment_method' => 'balance',
'payment_transaction_id' => $transaction_code,
'note' => (string) ($order['_ledger_paid_note'] ?? __('余额支付成功', 'aksrt')),
'require_transition' => true,
]);
return $paid ? true : new \WP_Error('aksrt_order_confirm_failed', __('订单状态确认失败', 'aksrt'), ['status' => 409]);
},
$guard
);
if (is_wp_error($result)) {
$error_data = $result->get_error_data();
return [
'success' => false,
'code' => $result->get_error_code(),
'status' => is_array($error_data) ? (int) ($error_data['status'] ?? 500) : 500,
'message' => $result->get_error_message() ?: __('扣款失败', 'aksrt'),
];
}
return [
'success' => true,
'transaction_id' => $transaction_code,
'ledger_transaction_id' => $result->transaction_id,
'flow_number' => $result->flow_number,
'replayed' => $result->replayed,
'order_committed' => true,
'message' => __('余额支付成功', 'aksrt'),
];
}
public function verify_callback(\WP_REST_Request $request): array {
// Balance payment is synchronous — no callback needed
return ['success' => false, 'message' => __('余额支付无需回调验证', 'aksrt')];
}
public function query_status(string $order_number): array {
$order_repo = OrderRepository::get_instance();
$order = $order_repo->get($order_number);
if (!$order) {
return ['success' => false, 'message' => __('订单不存在', 'aksrt')];
}
return [
'success' => true,
'status' => $order['status'],
'transaction_id' => $order['payment_transaction_id'] ?? '',
];
}
public function refund(string $order_number, string $amount, string $request_id = ''): array {
$order_repo = OrderRepository::get_instance();
$order = $order_repo->get($order_number);
if (!$order) {
return ['success' => false, 'message' => __('订单不存在', 'aksrt')];
}
$user_id = (int) $order['user_id'];
if (!$this->balance_enabled()) {
return ['success' => false, 'message' => __('余额功能未启用', 'aksrt')];
}
$request_id = sanitize_text_field($request_id);
try {
$amount_minor = \AKSRT\Ledger\AssetAmount::balance($amount)->minor();
if ($amount_minor <= 0) {
return ['success' => false, 'message' => __('退款金额无效', 'aksrt')];
}
$operation_key = \AKSRT\Ledger\OperationKey::deterministic('order-refund', [$request_id !== '' ? $request_id : $order_number]);
$history = \AKSRT\Ledger\LedgerQuery::get_instance()->history(['operation_key' => $operation_key, 'page' => 1, 'per_page' => 1]);
if (!empty($history['items'][0])) {
$committed = $history['items'][0];
$refund_entry = null;
foreach ((array) ($committed['entries'] ?? []) as $entry) {
if ((int) ($entry['user_id'] ?? 0) === $user_id
&& (string) ($entry['asset'] ?? '') === \AKSRT\Ledger\AssetType::BALANCE->value
&& (string) ($entry['entry_type'] ?? '') === 'refund'
) {
$refund_entry = $entry;
break;
}
}
if ((string) ($committed['reason'] ?? '') !== 'order_refund'
|| (string) ($committed['ref_type'] ?? '') !== 'order'
|| (string) ($committed['ref_key'] ?? '') !== $order_number
|| !is_array($refund_entry)
|| (int) ($refund_entry['delta_minor'] ?? 0) !== $amount_minor
) {
return ['success' => false, 'message' => __('相同退款标识已用于其他请求', 'aksrt')];
}
return [
'success' => true,
'refund_id' => 'BAL_REF_' . $order_number,
'transaction_id' => (int) ($committed['id'] ?? 0),
'ledger_transaction_id' => (int) ($committed['id'] ?? 0),
'replayed' => true,
'balance' => \AKSRT\Ledger\LedgerService::get_instance()->balance_decimal($user_id),
'message' => __('退款成功', 'aksrt'),
];
}
$commission = CommissionLedger::get_instance();
$commission_plan = $commission->prepare_full_refund($order_number);
if (is_wp_error($commission_plan)) {
return ['success' => false, 'message' => $commission_plan->get_error_message()];
}
$guard = static function () use ($order_number, $user_id, $amount_minor, $commission, $commission_plan) {
global $wpdb;
$table = \AKSRT\Core\DBMigrator::table('order');
$current = $wpdb->get_row($wpdb->prepare(
"SELECT user_id, amount, payment_method, status FROM {$table} WHERE order_number = %s FOR UPDATE",
$order_number
), ARRAY_A);
try {
$order_minor = is_array($current)
? \AKSRT\Ledger\AssetAmount::balance((string) ($current['amount'] ?? ''))->minor()
: -1;
} catch (\InvalidArgumentException $e) {
$order_minor = -1;
}
if (!is_array($current)
|| (int) ($current['user_id'] ?? 0) !== $user_id
|| sanitize_key((string) ($current['payment_method'] ?? '')) !== 'balance'
|| $order_minor !== $amount_minor
|| !in_array((string) ($current['status'] ?? ''), [Order::STATUS_PAID, Order::STATUS_REFUNDING], true)
) {
return new \WP_Error('aksrt_order_refund_conflict', __('退款订单状态或金额已变化', 'aksrt'), ['status' => 409]);
}
return $commission->guard_full_refund($commission_plan);
};
$mutation = static function () use ($order_number, $commission, $commission_plan) {
$repo = OrderRepository::get_instance();
$current = $repo->get($order_number);
if (!is_array($current)) {
return new \WP_Error('aksrt_order_refund_failed', __('退款订单不存在', 'aksrt'), ['status' => 404]);
}
if ((string) ($current['status'] ?? '') !== Order::STATUS_REFUNDING
&& !$repo->update_status($order_number, Order::STATUS_REFUNDING, ['note' => __('余额退款处理中', 'aksrt')])) {
return new \WP_Error('aksrt_order_refund_failed', __('退款状态更新失败', 'aksrt'), ['status' => 409]);
}
if (!$repo->update_status($order_number, Order::STATUS_REFUNDED, ['note' => __('余额退款已完成', 'aksrt')])) {
return new \WP_Error('aksrt_order_refund_failed', __('退款状态更新失败', 'aksrt'), ['status' => 409]);
}
return $commission->apply_full_refund($commission_plan);
};
$entries = [[
'user_id' => $user_id,
'asset' => \AKSRT\Ledger\AssetType::BALANCE->value,
'delta_minor' => $amount_minor,
'entry_type' => 'refund',
'remark' => sprintf(__('订单 %s 退款', 'aksrt'), $order_number),
]];
if (!empty($commission_plan['active']) && (string) ($commission_plan['status'] ?? '') === 'settled') {
$entries[] = [
'user_id' => (int) $commission_plan['promoter_id'],
'asset' => \AKSRT\Ledger\AssetType::BALANCE->value,
'delta_minor' => -(int) $commission_plan['amount_minor'],
'entry_type' => 'commission_reversal',
'counterparty_user_id' => $user_id,
'remark' => sprintf(__('订单 %s 返佣退款冲正', 'aksrt'), $order_number),
];
}
$result = \AKSRT\Ledger\LedgerService::get_instance()->post(new \AKSRT\Ledger\LedgerCommand(
$operation_key,
'order_refund',
$entries,
'order',
$order_number,
0,
['request_id' => $request_id, 'commission_id' => (int) ($commission_plan['id'] ?? 0)]
), $guard, $mutation);
if (is_wp_error($result)) {
return ['success' => false, 'message' => $result->get_error_message() ?: __('退款失败', 'aksrt')];
}
return [
'success' => true,
'refund_id' => 'BAL_REF_' . $order_number,
'transaction_id' => $result->transaction_id,
'ledger_transaction_id' => $result->transaction_id,
'flow_number' => $result->flow_number,
'replayed' => $result->replayed,
'balance' => \AKSRT\Ledger\LedgerService::get_instance()->balance_decimal($user_id),
'message' => __('退款成功', 'aksrt'),
];
} catch (\Throwable $e) {
return ['success' => false, 'message' => $e->getMessage()];
}
}
private function balance_enabled(): bool {
if (!class_exists('\AKSRT\Settings\Settings')) {
return false;
}
$settings = \AKSRT\Settings\Settings::get_payment_recharge_settings();
return !empty($settings['enabled']);
}
}