246 lines
9.1 KiB
PHP
246 lines
9.1 KiB
PHP
<?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 {
|
|
$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 {
|
|
// 优先从统一设置读取(schema 保存的 key)
|
|
if (class_exists('AKSRT_Settings')) {
|
|
$all = AKSRT_Settings::get_all();
|
|
if (!empty($all['wechat_merchantid'])) {
|
|
return [
|
|
'mch_id' => $all['wechat_merchantid'] ?? '',
|
|
'key' => $all['wechat_jsapi'] ?? '',
|
|
'appid' => $all['wechat_appid'] ?? '',
|
|
'appsecret' => $all['wechat_appsecret'] ?? '',
|
|
'cert_path' => $all['wechat_cert_path'] ?? '',
|
|
'key_path' => $all['wechat_key_path'] ?? '',
|
|
'payment_type' => $all['wechat_payment_type'] ?? 'native',
|
|
];
|
|
}
|
|
}
|
|
// 回退到独立 option
|
|
$settings = get_option('aksrt_payment_wechatpay', []);
|
|
return is_array($settings) ? $settings : [];
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|