积分与余额账务系统硬切重构

This commit is contained in:
kate522 committed 2026-08-24 15:59:11 +08:00
1 parent ca12226b39
commit 5cae5b5c93
159 files changed
+7275 -4718

No files matched your search

+30 -12
View File
@@ -46,11 +46,11 @@ class AlipayGateway implements \AKSRT\Payment\GatewayInterface {
? \AKSRT\Payment\OrderRepository::provider_out_trade_no($order)
: $order_number;
}
$amount = (float) ($order['amount'] ?? 0);
$amount = $this->amount_string($order['amount'] ?? null);
$subject = (string) ($order['subject'] ?? __('订单支付', 'aksrt'));
$payment_type = $this->get_config()['payment_type'];
if ($order_number === '' || $out_trade_no === '' || $amount <= 0) {
if ($order_number === '' || $out_trade_no === '' || $amount === '') {
return ['success' => false, 'message' => __('订单参数无效', 'aksrt')];
}
@@ -60,7 +60,7 @@ class AlipayGateway implements \AKSRT\Payment\GatewayInterface {
$payload = [
'_method' => 'GET',
'out_trade_no' => $out_trade_no,
'total_amount' => number_format($amount, 2, '.', ''),
'total_amount' => $amount,
'subject' => mb_substr($subject, 0, 256),
];
@@ -109,16 +109,17 @@ class AlipayGateway implements \AKSRT\Payment\GatewayInterface {
$order_number = (string) ($data['out_trade_no'] ?? '');
$trade_no = (string) ($data['trade_no'] ?? '');
$amount = $this->amount_string($data['total_amount'] ?? null);
if (($data['app_id'] ?? '') !== $config['app_id']
|| $order_number === ''
|| ($paid && ($trade_no === '' || (float) ($data['total_amount'] ?? 0) <= 0))) {
|| ($paid && ($trade_no === '' || $amount === ''))) {
return ['success' => false, 'message' => __('支付宝回调身份或订单数据无效', 'aksrt')];
}
return [
'success' => true,
'order_number' => $order_number,
'amount' => (float) ($data['total_amount'] ?? 0),
'amount' => $amount,
'currency' => 'CNY',
'transaction_id' => $trade_no,
'status' => $paid ? 'paid' : 'pending',
@@ -171,7 +172,7 @@ class AlipayGateway implements \AKSRT\Payment\GatewayInterface {
'success' => true,
// Keep provider out_trade_no for Gateway::complete_order resolve.
'order_number' => (string) ($data['out_trade_no'] ?? $out_trade_no),
'amount' => (float) ($data['total_amount'] ?? 0),
'amount' => $this->amount_string($data['total_amount'] ?? null),
'currency' => 'CNY',
'transaction_id' => (string) ($data['trade_no'] ?? ''),
'status' => $status,
@@ -200,8 +201,13 @@ class AlipayGateway implements \AKSRT\Payment\GatewayInterface {
}
}
public function refund(string $order_number, float $amount, string $request_id = ''): array {
if ($amount <= 0) {
public function refund(string $order_number, string $amount, string $request_id = ''): array {
try {
$refund_amount = \AKSRT\Ledger\AssetAmount::balance($amount);
} catch (\InvalidArgumentException $e) {
return ['success' => false, 'status' => 'failed', 'message' => __('退款金额无效', 'aksrt')];
}
if ($refund_amount->minor() <= 0) {
return ['success' => false, 'status' => 'failed', 'message' => __('退款金额无效', 'aksrt')];
}
if (!$this->is_configured()) {
@@ -219,14 +225,14 @@ class AlipayGateway implements \AKSRT\Payment\GatewayInterface {
);
$result = Pay::alipay()->refund([
'out_trade_no' => $out_trade_no,
'refund_amount' => number_format($amount, 2, '.', ''),
'refund_amount' => $refund_amount->decimal(),
'refund_reason' => __('用户申请退款', 'aksrt'),
'out_request_no' => $refund_request_id,
]);
$data = YansongdaPayFactory::to_array($result);
$fund_change = strtoupper((string) ($data['fund_change'] ?? ''));
$status = ($fund_change === 'Y' || (float) ($data['refund_fee'] ?? 0) > 0)
$status = ($fund_change === 'Y' || $this->amount_string($data['refund_fee'] ?? null) !== '')
? 'succeeded'
: 'unknown';
@@ -256,8 +262,8 @@ class AlipayGateway implements \AKSRT\Payment\GatewayInterface {
$result = Pay::alipay()->query($params);
$data = YansongdaPayFactory::to_array($result);
$refund_status = strtoupper((string) ($data['refund_status'] ?? ''));
$refund_amount = (float) ($data['refund_amount'] ?? 0);
$status = ($refund_status === 'REFUND_SUCCESS' || $refund_amount > 0)
$refund_amount = $this->amount_string($data['refund_amount'] ?? null);
$status = ($refund_status === 'REFUND_SUCCESS' || $refund_amount !== '')
? 'succeeded'
: ($refund_status === 'REFUND_FAIL' ? 'failed' : 'pending');
return [
@@ -291,6 +297,18 @@ class AlipayGateway implements \AKSRT\Payment\GatewayInterface {
];
}
private function amount_string(mixed $value): string {
if (!is_string($value)) {
return '';
}
try {
$amount = \AKSRT\Ledger\AssetAmount::balance(trim($value));
return $amount->minor() > 0 ? $amount->decimal() : '';
} catch (\InvalidArgumentException $e) {
return '';
}
}
/**
* Map local order_number → provider out_trade_no (supports resume attempts).
*/
+21 -9
View File
@@ -50,9 +50,9 @@ class EpayGateway implements \AKSRT\Payment\GatewayInterface {
? \AKSRT\Payment\OrderRepository::provider_out_trade_no($order)
: $order_number;
}
$amount = (float) ($order['amount'] ?? 0);
$amount = $this->amount_string($order['amount'] ?? null);
$subject = (string) ($order['subject'] ?? __('订单支付', 'aksrt'));
if ($order_number === '' || $out_trade_no === '' || $amount <= 0) {
if ($order_number === '' || $out_trade_no === '' || $amount === null) {
return ['success' => false, 'message' => __('订单参数无效', 'aksrt')];
}
@@ -64,7 +64,7 @@ class EpayGateway implements \AKSRT\Payment\GatewayInterface {
'notify_url' => rest_url('aksrt/v1/payment/' . $this->get_id() . '/notify'),
'return_url' => rest_url('aksrt/v1/payment/' . $this->get_id() . '/return'),
'name' => mb_substr(wp_strip_all_tags($subject), 0, 127),
'money' => number_format($amount, 2, '.', ''),
'money' => $amount,
];
$params['sign'] = $this->sign($params, $config['key']);
$params['sign_type'] = 'MD5';
@@ -147,7 +147,7 @@ class EpayGateway implements \AKSRT\Payment\GatewayInterface {
$order_number = sanitize_text_field((string) ($params['out_trade_no'] ?? ''));
$transaction_id = sanitize_text_field((string) ($params['trade_no'] ?? ''));
$amount = (float) ($params['money'] ?? 0);
$amount = $this->amount_string($params['money'] ?? null);
$trade_status = (string) ($params['trade_status'] ?? '');
$type = sanitize_key((string) ($params['type'] ?? ''));
@@ -157,14 +157,14 @@ class EpayGateway implements \AKSRT\Payment\GatewayInterface {
}
$paid = ($trade_status === '' || $trade_status === 'TRADE_SUCCESS');
if ($order_number === '' || ($paid && ($transaction_id === '' || $amount <= 0))) {
if ($order_number === '' || ($paid && ($transaction_id === '' || $amount === null))) {
return ['success' => false, 'message' => __('易支付回调数据无效', 'aksrt')];
}
return [
'success' => true,
'order_number' => $order_number,
'amount' => $amount,
'amount' => $amount ?? '0.00',
'currency' => 'CNY',
'transaction_id' => $transaction_id,
'status' => $paid ? 'paid' : 'pending',
@@ -209,20 +209,20 @@ class EpayGateway implements \AKSRT\Payment\GatewayInterface {
$status = (int) ($body['status'] ?? $body['trade_status'] ?? 0);
$paid = $code === 1 && ((string) $status === '1' || (string) ($body['trade_status'] ?? '') === 'TRADE_SUCCESS');
$amount = (float) ($body['money'] ?? $body['amount'] ?? 0);
$amount = $this->amount_string($body['money'] ?? $body['amount'] ?? null);
$tx = (string) ($body['trade_no'] ?? $body['api_trade_no'] ?? '');
return [
'success' => true,
'order_number' => (string) ($body['out_trade_no'] ?? $out_trade_no),
'amount' => $amount,
'amount' => $amount ?? '0.00',
'currency' => 'CNY',
'transaction_id' => $tx,
'status' => $paid ? 'paid' : 'pending',
];
}
public function refund(string $order_number, float $amount, string $request_id = ''): array {
public function refund(string $order_number, string $amount, string $request_id = ''): array {
return [
'success' => false,
'status' => 'failed',
@@ -288,6 +288,18 @@ class EpayGateway implements \AKSRT\Payment\GatewayInterface {
return rtrim($apiurl, '/') . '/' . ltrim($file, '/');
}
private function amount_string(mixed $value): ?string {
if (!is_string($value)) {
return null;
}
try {
$amount = \AKSRT\Ledger\AssetAmount::balance(trim($value));
return $amount->minor() > 0 ? $amount->decimal() : null;
} catch (\InvalidArgumentException $e) {
return null;
}
}
private function prefer_mobile(string $device): bool {
if ($device === 'mobile') {
return true;
+24 -5
View File
@@ -40,13 +40,16 @@ class PayPalGateway implements \AKSRT\Payment\GatewayInterface {
? \AKSRT\Payment\OrderRepository::provider_out_trade_no($order)
: $order_number;
}
$amount = (float) ($order['amount'] ?? 0);
$amount = $this->amount_string($order['amount'] ?? null);
$subject = $order['subject'] ?? __('订单支付', 'aksrt');
$order_currency = strtoupper((string) ($order['currency'] ?? ''));
$currency = strtoupper((string) ($config['currency'] ?? 'USD'));
if ($order_currency === '' || !hash_equals($order_currency, $currency)) {
return ['success' => false, 'message' => __('PayPal 币种与订单币种不一致', 'aksrt')];
}
if ($order_number === '' || $out_trade_no === '' || $amount === null) {
return ['success' => false, 'message' => __('订单金额无效', 'aksrt')];
}
try {
$response = $this->api_request('POST', '/v2/checkout/orders', [
@@ -56,7 +59,7 @@ class PayPalGateway implements \AKSRT\Payment\GatewayInterface {
'description' => $subject,
'amount' => [
'currency_code' => $currency,
'value' => number_format($amount, 2, '.', ''),
'value' => $amount,
],
]],
'application_context' => [
@@ -142,7 +145,7 @@ class PayPalGateway implements \AKSRT\Payment\GatewayInterface {
}
}
public function refund(string $order_number, float $amount, string $request_id = ''): array {
public function refund(string $order_number, string $amount, string $request_id = ''): array {
return [
'success' => false,
'message' => __('PayPal 退款需要通过后台手动处理', 'aksrt'),
@@ -167,7 +170,7 @@ class PayPalGateway implements \AKSRT\Payment\GatewayInterface {
* 用户在 return_url 返回后调用,确认真正扣款。
*
* @param string $paypal_order_id PayPal 订单 ID(create 时返回的 id)
* @return array{success:bool, order_number?:string, amount?:float, transaction_id?:string, status?:string, message?:string}
* @return array{success:bool, order_number?:string, amount?:string, transaction_id?:string, status?:string, message?:string}
*/
public function capture_order(string $paypal_order_id): array {
if ($paypal_order_id === '') {
@@ -192,16 +195,32 @@ class PayPalGateway implements \AKSRT\Payment\GatewayInterface {
private function completed_order_evidence(array $body): array {
$unit = $body['purchase_units'][0] ?? [];
$capture = $unit['payments']['captures'][0] ?? [];
$amount = $this->amount_string($capture['amount']['value'] ?? null);
if ($amount === null) {
return ['success' => false, 'message' => __('PayPal 返回金额无效', 'aksrt')];
}
return [
'success' => true,
'order_number' => (string) ($unit['reference_id'] ?? ''),
'amount' => (float) ($capture['amount']['value'] ?? 0),
'amount' => $amount,
'currency' => strtoupper((string) ($capture['amount']['currency_code'] ?? '')),
'transaction_id' => (string) ($capture['id'] ?? ($body['id'] ?? '')),
'status' => 'paid',
];
}
private function amount_string(mixed $value): ?string {
if (!is_string($value)) {
return null;
}
try {
$amount = \AKSRT\Ledger\AssetAmount::balance(trim($value));
return $amount->minor() > 0 ? $amount->decimal() : null;
} catch (\InvalidArgumentException $e) {
return null;
}
}
/**
* 获取 OAuth2 access token(v2 REST 必需)。
*/
+37 -10
View File
@@ -42,13 +42,17 @@ class StripeGateway implements \AKSRT\Payment\GatewayInterface {
? \AKSRT\Payment\OrderRepository::provider_out_trade_no($order)
: $order_number;
}
$amount = (float) ($order['amount'] ?? 0);
$amount = is_string($order['amount'] ?? null) ? trim($order['amount']) : '';
$subject = $order['subject'] ?? __('订单支付', 'aksrt');
$order_currency = strtoupper((string) ($order['currency'] ?? ''));
$currency = strtolower($config['currency'] ?? 'usd');
if ($order_currency === '' || !hash_equals($order_currency, strtoupper($currency))) {
return ['success' => false, 'message' => __('Stripe 币种与订单币种不一致', 'aksrt')];
}
$smallest_amount = $this->to_smallest_unit($amount, $currency);
if ($order_number === '' || $out_trade_no === '' || $smallest_amount === null || $smallest_amount <= 0) {
return ['success' => false, 'message' => __('订单金额无效', 'aksrt')];
}
try {
$stripe = $this->create_client($config);
@@ -62,7 +66,7 @@ class StripeGateway implements \AKSRT\Payment\GatewayInterface {
'product_data' => [
'name' => $subject,
],
'unit_amount' => $this->to_smallest_unit($amount, $currency),
'unit_amount' => $smallest_amount,
],
'quantity' => 1,
]],
@@ -204,7 +208,15 @@ class StripeGateway implements \AKSRT\Payment\GatewayInterface {
}
}
public function refund(string $order_number, float $amount, string $request_id = ''): array {
public function refund(string $order_number, string $amount, string $request_id = ''): array {
try {
$refund_amount = \AKSRT\Ledger\AssetAmount::balance($amount);
} catch (\InvalidArgumentException $e) {
return ['success' => false, 'message' => __('退款金额无效', 'aksrt')];
}
if ($refund_amount->minor() <= 0) {
return ['success' => false, 'message' => __('退款金额无效', 'aksrt')];
}
if (!$this->is_configured()) {
return ['success' => false, 'message' => __('Stripe 未配置', 'aksrt')];
}
@@ -228,6 +240,10 @@ class StripeGateway implements \AKSRT\Payment\GatewayInterface {
}
$currency = strtolower((string) ($found->currency ?? $config['currency'] ?? 'usd'));
$refund_smallest = $this->to_smallest_unit($refund_amount->decimal(), $currency);
if ($refund_smallest === null || $refund_smallest <= 0) {
return ['success' => false, 'message' => __('退款金额与币种精度不匹配', 'aksrt')];
}
$options = ['api_key' => $config['secret_key']];
if ($request_id !== '') {
@@ -235,7 +251,7 @@ class StripeGateway implements \AKSRT\Payment\GatewayInterface {
}
$refund = \Stripe\Refund::create([
'payment_intent' => $paymentIntentId,
'amount' => $this->to_smallest_unit($amount, $currency),
'amount' => $refund_smallest,
'reason' => 'requested_by_customer',
'metadata' => [
'aksrt_request_id' => $request_id,
@@ -390,26 +406,37 @@ class StripeGateway implements \AKSRT\Payment\GatewayInterface {
return array_values(array_intersect($methods, $valid)) ?: ['card'];
}
private function from_smallest_unit(int $amount, string $currency): float {
private function from_smallest_unit(int $amount, string $currency): string {
if ($amount < 0) {
return '0.00';
}
$zero_decimal = ['bif', 'clp', 'djf', 'gnf', 'jpy', 'kmf', 'krw', 'mga', 'pyg', 'rwf', 'ugx', 'vnd', 'vuv', 'xaf', 'xof', 'xpf'];
if (in_array(strtolower($currency), $zero_decimal, true)) {
return (float) $amount;
if ($amount > intdiv(PHP_INT_MAX, 100)) {
return '0.00';
}
return \AKSRT\Ledger\AssetAmount::from_minor(\AKSRT\Ledger\AssetType::BALANCE, $amount * 100)->decimal();
}
return $amount / 100;
return \AKSRT\Ledger\AssetAmount::from_minor(\AKSRT\Ledger\AssetType::BALANCE, $amount)->decimal();
}
/**
* 将金额转换为最小单位(分/cent)。
*/
private function to_smallest_unit(float $amount, string $currency): int {
private function to_smallest_unit(string $amount, string $currency): ?int {
try {
$parsed = \AKSRT\Ledger\AssetAmount::balance($amount);
} catch (\InvalidArgumentException $e) {
return null;
}
// 零小数货币(日元、韩元等)
$zeroDecimal = ['bif', 'clp', 'djf', 'gnf', 'jpy', 'kmf', 'krw', 'mga', 'pyg', 'rwf', 'ugx', 'vnd', 'vuv', 'xaf', 'xof', 'xpf'];
if (in_array(strtolower($currency), $zeroDecimal, true)) {
return (int) $amount;
return $parsed->minor() % 100 === 0 ? intdiv($parsed->minor(), 100) : null;
}
return (int) round($amount * 100);
return $parsed->minor();
}
}
+24 -6
View File
@@ -47,10 +47,13 @@ class WechatPayGateway implements \AKSRT\Payment\GatewayInterface {
? \AKSRT\Payment\OrderRepository::provider_out_trade_no($order)
: $order_number;
}
$amount = (float) ($order['amount'] ?? 0);
$subject = (string) ($order['subject'] ?? __('订单支付', 'aksrt'));
$payment_type = $this->get_config()['payment_type'];
$total_fen = (int) round($amount * 100);
try {
$total_fen = \AKSRT\Ledger\AssetAmount::balance(is_string($order['amount'] ?? null) ? trim($order['amount']) : '')->minor();
} catch (\InvalidArgumentException $e) {
$total_fen = 0;
}
if ($order_number === '' || $out_trade_no === '' || $total_fen <= 0) {
return ['success' => false, 'message' => __('订单参数无效', 'aksrt')];
@@ -178,7 +181,7 @@ class WechatPayGateway implements \AKSRT\Payment\GatewayInterface {
return [
'success' => true,
'order_number' => $order_number,
'amount' => $total_fen > 0 ? ($total_fen / 100) : 0.0,
'amount' => $this->fen_decimal($total_fen),
'currency' => strtoupper((string) YansongdaPayFactory::dig($data, 'amount.currency', 'CNY')),
'transaction_id' => $transaction_id,
'status' => $paid ? 'paid' : 'pending',
@@ -242,7 +245,7 @@ class WechatPayGateway implements \AKSRT\Payment\GatewayInterface {
'success' => true,
// Keep provider out_trade_no so Gateway::complete_order can resolve the local order.
'order_number' => (string) ($data['out_trade_no'] ?? $out_trade_no),
'amount' => ((int) YansongdaPayFactory::dig($data, 'amount.total', 0)) / 100,
'amount' => $this->fen_decimal((int) YansongdaPayFactory::dig($data, 'amount.total', 0)),
'currency' => strtoupper((string) YansongdaPayFactory::dig($data, 'amount.currency', 'CNY')),
'transaction_id' => (string) ($data['transaction_id'] ?? ''),
'status' => $status,
@@ -271,7 +274,15 @@ class WechatPayGateway implements \AKSRT\Payment\GatewayInterface {
}
}
public function refund(string $order_number, float $amount, string $request_id = ''): array {
public function refund(string $order_number, string $amount, string $request_id = ''): array {
try {
$refund_amount = \AKSRT\Ledger\AssetAmount::balance($amount);
} catch (\InvalidArgumentException $e) {
return ['success' => false, 'status' => 'failed', 'message' => __('退款金额无效', 'aksrt')];
}
if ($refund_amount->minor() <= 0) {
return ['success' => false, 'status' => 'failed', 'message' => __('退款金额无效', 'aksrt')];
}
if (!$this->is_configured()) {
return ['success' => false, 'message' => __('微信支付未配置', 'aksrt')];
}
@@ -288,7 +299,7 @@ class WechatPayGateway implements \AKSRT\Payment\GatewayInterface {
]);
$q = YansongdaPayFactory::to_array($query);
$total_fen = (int) YansongdaPayFactory::dig($q, 'amount.total', 0);
$refund_fen = (int) round($amount * 100);
$refund_fen = $refund_amount->minor();
if ($total_fen <= 0) {
return ['success' => false, 'status' => 'failed', 'message' => __('无法确认原订单金额', 'aksrt')];
}
@@ -387,6 +398,13 @@ class WechatPayGateway implements \AKSRT\Payment\GatewayInterface {
return $map[$type] ?? 'native';
}
private function fen_decimal(int $amount): string {
return \AKSRT\Ledger\AssetAmount::from_minor(
\AKSRT\Ledger\AssetType::BALANCE,
max(0, $amount)
)->decimal();
}
/**
* Map local order_number → provider out_trade_no (supports resume attempts).
*/