refactor(classes): organize inc/classes into domain folders
Move ~94 root class shells and root *-traits into domain directories (core, auth, user, credits, content, payment/gateways, forum, shop, sms, ai, messages, extensions, widgets, settings, admin). Update bootstrap require paths, co-locate traits with owners, fix forum/user relative requires, drop duplicate shop controller requires, and wire home-modules load. Path-only; class names and init order unchanged.
This commit is contained in:
1 parent
2a8fe68048
commit
ce1c13b947
115 files changed
+171
-224
No files matched your search
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Alipay Gateway — 支付宝支付网关
|
||||
*
|
||||
* 使用 yurunsoft/pay-sdk 实现电脑网站支付、手机网站支付。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Alipay_Gateway implements AKSRT_Payment_Gateway_Interface {
|
||||
|
||||
public function get_id(): string {
|
||||
return 'alipay';
|
||||
}
|
||||
|
||||
public function get_name(): string {
|
||||
return __('支付宝', 'aksrt');
|
||||
}
|
||||
|
||||
public function is_configured(): bool {
|
||||
if (class_exists('AKSRT_Settings_Payment_Runtime')
|
||||
&& !AKSRT_Settings_Payment_Runtime::is_gateway_enabled('alipay')) {
|
||||
return false;
|
||||
}
|
||||
$config = $this->get_config();
|
||||
return !empty($config['app_id']) && !empty($config['private_key']) && !empty($config['alipay_public_key']);
|
||||
}
|
||||
|
||||
public function create_payment(array $order): array {
|
||||
if (!$this->is_configured()) {
|
||||
return ['success' => false, 'message' => __('支付宝未配置', 'aksrt')];
|
||||
}
|
||||
|
||||
$config = $this->get_config();
|
||||
$order_number = $order['order_number'] ?? '';
|
||||
$amount = (float) ($order['amount'] ?? 0);
|
||||
$subject = $order['subject'] ?? __('订单支付', 'aksrt');
|
||||
$payment_type = $config['payment_type'] ?? 'page'; // page / wap
|
||||
|
||||
try {
|
||||
$sdk = $this->create_sdk($config);
|
||||
|
||||
if ($payment_type === 'wap') {
|
||||
// 手机网站支付
|
||||
$request = new \Yurun\PaySDK\AlipayApp\Wap\Params\Pay\Request;
|
||||
$request->notify_url = home_url('/wp-json/aksrt/v1/payment/alipay/notify');
|
||||
$request->return_url = home_url('/wp-json/aksrt/v1/payment/alipay/return');
|
||||
$request->businessParams->out_trade_no = $order_number;
|
||||
$request->businessParams->total_amount = number_format($amount, 2, '.', '');
|
||||
$request->businessParams->subject = $subject;
|
||||
} else {
|
||||
// 电脑网站支付
|
||||
$request = new \Yurun\PaySDK\AlipayApp\Page\Params\Pay\Request;
|
||||
$request->notify_url = home_url('/wp-json/aksrt/v1/payment/alipay/notify');
|
||||
$request->return_url = home_url('/wp-json/aksrt/v1/payment/alipay/return');
|
||||
$request->businessParams->out_trade_no = $order_number;
|
||||
$request->businessParams->total_amount = number_format($amount, 2, '.', '');
|
||||
$request->businessParams->subject = $subject;
|
||||
}
|
||||
|
||||
// 构建支付跳转 URL(prepareExecute 已将查询参数追加到 $url)
|
||||
$sdk->prepareExecute($request, $url, $data);
|
||||
$payment_url = $url;
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'payment_url' => $payment_url,
|
||||
'payment_data' => ['order_number' => $order_number],
|
||||
'message' => __('支付宝支付链接已生成', 'aksrt'),
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
public function verify_callback(WP_REST_Request $request): array {
|
||||
$config = $this->get_config();
|
||||
|
||||
try {
|
||||
$sdk = $this->create_sdk($config);
|
||||
$params = $request->get_params();
|
||||
|
||||
if ($sdk->verifyCallback($params)) {
|
||||
$trade_status = $params['trade_status'] ?? '';
|
||||
return [
|
||||
'success' => true,
|
||||
'order_number' => $params['out_trade_no'] ?? '',
|
||||
'amount' => (float) ($params['total_amount'] ?? 0),
|
||||
'transaction_id' => $params['trade_no'] ?? '',
|
||||
'status' => in_array($trade_status, ['TRADE_SUCCESS', 'TRADE_FINISHED']) ? 'paid' : 'pending',
|
||||
];
|
||||
}
|
||||
|
||||
return ['success' => false, 'message' => __('支付宝回调验证失败', 'aksrt')];
|
||||
} catch (\Exception $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
public function query_status(string $order_number): array {
|
||||
if (!$this->is_configured()) {
|
||||
return ['success' => false, 'message' => __('支付宝未配置', 'aksrt')];
|
||||
}
|
||||
|
||||
$config = $this->get_config();
|
||||
|
||||
try {
|
||||
$sdk = $this->create_sdk($config);
|
||||
|
||||
$request = new \Yurun\PaySDK\AlipayApp\Params\Query\Request;
|
||||
$request->businessParams->out_trade_no = $order_number;
|
||||
|
||||
$result = $sdk->execute($request);
|
||||
|
||||
if ($sdk->checkResult()) {
|
||||
$trade_status = $result['trade_status'] ?? '';
|
||||
$status = 'pending';
|
||||
if (in_array($trade_status, ['TRADE_SUCCESS', 'TRADE_FINISHED'])) {
|
||||
$status = 'paid';
|
||||
} elseif ($trade_status === 'TRADE_CLOSED') {
|
||||
$status = 'cancelled';
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'status' => $status,
|
||||
'transaction_id' => $result['trade_no'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
return ['success' => false, 'message' => $sdk->getError() ?: __('查询失败', 'aksrt')];
|
||||
} catch (\Exception $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
public function refund(string $order_number, float $amount): array {
|
||||
if (!$this->is_configured()) {
|
||||
return ['success' => false, 'message' => __('支付宝未配置', 'aksrt')];
|
||||
}
|
||||
|
||||
$config = $this->get_config();
|
||||
|
||||
try {
|
||||
$sdk = $this->create_sdk($config);
|
||||
|
||||
$request = new \Yurun\PaySDK\AlipayApp\Params\Refund\Request;
|
||||
$request->businessParams->out_trade_no = $order_number;
|
||||
$request->businessParams->refund_amount = number_format($amount, 2, '.', '');
|
||||
$request->businessParams->refund_reason = __('用户申请退款', 'aksrt');
|
||||
$request->businessParams->out_request_no = $order_number . '_refund_' . time();
|
||||
|
||||
$result = $sdk->execute($request);
|
||||
|
||||
if ($sdk->checkResult()) {
|
||||
return [
|
||||
'success' => true,
|
||||
'refund_id' => $result['trade_no'] ?? '',
|
||||
'message' => __('退款成功', 'aksrt'),
|
||||
];
|
||||
}
|
||||
|
||||
return ['success' => false, 'message' => $sdk->getError() ?: __('退款失败', 'aksrt')];
|
||||
} catch (\Exception $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
private function get_config(): array {
|
||||
if (!class_exists('AKSRT_Settings')) {
|
||||
return [];
|
||||
}
|
||||
$all = AKSRT_Settings::get_all();
|
||||
return [
|
||||
'app_id' => (string) ($all['alipay_appid'] ?? ''),
|
||||
'private_key' => (string) ($all['alipay_privatekey'] ?? ''),
|
||||
'alipay_public_key' => (string) ($all['alipay_publickey'] ?? ''),
|
||||
'return_url' => (string) ($all['alipay_return_url'] ?? ''),
|
||||
'notify_url' => (string) ($all['alipay_notify_url'] ?? ''),
|
||||
'payment_type' => (string) ($all['alipay_payment_type'] ?? 'page'),
|
||||
'sign_type' => (string) ($all['alipay_sign_type'] ?? 'RSA2'),
|
||||
];
|
||||
}
|
||||
|
||||
private function create_sdk(array $config): \Yurun\PaySDK\AlipayApp\SDK {
|
||||
$params = new \Yurun\PaySDK\AlipayApp\Params\PublicParams;
|
||||
$params->appID = $config['app_id'] ?? '';
|
||||
$params->appPrivateKey = $config['private_key'] ?? '';
|
||||
$params->appPublicKey = $config['alipay_public_key'] ?? '';
|
||||
$params->sign_type = $config['sign_type'] ?? 'RSA2';
|
||||
|
||||
return new \Yurun\PaySDK\AlipayApp\SDK($params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT PayPal Gateway — PayPal 支付网关
|
||||
*
|
||||
* 使用 WordPress HTTP API 直接调用 PayPal REST API,无需第三方 SDK。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_PayPal_Gateway implements AKSRT_Payment_Gateway_Interface {
|
||||
|
||||
public function get_id(): string {
|
||||
return 'paypal';
|
||||
}
|
||||
|
||||
public function get_name(): string {
|
||||
return __('PayPal', 'aksrt');
|
||||
}
|
||||
|
||||
public function is_configured(): bool {
|
||||
if (class_exists('AKSRT_Settings_Payment_Runtime')
|
||||
&& !AKSRT_Settings_Payment_Runtime::is_gateway_enabled('paypal')) {
|
||||
return false;
|
||||
}
|
||||
$config = $this->get_config();
|
||||
return !empty($config['client_id']) && !empty($config['client_secret']);
|
||||
}
|
||||
|
||||
public function create_payment(array $order): array {
|
||||
if (!$this->is_configured()) {
|
||||
return ['success' => false, 'message' => __('PayPal 未配置', 'aksrt')];
|
||||
}
|
||||
|
||||
$config = $this->get_config();
|
||||
$order_number = $order['order_number'] ?? '';
|
||||
$amount = (float) ($order['amount'] ?? 0);
|
||||
$subject = $order['subject'] ?? __('订单支付', 'aksrt');
|
||||
$currency = $config['currency'] ?? 'USD';
|
||||
|
||||
try {
|
||||
$response = $this->api_request('POST', '/v2/checkout/orders', [
|
||||
'intent' => 'CAPTURE',
|
||||
'purchase_units' => [[
|
||||
'reference_id' => $order_number,
|
||||
'description' => $subject,
|
||||
'amount' => [
|
||||
'currency_code' => $currency,
|
||||
'value' => number_format($amount, 2, '.', ''),
|
||||
],
|
||||
]],
|
||||
'application_context' => [
|
||||
'return_url' => home_url('/wp-json/aksrt/v1/payment/paypal/return?order=' . $order_number),
|
||||
'cancel_url' => home_url('/wp-json/aksrt/v1/payment/paypal/cancel?order=' . $order_number),
|
||||
'brand_name' => get_bloginfo('name'),
|
||||
'user_action' => 'PAY_NOW',
|
||||
],
|
||||
]);
|
||||
|
||||
if ($response['status'] === 201) {
|
||||
$data = $response['body'];
|
||||
$approveUrl = '';
|
||||
foreach ($data['links'] ?? [] as $link) {
|
||||
if ($link['rel'] === 'approve') {
|
||||
$approveUrl = $link['href'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'payment_url' => $approveUrl,
|
||||
'payment_data' => [
|
||||
'order_number' => $order_number,
|
||||
'paypal_id' => $data['id'] ?? '',
|
||||
],
|
||||
'message' => __('PayPal 订单已创建', 'aksrt'),
|
||||
];
|
||||
}
|
||||
|
||||
return ['success' => false, 'message' => $response['body']['message'] ?? __('PayPal 创建失败', 'aksrt')];
|
||||
} catch (\Exception $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
public function verify_callback(WP_REST_Request $request): array {
|
||||
try {
|
||||
$body = json_decode($request->get_body(), true);
|
||||
$eventType = $body['event_type'] ?? '';
|
||||
|
||||
if ($eventType !== 'CHECKOUT.ORDER.APPROVED') {
|
||||
return ['success' => false, 'message' => __('非支付完成事件', 'aksrt')];
|
||||
}
|
||||
|
||||
$resource = $body['resource'] ?? [];
|
||||
$orderNumber = '';
|
||||
$amount = 0;
|
||||
|
||||
foreach ($resource['purchase_units'] ?? [] as $unit) {
|
||||
$orderNumber = $unit['reference_id'] ?? '';
|
||||
$amount = (float) ($unit['amount']['value'] ?? 0);
|
||||
break;
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'order_number' => $orderNumber,
|
||||
'amount' => $amount,
|
||||
'transaction_id' => $resource['id'] ?? '',
|
||||
'status' => 'paid',
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
public function query_status(string $order_number): array {
|
||||
return [
|
||||
'success' => true,
|
||||
'status' => 'pending',
|
||||
'message' => __('PayPal 状态通过 Webhook 更新', 'aksrt'),
|
||||
];
|
||||
}
|
||||
|
||||
public function refund(string $order_number, float $amount): array {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => __('PayPal 退款需要通过后台手动处理', 'aksrt'),
|
||||
];
|
||||
}
|
||||
|
||||
private function get_config(): array {
|
||||
if (!class_exists('AKSRT_Settings')) {
|
||||
return [];
|
||||
}
|
||||
$all = AKSRT_Settings::get_all();
|
||||
return [
|
||||
'client_id' => (string) ($all['paypal_client_id'] ?? ''),
|
||||
'client_secret' => (string) ($all['paypal_client_secret'] ?? ''),
|
||||
'currency' => (string) ($all['paypal_currency'] ?? 'USD'),
|
||||
'mode' => !empty($all['paypal_sandbox']) ? 'sandbox' : 'live',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 捕获已批准的 PayPal 订单(v2 CAPTURE 必需步骤)。
|
||||
* 用户在 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}
|
||||
*/
|
||||
public function capture_order(string $paypal_order_id): array {
|
||||
if ($paypal_order_id === '') {
|
||||
return ['success' => false, 'message' => __('缺少 PayPal 订单号', 'aksrt')];
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->api_request('POST', '/v2/checkout/orders/' . rawurlencode($paypal_order_id) . '/capture');
|
||||
$body = $response['body'];
|
||||
|
||||
if (in_array($response['status'], [200, 201], true) && ($body['status'] ?? '') === 'COMPLETED') {
|
||||
$unit = $body['purchase_units'][0] ?? [];
|
||||
$capture = $unit['payments']['captures'][0] ?? [];
|
||||
return [
|
||||
'success' => true,
|
||||
'order_number' => $unit['reference_id'] ?? '',
|
||||
'amount' => (float) ($capture['amount']['value'] ?? 0),
|
||||
'transaction_id' => $capture['id'] ?? ($body['id'] ?? ''),
|
||||
'status' => 'paid',
|
||||
];
|
||||
}
|
||||
|
||||
return ['success' => false, 'message' => $body['message'] ?? __('PayPal 捕获失败', 'aksrt')];
|
||||
} catch (\Exception $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 OAuth2 access token(v2 REST 必需)。
|
||||
*/
|
||||
private function get_access_token(): string {
|
||||
$config = $this->get_config();
|
||||
$isSandbox = ($config['mode'] ?? 'live') === 'sandbox';
|
||||
$baseUrl = $isSandbox ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';
|
||||
|
||||
$response = wp_remote_post($baseUrl . '/v1/oauth2/token', [
|
||||
'headers' => [
|
||||
'Authorization' => 'Basic ' . base64_encode(($config['client_id'] ?? '') . ':' . ($config['client_secret'] ?? '')),
|
||||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||
],
|
||||
'body' => 'grant_type=client_credentials',
|
||||
'timeout' => 30,
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
throw new \Exception($response->get_error_message());
|
||||
}
|
||||
|
||||
$body = json_decode(wp_remote_retrieve_body($response), true);
|
||||
$token = $body['access_token'] ?? '';
|
||||
if ($token === '') {
|
||||
throw new \Exception($body['error_description'] ?? __('PayPal 鉴权失败', 'aksrt'));
|
||||
}
|
||||
|
||||
return (string) $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用 PayPal REST API(带 OAuth2 Bearer token)
|
||||
*/
|
||||
private function api_request(string $method, string $endpoint, array $data = []): array {
|
||||
$config = $this->get_config();
|
||||
$isSandbox = ($config['mode'] ?? 'live') === 'sandbox';
|
||||
$baseUrl = $isSandbox
|
||||
? 'https://api-m.sandbox.paypal.com'
|
||||
: 'https://api-m.paypal.com';
|
||||
|
||||
$url = $baseUrl . $endpoint;
|
||||
|
||||
$headers = [
|
||||
'Content-Type' => 'application/json',
|
||||
'Authorization' => 'Bearer ' . $this->get_access_token(),
|
||||
];
|
||||
|
||||
$args = [
|
||||
'method' => $method,
|
||||
'headers' => $headers,
|
||||
'timeout' => 30,
|
||||
];
|
||||
if (!empty($data)) {
|
||||
$args['body'] = wp_json_encode($data);
|
||||
}
|
||||
|
||||
$response = wp_remote_request($url, $args);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
throw new \Exception($response->get_error_message());
|
||||
}
|
||||
|
||||
$status = wp_remote_retrieve_response_code($response);
|
||||
$body = json_decode(wp_remote_retrieve_body($response), true);
|
||||
|
||||
return [
|
||||
'status' => $status,
|
||||
'body' => $body ?: [],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Stripe Gateway — Stripe 支付网关
|
||||
*
|
||||
* 使用 stripe/stripe-php 实现 Checkout Session 支付、Webhook 回调、退款。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Stripe_Gateway implements AKSRT_Payment_Gateway_Interface {
|
||||
|
||||
public function get_id(): string {
|
||||
return 'stripe';
|
||||
}
|
||||
|
||||
public function get_name(): string {
|
||||
return __('Stripe', 'aksrt');
|
||||
}
|
||||
|
||||
public function is_configured(): bool {
|
||||
if (class_exists('AKSRT_Settings_Payment_Runtime')
|
||||
&& !AKSRT_Settings_Payment_Runtime::is_gateway_enabled('stripe')) {
|
||||
return false;
|
||||
}
|
||||
$config = $this->get_config();
|
||||
return !empty($config['secret_key']) && !empty($config['publishable_key']);
|
||||
}
|
||||
|
||||
public function create_payment(array $order): array {
|
||||
if (!$this->is_configured()) {
|
||||
return ['success' => false, 'message' => __('Stripe 未配置', 'aksrt')];
|
||||
}
|
||||
|
||||
$config = $this->get_config();
|
||||
$order_number = $order['order_number'] ?? '';
|
||||
$amount = (float) ($order['amount'] ?? 0);
|
||||
$subject = $order['subject'] ?? __('订单支付', 'aksrt');
|
||||
$currency = strtolower($config['currency'] ?? 'usd');
|
||||
|
||||
try {
|
||||
$stripe = $this->create_client($config);
|
||||
|
||||
// 创建 Checkout Session
|
||||
$session_params = [
|
||||
'payment_method_types' => $this->get_payment_methods($config),
|
||||
'line_items' => [[
|
||||
'price_data' => [
|
||||
'currency' => $currency,
|
||||
'product_data' => [
|
||||
'name' => $subject,
|
||||
],
|
||||
'unit_amount' => $this->to_smallest_unit($amount, $currency),
|
||||
],
|
||||
'quantity' => 1,
|
||||
]],
|
||||
'mode' => 'payment',
|
||||
'success_url' => home_url('/wp-json/aksrt/v1/payment/stripe/return?session_id={CHECKOUT_SESSION_ID}&order=' . $order_number),
|
||||
'cancel_url' => home_url('/wp-json/aksrt/v1/payment/stripe/cancel?order=' . $order_number),
|
||||
'metadata' => [
|
||||
'order_number' => $order_number,
|
||||
],
|
||||
];
|
||||
|
||||
// 启用自动支付方式(Apple Pay, Google Pay 等)
|
||||
if (!empty($config['auto_payment_methods'])) {
|
||||
$session_params['automatic_payment_methods'] = [
|
||||
'enabled' => true,
|
||||
];
|
||||
}
|
||||
|
||||
$session = \Stripe\Checkout\Session::create($session_params, [
|
||||
'api_key' => $config['secret_key'],
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'payment_url' => $session->url,
|
||||
'payment_data' => [
|
||||
'order_number' => $order_number,
|
||||
'session_id' => $session->id,
|
||||
],
|
||||
'message' => __('Stripe 支付页面已创建', 'aksrt'),
|
||||
];
|
||||
} catch (\Stripe\Exception\ApiErrorException $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
} catch (\Exception $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
public function verify_callback(WP_REST_Request $request): array {
|
||||
$config = $this->get_config();
|
||||
|
||||
try {
|
||||
$stripe = $this->create_client($config);
|
||||
|
||||
$payload = $request->get_body();
|
||||
$sigHeader = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '';
|
||||
|
||||
// 验证 Webhook 签名
|
||||
$event = \Stripe\Webhook::constructEvent(
|
||||
$payload,
|
||||
$sigHeader,
|
||||
$config['webhook_secret'] ?? ''
|
||||
);
|
||||
|
||||
if ($event->type !== 'checkout.session.completed') {
|
||||
return ['success' => false, 'message' => __('非支付完成事件', 'aksrt')];
|
||||
}
|
||||
|
||||
$session = $event->data->object;
|
||||
$orderNumber = $session->metadata->order_number ?? '';
|
||||
$amount = ($session->amount_total ?? 0) / 100;
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'order_number' => $orderNumber,
|
||||
'amount' => $amount,
|
||||
'transaction_id' => $session->payment_intent ?? $session->id,
|
||||
'status' => $session->payment_status === 'paid' ? 'paid' : 'pending',
|
||||
];
|
||||
} catch (\Stripe\Exception\SignatureVerificationException $e) {
|
||||
return ['success' => false, 'message' => __('Webhook 签名验证失败', 'aksrt')];
|
||||
} catch (\Exception $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
public function query_status(string $order_number): array {
|
||||
if (!$this->is_configured()) {
|
||||
return ['success' => false, 'message' => __('Stripe 未配置', 'aksrt')];
|
||||
}
|
||||
|
||||
$config = $this->get_config();
|
||||
|
||||
try {
|
||||
$stripe = $this->create_client($config);
|
||||
|
||||
// 注意:Stripe API 的 Session::all() 不支持按 metadata 过滤。
|
||||
// 此处通过创建时间倒序获取最近的 session,再匹配 metadata。
|
||||
// 生产环境建议在 create_payment 时保存 session_id 并用 Session::retrieve() 查询。
|
||||
$sessions = \Stripe\Checkout\Session::all([
|
||||
'limit' => 100,
|
||||
], ['api_key' => $config['secret_key']]);
|
||||
|
||||
// 手动按 metadata 匹配
|
||||
$found = null;
|
||||
foreach ($sessions->data as $session) {
|
||||
if (($session->metadata->order_number ?? '') === $order_number) {
|
||||
$found = $session;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($found) {
|
||||
$status = 'pending';
|
||||
if ($found->payment_status === 'paid') {
|
||||
$status = 'paid';
|
||||
} elseif ($found->status === 'expired') {
|
||||
$status = 'cancelled';
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'status' => $status,
|
||||
'transaction_id' => $found->payment_intent ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
return ['success' => true, 'status' => 'pending', 'message' => __('未找到支付记录', 'aksrt')];
|
||||
} catch (\Exception $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
public function refund(string $order_number, float $amount): array {
|
||||
if (!$this->is_configured()) {
|
||||
return ['success' => false, 'message' => __('Stripe 未配置', 'aksrt')];
|
||||
}
|
||||
|
||||
$config = $this->get_config();
|
||||
|
||||
try {
|
||||
$stripe = $this->create_client($config);
|
||||
|
||||
// 先查询 payment_intent(手动按 metadata 匹配)
|
||||
$sessions = \Stripe\Checkout\Session::all([
|
||||
'limit' => 100,
|
||||
], ['api_key' => $config['secret_key']]);
|
||||
|
||||
$found = null;
|
||||
foreach ($sessions->data as $session) {
|
||||
if (($session->metadata->order_number ?? '') === $order_number) {
|
||||
$found = $session;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
return ['success' => false, 'message' => __('未找到支付记录', 'aksrt')];
|
||||
}
|
||||
|
||||
$paymentIntentId = $found->payment_intent;
|
||||
|
||||
if (empty($paymentIntentId)) {
|
||||
return ['success' => false, 'message' => __('未找到支付意图', 'aksrt')];
|
||||
}
|
||||
|
||||
$currency = strtolower($config['currency'] ?? 'usd');
|
||||
|
||||
$refund = \Stripe\Refund::create([
|
||||
'payment_intent' => $paymentIntentId,
|
||||
'amount' => $this->to_smallest_unit($amount, $currency),
|
||||
'reason' => 'requested_by_customer',
|
||||
], ['api_key' => $config['secret_key']]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'refund_id' => $refund->id,
|
||||
'message' => __('退款成功', 'aksrt'),
|
||||
];
|
||||
} catch (\Stripe\Exception\ApiErrorException $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
} catch (\Exception $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Stripe Publishable Key(前端使用)。
|
||||
*/
|
||||
public function get_publishable_key(): string {
|
||||
$config = $this->get_config();
|
||||
return $config['publishable_key'] ?? '';
|
||||
}
|
||||
|
||||
private function get_config(): array {
|
||||
if (!class_exists('AKSRT_Settings')) {
|
||||
return [];
|
||||
}
|
||||
$all = AKSRT_Settings::get_all();
|
||||
return [
|
||||
'secret_key' => (string) ($all['stripe_secret_key'] ?? ''),
|
||||
'publishable_key' => (string) ($all['stripe_publishable_key'] ?? ''),
|
||||
'webhook_secret' => (string) ($all['stripe_webhook_secret'] ?? ''),
|
||||
'currency' => (string) ($all['stripe_currency'] ?? 'usd'),
|
||||
'mode' => !empty($all['stripe_test_mode']) ? 'test' : 'live',
|
||||
];
|
||||
}
|
||||
|
||||
private function create_client(array $config): void {
|
||||
\Stripe\Stripe::setApiKey($config['secret_key']);
|
||||
|
||||
// 设置 API 版本
|
||||
\Stripe\Stripe::setApiVersion('2023-10-16');
|
||||
|
||||
// 启用遥测(可选)
|
||||
if (!empty($config['enable_telemetry'])) {
|
||||
\Stripe\Stripe::setEnableTelemetry(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支持的支付方式。
|
||||
*/
|
||||
private function get_payment_methods(array $config): array {
|
||||
$methods = $config['payment_methods'] ?? ['card'];
|
||||
|
||||
// 验证支付方式
|
||||
$valid = ['card', 'alipay', 'wechat_pay', 'sepa_debit', 'ideal', 'bancontact', 'giropay', 'sofort'];
|
||||
return array_values(array_intersect($methods, $valid)) ?: ['card'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 将金额转换为最小单位(分/cent)。
|
||||
*/
|
||||
private function to_smallest_unit(float $amount, string $currency): int {
|
||||
// 零小数货币(日元、韩元等)
|
||||
$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 (int) round($amount * 100);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT WeChat Pay Gateway — 微信支付网关
|
||||
*
|
||||
* 使用 yurunsoft/pay-sdk 实现 Native 支付(扫码)、JSAPI 支付、H5 支付。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_WechatPay_Gateway implements AKSRT_Payment_Gateway_Interface {
|
||||
|
||||
public function get_id(): string {
|
||||
return 'wechatpay';
|
||||
}
|
||||
|
||||
public function get_name(): string {
|
||||
return __('微信支付', 'aksrt');
|
||||
}
|
||||
|
||||
public function is_configured(): bool {
|
||||
if (class_exists('AKSRT_Settings_Payment_Runtime')
|
||||
&& !AKSRT_Settings_Payment_Runtime::is_gateway_enabled('wechatpay')) {
|
||||
return false;
|
||||
}
|
||||
$config = $this->get_config();
|
||||
return !empty($config['mch_id']) && !empty($config['key']);
|
||||
}
|
||||
|
||||
public function create_payment(array $order): array {
|
||||
if (!$this->is_configured()) {
|
||||
return ['success' => false, 'message' => __('微信支付未配置', 'aksrt')];
|
||||
}
|
||||
|
||||
$config = $this->get_config();
|
||||
$order_number = $order['order_number'] ?? '';
|
||||
$amount = (float) ($order['amount'] ?? 0);
|
||||
$subject = $order['subject'] ?? __('订单支付', 'aksrt');
|
||||
$payment_type = $config['payment_type'] ?? 'native'; // native / jsapi / h5
|
||||
|
||||
try {
|
||||
$sdk = $this->create_sdk($config);
|
||||
$total_fee = (int) ($amount * 100); // 转为分
|
||||
|
||||
switch ($payment_type) {
|
||||
case 'jsapi':
|
||||
$request = new \Yurun\PaySDK\Weixin\JSAPI\Params\Pay\Request;
|
||||
$request->openid = $order['openid'] ?? '';
|
||||
break;
|
||||
case 'h5':
|
||||
$request = new \Yurun\PaySDK\Weixin\H5\Params\Pay\Request;
|
||||
break;
|
||||
case 'native':
|
||||
default:
|
||||
$request = new \Yurun\PaySDK\Weixin\Native\Params\Pay\Request;
|
||||
break;
|
||||
}
|
||||
|
||||
$request->body = $subject;
|
||||
$request->out_trade_no = $order_number;
|
||||
$request->total_fee = $total_fee;
|
||||
$request->fee_type = 'CNY';
|
||||
$request->notify_url = home_url('/wp-json/aksrt/v1/payment/wechatpay/notify');
|
||||
$request->spbill_create_ip = $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
|
||||
|
||||
$result = $sdk->execute($request);
|
||||
|
||||
if ($sdk->checkResult()) {
|
||||
$result_data = [
|
||||
'success' => true,
|
||||
'payment_data' => array_merge($result, ['order_number' => $order_number]),
|
||||
'message' => __('微信支付订单已创建', 'aksrt'),
|
||||
];
|
||||
|
||||
if ($payment_type === 'native' && !empty($result['code_url'])) {
|
||||
$result_data['payment_url'] = $result['code_url'];
|
||||
} elseif ($payment_type === 'h5' && !empty($result['h5_url'])) {
|
||||
$result_data['payment_url'] = $result['h5_url'];
|
||||
}
|
||||
|
||||
return $result_data;
|
||||
}
|
||||
|
||||
return ['success' => false, 'message' => $sdk->getError() ?: __('微信支付创建失败', 'aksrt')];
|
||||
} catch (\Exception $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
public function verify_callback(WP_REST_Request $request): array {
|
||||
$config = $this->get_config();
|
||||
|
||||
try {
|
||||
$sdk = $this->create_sdk($config);
|
||||
|
||||
// 读取原始请求体
|
||||
$xml = file_get_contents('php://input');
|
||||
$data = \Yurun\PaySDK\Lib\XML::fromString($xml);
|
||||
|
||||
if ($sdk->verifyCallback($data)) {
|
||||
return [
|
||||
'success' => true,
|
||||
'order_number' => $data['out_trade_no'] ?? '',
|
||||
'amount' => (($data['total_fee'] ?? 0) / 100),
|
||||
'transaction_id' => $data['transaction_id'] ?? '',
|
||||
'status' => ($data['result_code'] ?? '') === 'SUCCESS' ? 'paid' : 'pending',
|
||||
];
|
||||
}
|
||||
|
||||
return ['success' => false, 'message' => __('微信支付回调签名验证失败', 'aksrt')];
|
||||
} catch (\Exception $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出微信支付回调响应
|
||||
*/
|
||||
public function output_callback_response(array $result): void {
|
||||
if ($result['success'] ?? false) {
|
||||
echo '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
|
||||
} else {
|
||||
echo '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[' . ($result['message'] ?? 'FAIL') . ']]></return_msg></xml>';
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
public function query_status(string $order_number): array {
|
||||
if (!$this->is_configured()) {
|
||||
return ['success' => false, 'message' => __('微信支付未配置', 'aksrt')];
|
||||
}
|
||||
|
||||
$config = $this->get_config();
|
||||
|
||||
try {
|
||||
$sdk = $this->create_sdk($config);
|
||||
|
||||
$request = new \Yurun\PaySDK\Weixin\OrderQuery\Request;
|
||||
$request->out_trade_no = $order_number;
|
||||
|
||||
$result = $sdk->execute($request);
|
||||
|
||||
if ($sdk->checkResult()) {
|
||||
$trade_state = $result['trade_state'] ?? '';
|
||||
$status = 'pending';
|
||||
if ($trade_state === 'SUCCESS') {
|
||||
$status = 'paid';
|
||||
} elseif (in_array($trade_state, ['CLOSED', 'REVOKED'])) {
|
||||
$status = 'cancelled';
|
||||
} elseif ($trade_state === 'REFUND') {
|
||||
$status = 'refunded';
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'status' => $status,
|
||||
'transaction_id' => $result['transaction_id'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
return ['success' => false, 'message' => $sdk->getError() ?: __('查询失败', 'aksrt')];
|
||||
} catch (\Exception $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
public function refund(string $order_number, float $amount): array {
|
||||
if (!$this->is_configured()) {
|
||||
return ['success' => false, 'message' => __('微信支付未配置', 'aksrt')];
|
||||
}
|
||||
|
||||
$config = $this->get_config();
|
||||
|
||||
try {
|
||||
$sdk = $this->create_sdk($config);
|
||||
|
||||
// 先查询订单获取 transaction_id
|
||||
$query = new \Yurun\PaySDK\Weixin\OrderQuery\Request;
|
||||
$query->out_trade_no = $order_number;
|
||||
$query_result = $sdk->execute($query);
|
||||
|
||||
if (!$sdk->checkResult() || empty($query_result['transaction_id'])) {
|
||||
return ['success' => false, 'message' => __('未找到支付记录', 'aksrt')];
|
||||
}
|
||||
|
||||
$refund_fee = (int) ($amount * 100);
|
||||
|
||||
$request = new \Yurun\PaySDK\Weixin\Refund\Request;
|
||||
$request->out_trade_no = $order_number;
|
||||
$request->out_refund_no = 'REF_' . $order_number . '_' . time();
|
||||
$request->total_fee = (int) ($query_result['total_fee'] ?? $refund_fee);
|
||||
$request->refund_fee = $refund_fee;
|
||||
$request->refund_desc = __('用户申请退款', 'aksrt');
|
||||
|
||||
$result = $sdk->execute($request);
|
||||
|
||||
if ($sdk->checkResult()) {
|
||||
return [
|
||||
'success' => true,
|
||||
'refund_id' => $result['refund_id'] ?? '',
|
||||
'message' => __('退款成功', 'aksrt'),
|
||||
];
|
||||
}
|
||||
|
||||
return ['success' => false, 'message' => $sdk->getError() ?: __('退款失败', 'aksrt')];
|
||||
} catch (\Exception $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
private function get_config(): array {
|
||||
if (!class_exists('AKSRT_Settings')) {
|
||||
return [];
|
||||
}
|
||||
$all = AKSRT_Settings::get_all();
|
||||
return [
|
||||
'mch_id' => (string) ($all['wechat_merchantid'] ?? ''),
|
||||
'key' => (string) ($all['wechat_jsapi'] ?? ''),
|
||||
'appid' => (string) ($all['wechat_appid'] ?? ''),
|
||||
'appsecret' => (string) ($all['wechat_appsecret'] ?? ''),
|
||||
'cert_path' => (string) ($all['wechat_cert_path'] ?? ''),
|
||||
'key_path' => (string) ($all['wechat_key_path'] ?? ''),
|
||||
'payment_type' => (string) ($all['wechat_payment_type'] ?? 'native'),
|
||||
];
|
||||
}
|
||||
|
||||
private function create_sdk(array $config): \Yurun\PaySDK\Weixin\SDK {
|
||||
$params = new \Yurun\PaySDK\Weixin\Params\PublicParams;
|
||||
$params->appID = $config['appid'] ?? '';
|
||||
$params->mch_id = $config['mch_id'] ?? '';
|
||||
$params->key = $config['key'] ?? '';
|
||||
$params->sign_type = $config['sign_type'] ?? 'HMAC-SHA256';
|
||||
|
||||
if (!empty($config['cert_path'])) {
|
||||
$params->certPath = $config['cert_path'];
|
||||
}
|
||||
if (!empty($config['key_path'])) {
|
||||
$params->keyPath = $config['key_path'];
|
||||
}
|
||||
|
||||
return new \Yurun\PaySDK\Weixin\SDK($params);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user