289 lines
12 KiB
PHP
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']);
|
|
}
|
|
}
|