refactor: flatten src/AKSRT → src (drop theme-identifier dir)
Move all domain folders from src/AKSRT/{Domain} to src/{Domain}; remap
Composer PSR-4 "AKSRT\\": "src/"; update audit script + README + ADR
paths; remove dead tools/build-psr4-map.php (scanned deleted inc/classes).
This commit is contained in:
1 parent
56f1a8175f
commit
8c5b1555c9
285 files changed
+26
-3600
No files matched your search
@@ -0,0 +1,311 @@
|
||||
<?php
|
||||
/**
|
||||
* Alipay gateway via yansongda/pay (cert mode).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Payment\Gateways;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
use Yansongda\Pay\Pay;
|
||||
|
||||
class AlipayGateway implements \AKSRT\Payment\GatewayInterface {
|
||||
|
||||
public function get_id(): string {
|
||||
return 'alipay';
|
||||
}
|
||||
|
||||
public function get_name(): string {
|
||||
return __('支付宝', 'aksrt');
|
||||
}
|
||||
|
||||
public function is_configured(): bool {
|
||||
if (class_exists('\AKSRT\Settings\PaymentRuntime')
|
||||
&& !\AKSRT\Settings\PaymentRuntime::is_gateway_enabled('alipay')) {
|
||||
return false;
|
||||
}
|
||||
$c = $this->get_config();
|
||||
return $c['app_id'] !== ''
|
||||
&& $c['private_key'] !== ''
|
||||
&& $c['app_public_cert'] !== ''
|
||||
&& $c['alipay_public_cert'] !== ''
|
||||
&& $c['alipay_root_cert'] !== '';
|
||||
}
|
||||
|
||||
public function create_payment(array $order): array {
|
||||
if (!$this->is_configured()) {
|
||||
return ['success' => false, 'message' => __('支付宝未配置', 'aksrt')];
|
||||
}
|
||||
|
||||
$order_number = (string) ($order['order_number'] ?? '');
|
||||
$out_trade_no = (string) ($order['out_trade_no'] ?? '');
|
||||
if ($out_trade_no === '') {
|
||||
$out_trade_no = class_exists('\AKSRT\Payment\OrderRepository')
|
||||
? \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'];
|
||||
|
||||
if ($order_number === '' || $out_trade_no === '' || $amount <= 0) {
|
||||
return ['success' => false, 'message' => __('订单参数无效', 'aksrt')];
|
||||
}
|
||||
|
||||
try {
|
||||
YansongdaPayFactory::boot_alipay();
|
||||
|
||||
$payload = [
|
||||
'_method' => 'GET',
|
||||
'out_trade_no' => $out_trade_no,
|
||||
'total_amount' => number_format($amount, 2, '.', ''),
|
||||
'subject' => mb_substr($subject, 0, 256),
|
||||
];
|
||||
|
||||
$result = $payment_type === 'wap'
|
||||
? Pay::alipay()->h5($payload)
|
||||
: Pay::alipay()->web($payload);
|
||||
|
||||
$payment_url = YansongdaPayFactory::extract_payment_url($result);
|
||||
if ($payment_url === '') {
|
||||
return ['success' => false, 'message' => __('支付宝支付链接生成失败', 'aksrt')];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'payment_url' => $payment_url,
|
||||
'payment_data' => [
|
||||
'order_number' => $order_number,
|
||||
'out_trade_no' => $out_trade_no,
|
||||
],
|
||||
'message' => __('支付宝支付链接已生成', 'aksrt'),
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
public function verify_callback(\WP_REST_Request $request): array {
|
||||
try {
|
||||
YansongdaPayFactory::boot_alipay();
|
||||
|
||||
// Use the unsanitized form/query payload; route params must not enter the signature base.
|
||||
$callback_input = strtoupper($request->get_method()) === 'POST'
|
||||
? $request->get_body_params()
|
||||
: $request->get_query_params();
|
||||
if (!is_array($callback_input)) {
|
||||
$callback_input = [];
|
||||
}
|
||||
unset($callback_input['gateway']);
|
||||
|
||||
$data_obj = Pay::alipay()->callback($callback_input);
|
||||
$data = YansongdaPayFactory::to_array($data_obj);
|
||||
|
||||
$config = $this->get_config();
|
||||
$trade_status = (string) ($data['trade_status'] ?? '');
|
||||
$paid = in_array($trade_status, ['TRADE_SUCCESS', 'TRADE_FINISHED'], true);
|
||||
$order_number = (string) ($data['out_trade_no'] ?? '');
|
||||
$trade_no = (string) ($data['trade_no'] ?? '');
|
||||
|
||||
if (($data['app_id'] ?? '') !== $config['app_id']
|
||||
|| $order_number === ''
|
||||
|| ($paid && ($trade_no === '' || (float) ($data['total_amount'] ?? 0) <= 0))) {
|
||||
return ['success' => false, 'message' => __('支付宝回调身份或订单数据无效', 'aksrt')];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'order_number' => $order_number,
|
||||
'amount' => (float) ($data['total_amount'] ?? 0),
|
||||
'currency' => 'CNY',
|
||||
'transaction_id' => $trade_no,
|
||||
'status' => $paid ? 'paid' : 'pending',
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alipay async notify expects plain text success/fail (Gateway also handles this).
|
||||
*/
|
||||
public function output_callback_response(array $result): void {
|
||||
try {
|
||||
if (!empty($result['success'])) {
|
||||
YansongdaPayFactory::boot_alipay();
|
||||
YansongdaPayFactory::emit_response(Pay::alipay()->success(), 'success');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// fall through
|
||||
}
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'fail';
|
||||
exit;
|
||||
}
|
||||
|
||||
public function query_status(string $order_number): array {
|
||||
if (!$this->is_configured()) {
|
||||
return ['success' => false, 'message' => __('支付宝未配置', 'aksrt')];
|
||||
}
|
||||
|
||||
$out_trade_no = $this->resolve_out_trade_no($order_number);
|
||||
|
||||
try {
|
||||
YansongdaPayFactory::boot_alipay();
|
||||
$result = Pay::alipay()->query([
|
||||
'out_trade_no' => $out_trade_no,
|
||||
]);
|
||||
$data = YansongdaPayFactory::to_array($result);
|
||||
|
||||
$trade_status = (string) ($data['trade_status'] ?? '');
|
||||
$status = 'pending';
|
||||
if (in_array($trade_status, ['TRADE_SUCCESS', 'TRADE_FINISHED'], true)) {
|
||||
$status = 'paid';
|
||||
} elseif ($trade_status === 'TRADE_CLOSED') {
|
||||
$status = 'cancelled';
|
||||
}
|
||||
|
||||
return [
|
||||
'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),
|
||||
'currency' => 'CNY',
|
||||
'transaction_id' => (string) ($data['trade_no'] ?? ''),
|
||||
'status' => $status,
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Best-effort close of a previous provider trade before re-pay.
|
||||
*/
|
||||
public function close_trade(string $out_trade_no): void {
|
||||
$out_trade_no = sanitize_text_field($out_trade_no);
|
||||
if ($out_trade_no === '' || !$this->is_configured()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
YansongdaPayFactory::boot_alipay();
|
||||
// alipay.trade.close — supported by yansongda as close().
|
||||
Pay::alipay()->close([
|
||||
'out_trade_no' => $out_trade_no,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
public function refund(string $order_number, float $amount, string $request_id = ''): array {
|
||||
if ($amount <= 0) {
|
||||
return ['success' => false, 'status' => 'failed', 'message' => __('退款金额无效', 'aksrt')];
|
||||
}
|
||||
if (!$this->is_configured()) {
|
||||
return ['success' => false, 'message' => __('支付宝未配置', 'aksrt')];
|
||||
}
|
||||
|
||||
$out_trade_no = $this->resolve_out_trade_no($order_number);
|
||||
|
||||
try {
|
||||
YansongdaPayFactory::boot_alipay();
|
||||
$refund_request_id = (string) preg_replace(
|
||||
'/[^A-Za-z0-9_-]/',
|
||||
'',
|
||||
$request_id !== '' ? $request_id : $order_number . '_refund'
|
||||
);
|
||||
$result = Pay::alipay()->refund([
|
||||
'out_trade_no' => $out_trade_no,
|
||||
'refund_amount' => number_format($amount, 2, '.', ''),
|
||||
'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)
|
||||
? 'succeeded'
|
||||
: 'unknown';
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'status' => $status,
|
||||
'refund_id' => (string) ($data['trade_no'] ?? $data['out_trade_no'] ?? ''),
|
||||
'request_id' => $refund_request_id,
|
||||
'message' => $status === 'succeeded' ? __('退款成功', 'aksrt') : __('退款结果待确认', 'aksrt'),
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
public function query_refund(string $request_id, string $order_number = ''): array {
|
||||
if (!$this->is_configured() || $request_id === '') {
|
||||
return ['success' => false, 'status' => 'failed', 'message' => __('支付宝退款查询参数无效', 'aksrt')];
|
||||
}
|
||||
|
||||
try {
|
||||
YansongdaPayFactory::boot_alipay();
|
||||
$params = ['out_request_no' => $request_id, '_action' => 'refund'];
|
||||
if ($order_number !== '') {
|
||||
$params['out_trade_no'] = $this->resolve_out_trade_no($order_number);
|
||||
}
|
||||
$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)
|
||||
? 'succeeded'
|
||||
: ($refund_status === 'REFUND_FAIL' ? 'failed' : 'pending');
|
||||
return [
|
||||
'success' => true,
|
||||
'status' => $status,
|
||||
'refund_id' => (string) ($data['trade_no'] ?? $data['out_request_no'] ?? ''),
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
return ['success' => false, 'status' => 'unknown', 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{app_id:string,private_key:string,app_public_cert:string,alipay_public_cert:string,alipay_root_cert:string,payment_type:string}
|
||||
*/
|
||||
private function get_config(): array {
|
||||
$all = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_all()
|
||||
: [];
|
||||
$type = sanitize_key((string) ($all['alipay_payment_type'] ?? 'page'));
|
||||
if (!in_array($type, ['page', 'wap'], true)) {
|
||||
$type = 'page';
|
||||
}
|
||||
return [
|
||||
'app_id' => (string) ($all['alipay_appid'] ?? ''),
|
||||
'private_key' => (string) ($all['alipay_privatekey'] ?? ''),
|
||||
'app_public_cert' => (string) ($all['alipay_app_public_cert_path'] ?? ''),
|
||||
'alipay_public_cert' => (string) ($all['alipay_public_cert_path'] ?? ''),
|
||||
'alipay_root_cert' => (string) ($all['alipay_root_cert_path'] ?? ''),
|
||||
'payment_type' => $type,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Map local order_number → provider out_trade_no (supports resume attempts).
|
||||
*/
|
||||
private function resolve_out_trade_no(string $order_number): string {
|
||||
if (class_exists('\AKSRT\Payment\OrderRepository')) {
|
||||
$repo = \AKSRT\Payment\OrderRepository::get_instance();
|
||||
// A provider attempt id must be queried verbatim, including superseded Pn.
|
||||
if ($repo->resolve_by_out_trade_no($order_number) && !$repo->get($order_number)) {
|
||||
return $order_number;
|
||||
}
|
||||
$order = $repo->get($order_number);
|
||||
if ($order) {
|
||||
return \AKSRT\Payment\OrderRepository::provider_out_trade_no($order);
|
||||
}
|
||||
}
|
||||
return $order_number;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user