diff --git a/assets/admin/src/schema/sections/payment/gateway.js b/assets/admin/src/schema/sections/payment/gateway.js
index c0f24a0f..f6c79009 100644
--- a/assets/admin/src/schema/sections/payment/gateway.js
+++ b/assets/admin/src/schema/sections/payment/gateway.js
@@ -22,15 +22,17 @@ export const gatewaySchema = {
payment_codepay_enabled: false,
payment_stripe_enabled: false,
wechat_merchantid: '',
- wechat_jsapi: '',
- wechat_appsecret: '',
+ wechat_appid: '',
+ wechat_payment_type: 'native',
+ wechat_api_v3_key: '',
+ wechat_private_key_path: '',
wechat_cert_path: '',
- wechat_key_path: '',
alipay_appid: '',
alipay_privatekey: '',
- alipay_publickey: '',
- alipay_return_url: '',
- alipay_notify_url: '',
+ alipay_payment_type: 'page',
+ alipay_app_public_cert_path: '',
+ alipay_public_cert_path: '',
+ alipay_root_cert_path: '',
paypal_client_id: '',
paypal_client_secret: '',
paypal_currency: 'USD',
@@ -94,10 +96,14 @@ export const gatewaySchema = {
dependsOn: { key: 'payment_wechat_enabled', value: true },
fields: [
{ key: 'wechat_merchantid', label: '商户号', type: 'input', default: '' },
- { key: 'wechat_jsapi', label: 'JSAPI密钥', type: 'input', default: '' },
- { key: 'wechat_appsecret', label: 'AppSecret', type: 'input', default: '' },
- { key: 'wechat_cert_path', label: '证书路径', type: 'input', default: '', help: '退款等接口需要,绝对路径。' },
- { key: 'wechat_key_path', label: '密钥路径', type: 'input', default: '' },
+ { key: 'wechat_appid', label: 'AppID', type: 'input', default: '' },
+ { key: 'wechat_payment_type', label: '支付方式', type: 'select', default: 'native', options: [
+ { label: '扫码支付(Native)', value: 'native' },
+ { label: 'H5支付', value: 'h5' },
+ ] },
+ { key: 'wechat_api_v3_key', label: 'APIv3密钥', type: 'input', default: '', help: '微信商户平台设置的 APIv3 密钥(32位)。' },
+ { key: 'wechat_private_key_path', label: '商户私钥路径', type: 'input', default: '', help: 'apiclient_key.pem 绝对路径,用于签名。' },
+ { key: 'wechat_cert_path', label: '商户API证书路径', type: 'input', default: '', help: 'apiclient_cert.pem 绝对路径。' },
],
},
{
@@ -111,9 +117,14 @@ export const gatewaySchema = {
fields: [
{ key: 'alipay_appid', label: 'AppID', type: 'input', default: '' },
{ key: 'alipay_privatekey', label: '应用私钥', type: 'textarea', rows: 3, default: '' },
- { key: 'alipay_publickey', label: '支付宝公钥', type: 'textarea', rows: 3, default: '' },
- { key: 'alipay_return_url', label: '同步回调地址', type: 'input', default: '' },
- { key: 'alipay_notify_url', label: '异步回调地址', type: 'input', default: '' },
+ { key: 'alipay_payment_type', label: '支付方式', type: 'select', default: 'page', options: [
+ { label: '电脑网站支付(web)', value: 'page' },
+ { label: '手机网站支付(wap)', value: 'wap' },
+ ] },
+ { key: 'alipay_app_public_cert_path', label: '应用公钥证书路径', type: 'input', default: '', help: 'appCertPublicKey.crt 绝对路径。' },
+ { key: 'alipay_public_cert_path', label: '支付宝公钥证书路径', type: 'input', default: '', help: 'alipayCertPublicKey_RSA2.crt 绝对路径。' },
+ { key: 'alipay_root_cert_path', label: '支付宝根证书路径', type: 'input', default: '', help: 'alipayRootCert.crt 绝对路径。' },
+ { key: 'alipay_notify_help', label: '回调地址说明', type: 'notice', alertType: 'info', alertContent: '异步通知 / 同步返回已固定为 /wp-json/aksrt/v1/payment/alipay/{notify|return},无需在此填写。' },
],
},
{
diff --git a/composer.json b/composer.json
index 33372bf6..4d863951 100644
--- a/composer.json
+++ b/composer.json
@@ -5,7 +5,7 @@
"require": {
"php": ">=7.4",
"stripe/stripe-php": "^16.1",
- "yurunsoft/pay-sdk": "~3.0",
+ "yansongda/pay": "^3.7",
"yurunsoft/yurun-oauth-login": "~3.0"
},
"config": {
diff --git a/composer.lock b/composer.lock
index 70058c82..25d124b6 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,8 +4,337 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "8f8c952e2744521268680c9c6f7ad76a",
+ "content-hash": "fe1258c91a142e623f464f5ddaef58f9",
"packages": [
+ {
+ "name": "guzzlehttp/psr7",
+ "version": "2.13.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "dad89620b7a6edb60c15858442eb2e408b45d8f4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/dad89620b7a6edb60c15858442eb2e408b45d8f4",
+ "reference": "dad89620b7a6edb60c15858442eb2e408b45d8f4",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0",
+ "psr/http-factory": "^1.0",
+ "psr/http-message": "^1.1 || ^2.0",
+ "ralouphie/getallheaders": "^3.0",
+ "symfony/deprecation-contracts": "^2.5 || ^3.0",
+ "symfony/polyfill-php80": "^1.25"
+ },
+ "provide": {
+ "psr/http-factory-implementation": "1.0",
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "http-interop/http-factory-tests": "1.1.0",
+ "jshttp/mime-db": "1.54.0.1",
+ "phpunit/phpunit": "^8.5.52 || ^9.6.34"
+ },
+ "suggest": {
+ "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "George Mponos",
+ "email": "gmponos@gmail.com",
+ "homepage": "https://github.com/gmponos"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://github.com/sagikazarmark"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://sagikazarmark.hu"
+ }
+ ],
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": [
+ "http",
+ "message",
+ "psr-7",
+ "request",
+ "response",
+ "stream",
+ "uri",
+ "url"
+ ],
+ "support": {
+ "issues": "https://github.com/guzzle/psr7/issues",
+ "source": "https://github.com/guzzle/psr7/tree/2.13.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-07-16T22:23:49+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/container/issues",
+ "source": "https://github.com/php-fig/container/tree/2.0.2"
+ },
+ "time": "2021-11-05T16:47:00+00:00"
+ },
+ {
+ "name": "psr/event-dispatcher",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/event-dispatcher.git",
+ "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
+ "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\EventDispatcher\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Standard interfaces for event handling.",
+ "keywords": [
+ "events",
+ "psr",
+ "psr-14"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/event-dispatcher/issues",
+ "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
+ },
+ "time": "2019-01-08T18:20:26+00:00"
+ },
+ {
+ "name": "psr/http-client",
+ "version": "1.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-client.git",
+ "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
+ "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0 || ^8.0",
+ "psr/http-message": "^1.0 || ^2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Client\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP clients",
+ "homepage": "https://github.com/php-fig/http-client",
+ "keywords": [
+ "http",
+ "http-client",
+ "psr",
+ "psr-18"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-client"
+ },
+ "time": "2023-09-23T14:17:50+00:00"
+ },
+ {
+ "name": "psr/http-factory",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-factory.git",
+ "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+ "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1",
+ "psr/http-message": "^1.0 || ^2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
+ "keywords": [
+ "factory",
+ "http",
+ "message",
+ "psr",
+ "psr-17",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-factory"
+ },
+ "time": "2024-04-15T12:06:14+00:00"
+ },
{
"name": "psr/http-message",
"version": "2.0",
@@ -109,6 +438,50 @@
},
"time": "2024-09-11T13:17:53+00:00"
},
+ {
+ "name": "ralouphie/getallheaders",
+ "version": "3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ralouphie/getallheaders.git",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.1",
+ "phpunit/phpunit": "^5 || ^6.5"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/getallheaders.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "description": "A polyfill for getallheaders.",
+ "support": {
+ "issues": "https://github.com/ralouphie/getallheaders/issues",
+ "source": "https://github.com/ralouphie/getallheaders/tree/develop"
+ },
+ "time": "2019-03-08T08:55:37+00:00"
+ },
{
"name": "stripe/stripe-php",
"version": "v16.6.0",
@@ -169,45 +542,367 @@
"time": "2025-02-24T22:35:29+00:00"
},
{
- "name": "yurunsoft/pay-sdk",
- "version": "v3.1.7",
+ "name": "symfony/deprecation-contracts",
+ "version": "v3.7.1",
"source": {
"type": "git",
- "url": "https://github.com/Yurunsoft/PaySDK.git",
- "reference": "e146a056f3aa1b68f64bbc5e261bf137b7723307"
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Yurunsoft/PaySDK/zipball/e146a056f3aa1b68f64bbc5e261bf137b7723307",
- "reference": "e146a056f3aa1b68f64bbc5e261bf137b7723307",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d",
+ "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d",
"shasum": ""
},
"require": {
- "php": ">=5.5",
- "yurunsoft/yurun-http": "~4.0|~5.0"
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.7-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A generic function and convention to trigger deprecation notices",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-06-05T06:23:12+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php80",
+ "version": "v1.37.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
+ "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-04-10T16:19:22+00:00"
+ },
+ {
+ "name": "yansongda/artful",
+ "version": "v1.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/yansongda/artful.git",
+ "reference": "6fb48671fe99c3008566481b16771edcf82ecd0e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/yansongda/artful/zipball/6fb48671fe99c3008566481b16771edcf82ecd0e",
+ "reference": "6fb48671fe99c3008566481b16771edcf82ecd0e",
+ "shasum": ""
+ },
+ "require": {
+ "guzzlehttp/psr7": "^2.6",
+ "php": ">=8.0",
+ "psr/container": "^1.1 || ^2.0",
+ "psr/event-dispatcher": "^1.0",
+ "psr/http-client": "^1.0",
+ "psr/http-message": "^1.1 || ^2.0",
+ "psr/log": "^1.1 || ^2.0 || ^3.0",
+ "yansongda/supports": "~4.0.10"
},
"require-dev": {
- "friendsofphp/php-cs-fixer": "2.18.3"
+ "friendsofphp/php-cs-fixer": "^3.44",
+ "guzzlehttp/guzzle": "^7.0",
+ "hyperf/pimple": "^2.2",
+ "mockery/mockery": "^1.4",
+ "monolog/monolog": "^2.2",
+ "phpstan/phpstan": "^1.0.0 || ^2.0.0",
+ "phpunit/phpunit": "^9.0",
+ "symfony/event-dispatcher": "^5.2.0",
+ "symfony/http-foundation": "^5.2.0",
+ "symfony/psr-http-message-bridge": "^2.1",
+ "symfony/var-dumper": "^5.1"
+ },
+ "suggest": {
+ "hyperf/pimple": "其它/无框架下使用 SDK,请安装,任选其一",
+ "illuminate/container": "其它/无框架下使用 SDK,请安装,任选其一"
},
- "bin": [
- "src/Weixin/Tool/CertificateDownloader.php"
- ],
"type": "library",
"autoload": {
+ "files": [
+ "src/Functions.php"
+ ],
"psr-4": {
- "Yurun\\PaySDK\\": "src/"
+ "Yansongda\\Artful\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
- "description": "PHP 集成支付 SDK ,集成了支付宝、微信支付的支付接口和其它相关接口的操作。",
+ "authors": [
+ {
+ "name": "yansongda",
+ "email": "me@yansongda.cn"
+ }
+ ],
+ "description": "Artful 是一个简单易用的 API 请求框架 PHP Api RequesT Framwork U Like。",
+ "keywords": [
+ "api",
+ "artful",
+ "framework",
+ "request"
+ ],
"support": {
- "issues": "https://github.com/Yurunsoft/PaySDK/issues",
- "source": "https://github.com/Yurunsoft/PaySDK/tree/v3.1.7"
+ "homepage": "https://artful.yansongda.cn",
+ "issues": "https://github.com/yansongda/artful/issues",
+ "source": "https://github.com/yansongda/artful"
},
- "time": "2024-09-02T06:00:28+00:00"
+ "time": "2026-04-15T06:37:37+00:00"
+ },
+ {
+ "name": "yansongda/pay",
+ "version": "v3.7.20",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/yansongda/pay.git",
+ "reference": "26987ebf789f1e7f0a85febb640986ab4289fd7f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/yansongda/pay/zipball/26987ebf789f1e7f0a85febb640986ab4289fd7f",
+ "reference": "26987ebf789f1e7f0a85febb640986ab4289fd7f",
+ "shasum": ""
+ },
+ "require": {
+ "ext-bcmath": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-openssl": "*",
+ "ext-simplexml": "*",
+ "php": ">=8.0",
+ "yansongda/artful": "~1.1.3",
+ "yansongda/supports": "~4.0.10"
+ },
+ "conflict": {
+ "hyperf/framework": "<3.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.44",
+ "guzzlehttp/guzzle": "^7.0",
+ "hyperf/pimple": "^2.2",
+ "jetbrains/phpstorm-attributes": "^1.1",
+ "mockery/mockery": "^1.4",
+ "monolog/monolog": "^2.2",
+ "phpstan/phpstan": "^1.0.0 || ^2.0.0",
+ "phpunit/phpunit": "^9.0",
+ "symfony/event-dispatcher": "^5.2.0",
+ "symfony/http-foundation": "^5.2.0",
+ "symfony/psr-http-message-bridge": "^2.1",
+ "symfony/var-dumper": "^5.1"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/Functions.php"
+ ],
+ "psr-4": {
+ "Yansongda\\Pay\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "yansongda",
+ "email": "me@yansongda.cn"
+ }
+ ],
+ "description": "可能是我用过的最优雅的 Alipay 和 WeChat 的支付 SDK 扩展包了",
+ "keywords": [
+ "alipay",
+ "pay",
+ "wechat"
+ ],
+ "support": {
+ "homepage": "https://pay.yansongda.cn",
+ "issues": "https://github.com/yansongda/pay/issues",
+ "source": "https://github.com/yansongda/pay"
+ },
+ "time": "2026-03-23T07:33:29+00:00"
+ },
+ {
+ "name": "yansongda/supports",
+ "version": "v4.0.12",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/yansongda/supports.git",
+ "reference": "308de376d20cb1cd4f959644793e0582ccd1ef6d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/yansongda/supports/zipball/308de376d20cb1cd4f959644793e0582ccd1ef6d",
+ "reference": "308de376d20cb1cd4f959644793e0582ccd1ef6d",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-mbstring": "*",
+ "php": ">=8.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.0",
+ "mockery/mockery": "^1.4",
+ "phpstan/phpstan": "^1.1.0",
+ "phpunit/phpunit": "^9.0"
+ },
+ "suggest": {
+ "monolog/monolog": "Use logger",
+ "symfony/console": "Use stdout logger"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/Functions.php"
+ ],
+ "psr-4": {
+ "Yansongda\\Supports\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "yansongda",
+ "email": "me@yansongda.cn"
+ }
+ ],
+ "description": "common components",
+ "keywords": [
+ "array",
+ "collection",
+ "config",
+ "support"
+ ],
+ "support": {
+ "issues": "https://github.com/yansongda/supports/issues",
+ "source": "https://github.com/yansongda/supports"
+ },
+ "time": "2025-01-08T08:55:20+00:00"
},
{
"name": "yurunsoft/yurun-http",
diff --git a/src/AKSRT/Admin/Schema/SchemaPayment.php b/src/AKSRT/Admin/Schema/SchemaPayment.php
index 5b0fe017..3a23fc44 100644
--- a/src/AKSRT/Admin/Schema/SchemaPayment.php
+++ b/src/AKSRT/Admin/Schema/SchemaPayment.php
@@ -247,17 +247,26 @@ class SchemaPayment {
// Field-level dependsOn kept for contract audit / non-SPA consumers.
['key' => 'wechat', 'title' => '微信支付配置', 'collapsible' => true, 'expandWhen' => 'payment_wechat_enabled', 'dependsOn' => ['key' => 'payment_wechat_enabled', 'value' => true], 'fields' => [
['key' => 'wechat_merchantid', 'label' => '商户号', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_wechat_enabled', 'value' => true]],
- ['key' => 'wechat_jsapi', 'label' => 'JSAPI密钥', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_wechat_enabled', 'value' => true]],
- ['key' => 'wechat_appsecret', 'label' => 'AppSecret', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_wechat_enabled', 'value' => true]],
- ['key' => 'wechat_cert_path', 'label' => '证书路径', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_wechat_enabled', 'value' => true]],
- ['key' => 'wechat_key_path', 'label' => '密钥路径', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_wechat_enabled', 'value' => true]],
+ ['key' => 'wechat_appid', 'label' => 'AppID', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_wechat_enabled', 'value' => true]],
+ ['key' => 'wechat_payment_type', 'label' => '支付方式', 'type' => 'select', 'default' => 'native', 'options' => [
+ ['value' => 'native', 'label' => '扫码支付(Native)'],
+ ['value' => 'h5', 'label' => 'H5支付'],
+ ], 'dependsOn' => ['key' => 'payment_wechat_enabled', 'value' => true]],
+ ['key' => 'wechat_api_v3_key', 'label' => 'APIv3密钥', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_wechat_enabled', 'value' => true]],
+ ['key' => 'wechat_private_key_path', 'label' => '商户私钥路径', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_wechat_enabled', 'value' => true]],
+ ['key' => 'wechat_cert_path', 'label' => '商户API证书路径', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_wechat_enabled', 'value' => true]],
]],
['key' => 'alipay', 'title' => '支付宝配置', 'collapsible' => true, 'expandWhen' => 'payment_alipay_enabled', 'dependsOn' => ['key' => 'payment_alipay_enabled', 'value' => true], 'fields' => [
['key' => 'alipay_appid', 'label' => 'AppID', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_alipay_enabled', 'value' => true]],
['key' => 'alipay_privatekey', 'label' => '应用私钥', 'type' => 'textarea', 'default' => '', 'dependsOn' => ['key' => 'payment_alipay_enabled', 'value' => true]],
- ['key' => 'alipay_publickey', 'label' => '支付宝公钥', 'type' => 'textarea', 'default' => '', 'dependsOn' => ['key' => 'payment_alipay_enabled', 'value' => true]],
- ['key' => 'alipay_return_url', 'label' => '同步回调地址', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_alipay_enabled', 'value' => true]],
- ['key' => 'alipay_notify_url', 'label' => '异步回调地址', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_alipay_enabled', 'value' => true]],
+ ['key' => 'alipay_payment_type', 'label' => '支付方式', 'type' => 'select', 'default' => 'page', 'options' => [
+ ['value' => 'page', 'label' => '电脑网站支付(web)'],
+ ['value' => 'wap', 'label' => '手机网站支付(wap)'],
+ ], 'dependsOn' => ['key' => 'payment_alipay_enabled', 'value' => true]],
+ ['key' => 'alipay_app_public_cert_path', 'label' => '应用公钥证书路径', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_alipay_enabled', 'value' => true]],
+ ['key' => 'alipay_public_cert_path', 'label' => '支付宝公钥证书路径', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_alipay_enabled', 'value' => true]],
+ ['key' => 'alipay_root_cert_path', 'label' => '支付宝根证书路径', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_alipay_enabled', 'value' => true]],
+ ['key' => 'alipay_notify_help', 'label' => '回调地址说明', 'type' => 'notice', 'alertType' => 'info', 'alertContent' => '异步通知 / 同步返回已固定为 /wp-json/aksrt/v1/payment/alipay/{notify|return},无需在此填写。', 'dependsOn' => ['key' => 'payment_alipay_enabled', 'value' => true]],
]],
['key' => 'paypal', 'title' => 'PayPal配置', 'collapsible' => true, 'expandWhen' => 'payment_paypal_enabled', 'dependsOn' => ['key' => 'payment_paypal_enabled', 'value' => true], 'fields' => [
['key' => 'paypal_client_id', 'label' => 'Client ID', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_paypal_enabled', 'value' => true]],
diff --git a/src/AKSRT/Payment/AfterSale.php b/src/AKSRT/Payment/AfterSale.php
index 9a11c216..57f5abb2 100644
--- a/src/AKSRT/Payment/AfterSale.php
+++ b/src/AKSRT/Payment/AfterSale.php
@@ -328,6 +328,10 @@ class AfterSale {
$update['resolved_at'] = current_time('mysql');
}
+ if ($new_status === self::STATUS_COMPLETED && !$this->complete_refund($record)) {
+ return false;
+ }
+
$updated = (bool) $wpdb->update($this->table(), $update, ['id' => $id]);
if ($updated) {
$note = $operator_note !== '' ? $operator_note : sprintf(__('售后状态更新为:%s', 'aksrt'), $this->get_status_label($new_status));
@@ -565,6 +569,74 @@ class AfterSale {
do_action('aksrt_shop_refund_executed', $order_number, $user_id, $refund_amount, $payment_method);
}
+ /**
+ * Execute an online-channel refund before completing the after-sale ticket.
+ */
+ private function complete_refund(array $record): bool {
+ $type = (string) ($record['type'] ?? '');
+ if (!in_array($type, [self::TYPE_REFUND, self::TYPE_REFUND_RETURN, self::TYPE_INSURED_PRICE], true)) {
+ return true;
+ }
+
+ if (!class_exists('AKSRT\\Payment\\OrderRepository')) {
+ return false;
+ }
+
+ $order_number = sanitize_text_field((string) ($record['order_number'] ?? ''));
+ $order = OrderRepository::get_instance()->get($order_number);
+ if (!$order) {
+ return false;
+ }
+
+ $payment_method = (string) ($order['payment_method'] ?? 'balance');
+ if (!in_array($payment_method, ['wechatpay', 'alipay'], true)) {
+ return true;
+ }
+
+ $refund_amount = (float) ($record['amount'] ?? 0);
+ if ($refund_amount <= 0) {
+ return false;
+ }
+
+ $request_id = sprintf(
+ 'AS_%d_%s',
+ (int) ($record['id'] ?? 0),
+ substr(hash('sha256', $order_number), 0, 16)
+ );
+ return $this->refund_via_gateway($payment_method, $order_number, $refund_amount, $request_id);
+ }
+
+ private function refund_via_gateway(string $payment_method, string $order_number, float $refund_amount, string $request_id): bool {
+ $gateway = Gateway::get_instance()->get_gateway($payment_method);
+ if (!$gateway || !method_exists($gateway, 'refund')) {
+ $this->log_refund_error($order_number, $payment_method, 'gateway unavailable');
+ return false;
+ }
+
+ try {
+ $result = $gateway->refund($order_number, $refund_amount, $request_id);
+ } catch (\Throwable $e) {
+ $this->log_refund_error($order_number, $payment_method, $e->getMessage());
+ return false;
+ }
+
+ if (empty($result['success'])) {
+ $this->log_refund_error($order_number, $payment_method, (string) ($result['message'] ?? 'unknown error'));
+ return false;
+ }
+
+ return true;
+ }
+
+ private function log_refund_error(string $order_number, string $payment_method, string $message): void {
+ error_log(sprintf(
+ '[AKSRT AfterSale] gateway refund failed order=%s gateway=%s: %s',
+ $order_number,
+ $payment_method,
+ $message
+ ));
+ }
+
private function sync_order_status(array $record, string $new_status, string $note = ''): void {
if (!class_exists('AKSRT\\Payment\\OrderRepository') || !class_exists('AKSRT\\Payment\\Order')) {
return;
@@ -807,13 +879,20 @@ class AfterSale {
return true;
}
- $wpdb->update($this->table(), [
+ if (!$this->complete_refund($record)) {
+ return false;
+ }
+
+ $updated = (bool) $wpdb->update($this->table(), [
'status' => self::STATUS_COMPLETED,
'operator_id' => $operator_id,
'operator_note' => sanitize_textarea_field($note),
'updated_at' => current_time('mysql'),
'resolved_at' => current_time('mysql'),
], ['id' => $ticket_id]);
+ if (!$updated) {
+ return false;
+ }
update_post_meta($ticket_id, '_aksrt_as_progress', self::PROGRESS_DONE);
$this->sync_order_status($record, self::STATUS_COMPLETED, $note ?: __('售后已完成', 'aksrt'));
diff --git a/src/AKSRT/Payment/Gateway.php b/src/AKSRT/Payment/Gateway.php
index f956e7a6..fa1ca02e 100644
--- a/src/AKSRT/Payment/Gateway.php
+++ b/src/AKSRT/Payment/Gateway.php
@@ -114,27 +114,29 @@ class Gateway {
return new \WP_REST_Response('gateway not found', 404);
}
- $verify = $gateway->verify_callback($request);
- $verified = !empty($verify['success']);
- $paid = $verified && ($verify['status'] ?? '') === 'paid';
+ $verify = $gateway->verify_callback($request);
+ $verified = !empty($verify['success']);
+ $paid = $verified && ($verify['status'] ?? '') === 'paid';
+ $processed = $verified;
if ($paid) {
- $this->complete_order($gateway_id, $verify);
+ // Only ACK paid notifications after the order was actually synchronized.
+ $processed = $this->complete_order($gateway_id, $verify);
}
// 网关各自要求的 ACK 格式
if ($gateway_id === 'wechatpay' && method_exists($gateway, 'output_callback_response')) {
- $gateway->output_callback_response(['success' => $verified]); // exit
+ $gateway->output_callback_response(['success' => $processed]); // exit
}
if ($gateway_id === 'alipay') {
// 支付宝异步通知要求返回纯文本 success,否则会重复通知
header('Content-Type: text/plain; charset=utf-8');
- echo $verified ? 'success' : 'fail';
+ echo $processed ? 'success' : 'fail';
exit;
}
- // Stripe / PayPal webhook:200 表示已接收;验签失败返回 400 触发重试
- return new \WP_REST_Response(['received' => $verified], $verified ? 200 : 400);
+ // Stripe / PayPal webhook:200 表示已接收;处理失败返回 400 触发重试
+ return new \WP_REST_Response(['received' => $processed], $processed ? 200 : 400);
}
/**
@@ -156,6 +158,20 @@ class Gateway {
$order_number = $order_number ?: sanitize_text_field((string) ($verify['order_number'] ?? ''));
if (($verify['status'] ?? '') === 'paid' && $this->complete_order($gateway_id, $verify)) {
$status = 'success';
+ } elseif ($order_number !== '') {
+ // Alipay synchronous returns generally omit trade_status; query after signature verification.
+ $query = $gateway->query_status($order_number);
+ if (!empty($query['success']) && ($query['status'] ?? '') === 'paid') {
+ $done = $this->complete_order($gateway_id, [
+ 'order_number' => $order_number,
+ 'amount' => 0,
+ 'transaction_id' => $query['transaction_id'] ?? '',
+ 'status' => 'paid',
+ ]);
+ if ($done) {
+ $status = 'success';
+ }
+ }
}
}
} elseif ($gateway_id === 'paypal' && method_exists($gateway, 'capture_order')) {
diff --git a/src/AKSRT/Payment/Gateways/AlipayGateway.php b/src/AKSRT/Payment/Gateways/AlipayGateway.php
index daee0bb7..47ac87cf 100644
--- a/src/AKSRT/Payment/Gateways/AlipayGateway.php
+++ b/src/AKSRT/Payment/Gateways/AlipayGateway.php
@@ -1,5 +1,7 @@
get_config();
- return !empty($config['app_id']) && !empty($config['private_key']) && !empty($config['alipay_public_key']);
- }
+ 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')];
- }
+ 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
+ $order_number = (string) ($order['order_number'] ?? '');
+ $amount = (float) ($order['amount'] ?? 0);
+ $subject = (string) ($order['subject'] ?? __('订单支付', 'aksrt'));
+ $payment_type = $this->get_config()['payment_type'];
- try {
- $sdk = $this->create_sdk($config);
+ if ($order_number === '' || $amount <= 0) {
+ return ['success' => false, 'message' => __('订单参数无效', 'aksrt')];
+ }
- 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;
- }
+ try {
+ YansongdaPayFactory::boot_alipay();
- // 构建支付跳转 URL(prepareExecute 已将查询参数追加到 $url)
- $sdk->prepareExecute($request, $url, $data);
- $payment_url = $url;
+ $payload = [
+ 'out_trade_no' => $order_number,
+ 'total_amount' => number_format($amount, 2, '.', ''),
+ 'subject' => mb_substr($subject, 0, 256),
+ ];
- return [
- 'success' => true,
- 'payment_url' => $payment_url,
- 'payment_data' => ['order_number' => $order_number],
- 'message' => __('支付宝支付链接已生成', 'aksrt'),
- ];
- } catch (\Exception $e) {
- return ['success' => false, 'message' => $e->getMessage()];
- }
- }
+ $result = $payment_type === 'wap'
+ ? Pay::alipay()->wap($payload)
+ : Pay::alipay()->web($payload);
- public function verify_callback(\WP_REST_Request $request): array {
- $config = $this->get_config();
+ $payment_url = YansongdaPayFactory::extract_payment_url($result);
+ $html = YansongdaPayFactory::extract_html_body($result);
- try {
- $sdk = $this->create_sdk($config);
- $params = $request->get_params();
+ // Prefer Location / action URL; if only auto-submit HTML, expose as payment_data for frontend.
+ if ($payment_url === '' && $html !== '' && preg_match('#action=[\'"]([^\'"]+)[\'"]#i', $html, $m)) {
+ $payment_url = html_entity_decode($m[1], ENT_QUOTES);
+ }
- 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',
- ];
- }
+ if ($payment_url === '' && $html === '') {
+ return ['success' => false, 'message' => __('支付宝支付链接生成失败', 'aksrt')];
+ }
- return ['success' => false, 'message' => __('支付宝回调验证失败', 'aksrt')];
- } catch (\Exception $e) {
- return ['success' => false, 'message' => $e->getMessage()];
- }
- }
+ return [
+ 'success' => true,
+ 'payment_url' => $payment_url,
+ 'payment_data' => array_filter([
+ 'order_number' => $order_number,
+ 'html' => $html !== '' ? $html : null,
+ ]),
+ 'message' => __('支付宝支付链接已生成', 'aksrt'),
+ ];
+ } catch (\Throwable $e) {
+ return ['success' => false, 'message' => $e->getMessage()];
+ }
+ }
- public function query_status(string $order_number): array {
- if (!$this->is_configured()) {
- return ['success' => false, 'message' => __('支付宝未配置', 'aksrt')];
- }
+ public function verify_callback(\WP_REST_Request $request): array {
+ try {
+ YansongdaPayFactory::boot_alipay();
- $config = $this->get_config();
+ // 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']);
- try {
- $sdk = $this->create_sdk($config);
+ $data_obj = Pay::alipay()->callback($callback_input);
+ $data = YansongdaPayFactory::to_array($data_obj);
- $request = new \Yurun\PaySDK\AlipayApp\Params\Query\Request;
- $request->businessParams->out_trade_no = $order_number;
+ $trade_status = (string) ($data['trade_status'] ?? '');
+ $paid = in_array($trade_status, ['TRADE_SUCCESS', 'TRADE_FINISHED'], true);
- $result = $sdk->execute($request);
+ return [
+ 'success' => true,
+ 'order_number' => (string) ($data['out_trade_no'] ?? ''),
+ 'amount' => (float) ($data['total_amount'] ?? 0),
+ 'transaction_id' => (string) ($data['trade_no'] ?? ''),
+ 'status' => $paid ? 'paid' : 'pending',
+ ];
+ } catch (\Throwable $e) {
+ return ['success' => false, 'message' => $e->getMessage()];
+ }
+ }
- 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';
- }
+ /**
+ * 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;
+ }
- return [
- 'success' => true,
- 'status' => $status,
- 'transaction_id' => $result['trade_no'] ?? '',
- ];
- }
+ public function query_status(string $order_number): array {
+ if (!$this->is_configured()) {
+ return ['success' => false, 'message' => __('支付宝未配置', 'aksrt')];
+ }
- return ['success' => false, 'message' => $sdk->getError() ?: __('查询失败', 'aksrt')];
- } catch (\Exception $e) {
- return ['success' => false, 'message' => $e->getMessage()];
- }
- }
+ try {
+ YansongdaPayFactory::boot_alipay();
+ $result = Pay::alipay()->query([
+ 'out_trade_no' => $order_number,
+ ]);
+ $data = YansongdaPayFactory::to_array($result);
- public function refund(string $order_number, float $amount): array {
- if (!$this->is_configured()) {
- return ['success' => false, 'message' => __('支付宝未配置', 'aksrt')];
- }
+ $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';
+ }
- $config = $this->get_config();
+ return [
+ 'success' => true,
+ 'status' => $status,
+ 'transaction_id' => (string) ($data['trade_no'] ?? ''),
+ ];
+ } catch (\Throwable $e) {
+ return ['success' => false, 'message' => $e->getMessage()];
+ }
+ }
- try {
- $sdk = $this->create_sdk($config);
+ public function refund(string $order_number, float $amount, string $request_id = ''): array {
+ if (!$this->is_configured()) {
+ return ['success' => false, 'message' => __('支付宝未配置', 'aksrt')];
+ }
- $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();
+ try {
+ YansongdaPayFactory::boot_alipay();
+ $refund_request_id = sanitize_key($request_id !== '' ? $request_id : $order_number . '_refund');
+ $result = Pay::alipay()->refund([
+ 'out_trade_no' => $order_number,
+ 'refund_amount' => number_format($amount, 2, '.', ''),
+ 'refund_reason' => __('用户申请退款', 'aksrt'),
+ 'out_request_no' => $refund_request_id,
+ ]);
+ $data = YansongdaPayFactory::to_array($result);
- $result = $sdk->execute($request);
+ return [
+ 'success' => true,
+ 'refund_id' => (string) ($data['trade_no'] ?? $data['out_trade_no'] ?? ''),
+ 'message' => __('退款成功', 'aksrt'),
+ ];
+ } catch (\Throwable $e) {
+ return ['success' => false, 'message' => $e->getMessage()];
+ }
+ }
- 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\Settings')) {
- return [];
- }
- $all = \AKSRT\Settings\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);
- }
+ /**
+ * @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,
+ ];
+ }
}
diff --git a/src/AKSRT/Payment/Gateways/WechatPayGateway.php b/src/AKSRT/Payment/Gateways/WechatPayGateway.php
index 1a1def08..ac414e5f 100644
--- a/src/AKSRT/Payment/Gateways/WechatPayGateway.php
+++ b/src/AKSRT/Payment/Gateways/WechatPayGateway.php
@@ -1,5 +1,7 @@
get_config();
- return !empty($config['mch_id']) && !empty($config['key']);
- }
+ public function is_configured(): bool {
+ if (class_exists('\AKSRT\Settings\PaymentRuntime')
+ && !\AKSRT\Settings\PaymentRuntime::is_gateway_enabled('wechatpay')) {
+ return false;
+ }
+ $c = $this->get_config();
+ return $c['mch_id'] !== ''
+ && $c['appid'] !== ''
+ && $c['api_v3_key'] !== ''
+ && $c['private_key'] !== ''
+ && $c['cert_path'] !== '';
+ }
- public function create_payment(array $order): array {
- if (!$this->is_configured()) {
- return ['success' => false, 'message' => __('微信支付未配置', 'aksrt')];
- }
+ 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
+ $order_number = (string) ($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 {
- $sdk = $this->create_sdk($config);
- $total_fee = (int) ($amount * 100); // 转为分
+ if ($order_number === '' || $total_fen <= 0) {
+ return ['success' => false, 'message' => __('订单参数无效', 'aksrt')];
+ }
- 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;
- }
+ try {
+ YansongdaPayFactory::boot_wechat();
- $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';
+ $payload = [
+ 'out_trade_no' => $order_number,
+ 'description' => mb_substr($subject, 0, 127),
+ 'amount' => [
+ 'total' => $total_fen,
+ 'currency' => 'CNY',
+ ],
+ ];
- $result = $sdk->execute($request);
+ switch ($payment_type) {
+ case 'h5':
+ $payload['scene_info'] = [
+ 'payer_client_ip' => (string) ($_SERVER['REMOTE_ADDR'] ?? '127.0.0.1'),
+ 'h5_info' => ['type' => 'Wap'],
+ ];
+ $result = Pay::wechat()->h5($payload);
+ break;
+ case 'native':
+ default:
+ $result = Pay::wechat()->scan($payload);
+ break;
+ }
- if ($sdk->checkResult()) {
- $result_data = [
- 'success' => true,
- 'payment_data' => array_merge($result, ['order_number' => $order_number]),
- 'message' => __('微信支付订单已创建', 'aksrt'),
- ];
+ $data = YansongdaPayFactory::to_array($result);
+ $payment_data = array_merge($data, ['order_number' => $order_number]);
- 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'];
- }
+ $result_data = [
+ 'success' => true,
+ 'payment_data' => $payment_data,
+ 'message' => __('微信支付订单已创建', 'aksrt'),
+ ];
- return $result_data;
- }
+ if ($payment_type === 'native' && !empty($data['code_url'])) {
+ $result_data['payment_url'] = (string) $data['code_url'];
+ } elseif ($payment_type === 'h5' && !empty($data['h5_url'])) {
+ $result_data['payment_url'] = (string) $data['h5_url'];
+ }
- return ['success' => false, 'message' => $sdk->getError() ?: __('微信支付创建失败', 'aksrt')];
- } catch (\Exception $e) {
- return ['success' => false, 'message' => $e->getMessage()];
- }
- }
+ return $result_data;
+ } catch (\Throwable $e) {
+ return ['success' => false, 'message' => $e->getMessage()];
+ }
+ }
- public function verify_callback(\WP_REST_Request $request): array {
- $config = $this->get_config();
+ public function verify_callback(\WP_REST_Request $request): array {
+ try {
+ YansongdaPayFactory::boot_wechat();
- try {
- $sdk = $this->create_sdk($config);
+ // Pass raw body + headers so callback works under WP REST (php://input may be drained).
+ $raw = $request->get_body();
+ $headers = [
+ 'Content-Type' => (string) ($request->get_header('content-type') ?: 'application/json'),
+ 'Wechatpay-Serial' => (string) $request->get_header('wechatpay-serial'),
+ 'Wechatpay-Timestamp' => (string) $request->get_header('wechatpay-timestamp'),
+ 'Wechatpay-Nonce' => (string) $request->get_header('wechatpay-nonce'),
+ 'Wechatpay-Signature' => (string) $request->get_header('wechatpay-signature'),
+ ];
+ $callback = Pay::wechat()->callback([
+ 'body' => $raw !== '' ? $raw : file_get_contents('php://input'),
+ 'headers' => $headers,
+ ]);
- // 读取原始请求体
- $xml = file_get_contents('php://input');
- $data = \Yurun\PaySDK\Lib\XML::fromString($xml);
+ $data = YansongdaPayFactory::to_array($callback);
+ // Decrypted resource is nested under resource (array) after CallbackPlugin.
+ $resource = [];
+ if (isset($data['resource']) && is_array($data['resource'])) {
+ $resource = $data['resource'];
+ // ciphertext decrypt may leave plaintext fields at resource root or under ciphertext key.
+ if (isset($resource['ciphertext']) && is_array($resource['ciphertext'])) {
+ $resource = $resource['ciphertext'];
+ }
+ }
- 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',
- ];
- }
+ $order_number = (string) ($resource['out_trade_no'] ?? $data['out_trade_no'] ?? '');
+ $transaction_id = (string) ($resource['transaction_id'] ?? $data['transaction_id'] ?? '');
+ $trade_state = (string) ($resource['trade_state'] ?? $data['trade_state'] ?? '');
+ $event_type = (string) ($data['event_type'] ?? '');
- return ['success' => false, 'message' => __('微信支付回调签名验证失败', 'aksrt')];
- } catch (\Exception $e) {
- return ['success' => false, 'message' => $e->getMessage()];
- }
- }
+ $total_fen = (int) YansongdaPayFactory::dig($resource, 'amount.total', 0);
+ if ($total_fen <= 0) {
+ $total_fen = (int) YansongdaPayFactory::dig($data, 'amount.total', 0);
+ }
- /**
- * 输出微信支付回调响应
- */
- public function output_callback_response(array $result): void {
- if ($result['success'] ?? false) {
- echo '';
- } else {
- echo '';
- }
- exit;
- }
+ $paid = ($trade_state === 'SUCCESS') || ($event_type === 'TRANSACTION.SUCCESS');
- public function query_status(string $order_number): array {
- if (!$this->is_configured()) {
- return ['success' => false, 'message' => __('微信支付未配置', 'aksrt')];
- }
+ // When event is pay success, resource has trade_state SUCCESS.
+ if ($event_type !== '' && $event_type !== 'TRANSACTION.SUCCESS' && $trade_state !== 'SUCCESS') {
+ $paid = false;
+ }
- $config = $this->get_config();
+ return [
+ 'success' => true,
+ 'order_number' => $order_number,
+ 'amount' => $total_fen > 0 ? ($total_fen / 100) : 0.0,
+ 'transaction_id' => $transaction_id,
+ 'status' => $paid ? 'paid' : 'pending',
+ ];
+ } catch (\Throwable $e) {
+ return ['success' => false, 'message' => $e->getMessage()];
+ }
+ }
- try {
- $sdk = $this->create_sdk($config);
+ /**
+ * Output WeChat v3 notify ACK (JSON).
+ */
+ public function output_callback_response(array $result): void {
+ try {
+ YansongdaPayFactory::boot_wechat();
+ if (!empty($result['success'])) {
+ YansongdaPayFactory::emit_response(Pay::wechat()->success(), '{"code":"SUCCESS","message":"成功"}');
+ }
+ } catch (\Throwable $e) {
+ // fall through to fail body
+ }
- $request = new \Yurun\PaySDK\Weixin\OrderQuery\Request;
- $request->out_trade_no = $order_number;
+ if (!headers_sent()) {
+ http_response_code(500);
+ header('Content-Type: application/json; charset=utf-8');
+ }
+ echo wp_json_encode([
+ 'code' => 'FAIL',
+ 'message' => (string) ($result['message'] ?? 'FAIL'),
+ ]);
+ exit;
+ }
- $result = $sdk->execute($request);
+ public function query_status(string $order_number): array {
+ if (!$this->is_configured()) {
+ return ['success' => false, 'message' => __('微信支付未配置', 'aksrt')];
+ }
- 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';
- }
+ try {
+ YansongdaPayFactory::boot_wechat();
+ $action = $this->query_action();
+ $result = Pay::wechat()->query([
+ 'out_trade_no' => $order_number,
+ '_action' => $action,
+ ]);
+ $data = YansongdaPayFactory::to_array($result);
- return [
- 'success' => true,
- 'status' => $status,
- 'transaction_id' => $result['transaction_id'] ?? '',
- ];
- }
+ $trade_state = (string) ($data['trade_state'] ?? '');
+ $status = 'pending';
+ if ($trade_state === 'SUCCESS') {
+ $status = 'paid';
+ } elseif (in_array($trade_state, ['CLOSED', 'REVOKED'], true)) {
+ $status = 'cancelled';
+ } elseif ($trade_state === 'REFUND') {
+ $status = 'refunded';
+ }
- return ['success' => false, 'message' => $sdk->getError() ?: __('查询失败', 'aksrt')];
- } catch (\Exception $e) {
- return ['success' => false, 'message' => $e->getMessage()];
- }
- }
+ return [
+ 'success' => true,
+ 'status' => $status,
+ 'transaction_id' => (string) ($data['transaction_id'] ?? ''),
+ ];
+ } catch (\Throwable $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')];
- }
+ public function refund(string $order_number, float $amount, string $request_id = ''): array {
+ if (!$this->is_configured()) {
+ return ['success' => false, 'message' => __('微信支付未配置', 'aksrt')];
+ }
- $config = $this->get_config();
+ try {
+ YansongdaPayFactory::boot_wechat();
+ $action = $this->query_action();
- try {
- $sdk = $this->create_sdk($config);
+ $query = Pay::wechat()->query([
+ 'out_trade_no' => $order_number,
+ '_action' => $action,
+ ]);
+ $q = YansongdaPayFactory::to_array($query);
+ $total_fen = (int) YansongdaPayFactory::dig($q, 'amount.total', 0);
+ $refund_fen = (int) round($amount * 100);
+ if ($total_fen <= 0) {
+ $total_fen = $refund_fen;
+ }
+ if ($refund_fen <= 0) {
+ return ['success' => false, 'message' => __('退款金额无效', 'aksrt')];
+ }
- // 先查询订单获取 transaction_id
- $query = new \Yurun\PaySDK\Weixin\OrderQuery\Request;
- $query->out_trade_no = $order_number;
- $query_result = $sdk->execute($query);
+ $refund_request_id = sanitize_key($request_id !== '' ? $request_id : 'REF_' . $order_number);
+ $result = Pay::wechat()->refund([
+ '_action' => 'refund_' . $action,
+ 'out_trade_no' => $order_number,
+ 'out_refund_no' => $refund_request_id,
+ 'reason' => __('用户申请退款', 'aksrt'),
+ 'amount' => [
+ 'refund' => $refund_fen,
+ 'total' => $total_fen,
+ 'currency' => 'CNY',
+ ],
+ ]);
+ $data = YansongdaPayFactory::to_array($result);
- if (!$sdk->checkResult() || empty($query_result['transaction_id'])) {
- return ['success' => false, 'message' => __('未找到支付记录', 'aksrt')];
- }
+ return [
+ 'success' => true,
+ 'refund_id' => (string) ($data['refund_id'] ?? $data['out_refund_no'] ?? ''),
+ 'message' => __('退款成功', 'aksrt'),
+ ];
+ } catch (\Throwable $e) {
+ return ['success' => false, 'message' => $e->getMessage()];
+ }
+ }
- $refund_fee = (int) ($amount * 100);
+ /**
+ * @return array{mch_id:string,appid:string,api_v3_key:string,private_key:string,cert_path: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['wechat_payment_type'] ?? 'native'));
+ if (!in_array($type, ['native', 'h5'], true)) {
+ $type = 'native';
+ }
+ return [
+ 'mch_id' => (string) ($all['wechat_merchantid'] ?? ''),
+ 'appid' => (string) ($all['wechat_appid'] ?? ''),
+ 'api_v3_key' => (string) ($all['wechat_api_v3_key'] ?? ''),
+ 'private_key' => (string) ($all['wechat_private_key_path'] ?? ''),
+ 'cert_path' => (string) ($all['wechat_cert_path'] ?? ''),
+ 'payment_type' => $type,
+ ];
+ }
- $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\Settings')) {
- return [];
- }
- $all = \AKSRT\Settings\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);
- }
+ private function query_action(): string {
+ $map = [
+ 'native' => 'native',
+ 'h5' => 'h5',
+ ];
+ $type = $this->get_config()['payment_type'];
+ return $map[$type] ?? 'native';
+ }
}
diff --git a/src/AKSRT/Payment/Gateways/YansongdaPayFactory.php b/src/AKSRT/Payment/Gateways/YansongdaPayFactory.php
new file mode 100644
index 00000000..2e03c46b
--- /dev/null
+++ b/src/AKSRT/Payment/Gateways/YansongdaPayFactory.php
@@ -0,0 +1,275 @@
+ $channels Keys: wechat?, alipay?
+ */
+ public static function boot(array $channels): void {
+ $config = [
+ 'logger' => self::logger_config(),
+ 'http' => [
+ 'timeout' => 10.0,
+ 'connect_timeout' => 5.0,
+ ],
+ ];
+
+ if (!empty($channels['wechat']) && is_array($channels['wechat'])) {
+ $config['wechat'] = [
+ 'default' => $channels['wechat'],
+ ];
+ }
+ if (!empty($channels['alipay']) && is_array($channels['alipay'])) {
+ $config['alipay'] = [
+ 'default' => $channels['alipay'],
+ ];
+ }
+
+ // Re-config is safe; Pay caches tenant config per process.
+ Pay::config($config);
+ }
+
+ /**
+ * WeChat Pay API v3 merchant config.
+ *
+ * @return array
+ */
+ public static function wechat_from_settings(): array {
+ $all = self::settings();
+ $notify = home_url('/wp-json/aksrt/v1/payment/wechatpay/notify');
+
+ return [
+ 'mch_id' => (string) ($all['wechat_merchantid'] ?? ''),
+ 'mch_secret_key' => (string) ($all['wechat_api_v3_key'] ?? ''),
+ 'mch_secret_cert' => (string) ($all['wechat_private_key_path'] ?? ''),
+ 'mch_public_cert_path' => (string) ($all['wechat_cert_path'] ?? ''),
+ 'mp_app_id' => (string) ($all['wechat_appid'] ?? ''),
+ 'notify_url' => $notify,
+ 'mode' => Pay::MODE_NORMAL,
+ ];
+ }
+
+ /**
+ * Alipay cert-mode config (yansongda/pay 3.x requires cert paths for SN + verify).
+ *
+ * app_secret_cert may be PEM string or file path.
+ * Cert path fields accept absolute file paths (or PEM content via get_public_cert).
+ *
+ * @return array
+ */
+ public static function alipay_from_settings(): array {
+ $all = self::settings();
+ $notify = home_url('/wp-json/aksrt/v1/payment/alipay/notify');
+ $return = home_url('/wp-json/aksrt/v1/payment/alipay/return');
+
+ return [
+ 'app_id' => (string) ($all['alipay_appid'] ?? ''),
+ 'app_secret_cert' => (string) ($all['alipay_privatekey'] ?? ''),
+ 'app_public_cert_path' => (string) ($all['alipay_app_public_cert_path'] ?? ''),
+ 'alipay_public_cert_path' => (string) ($all['alipay_public_cert_path'] ?? ''),
+ 'alipay_root_cert_path' => (string) ($all['alipay_root_cert_path'] ?? ''),
+ 'return_url' => $return,
+ 'notify_url' => $notify,
+ 'mode' => Pay::MODE_NORMAL,
+ ];
+ }
+
+ public static function boot_wechat(): void {
+ self::boot(['wechat' => self::wechat_from_settings()]);
+ }
+
+ public static function boot_alipay(): void {
+ self::boot(['alipay' => self::alipay_from_settings()]);
+ }
+
+ /**
+ * Convert Collection / ArrayAccess / array to plain array.
+ *
+ * @param mixed $data
+ * @return array
+ */
+ public static function to_array($data): array {
+ if (is_array($data)) {
+ return $data;
+ }
+ if ($data instanceof \Yansongda\Supports\Collection) {
+ return $data->all();
+ }
+ if ($data instanceof \ArrayAccess) {
+ $out = [];
+ foreach ($data as $k => $v) {
+ $out[$k] = is_array($v) || $v instanceof \ArrayAccess || $v instanceof \Yansongda\Supports\Collection
+ ? self::to_array($v)
+ : $v;
+ }
+ return $out;
+ }
+ if (is_object($data) && method_exists($data, 'toArray')) {
+ $arr = $data->toArray();
+ return is_array($arr) ? $arr : [];
+ }
+ return [];
+ }
+
+ /**
+ * Nested value from array using dot path.
+ *
+ * @param array $data
+ * @return mixed
+ */
+ public static function dig(array $data, string $path, $default = null) {
+ $cur = $data;
+ foreach (explode('.', $path) as $seg) {
+ if (!is_array($cur) || !array_key_exists($seg, $cur)) {
+ return $default;
+ }
+ $cur = $cur[$seg];
+ }
+ return $cur;
+ }
+
+ /**
+ * Extract a redirect / payment URL from alipay web/wap response.
+ *
+ * @param mixed $result
+ */
+ public static function extract_payment_url($result): string {
+ if (is_string($result) && $result !== '') {
+ return $result;
+ }
+ if ($result instanceof \Psr\Http\Message\ResponseInterface) {
+ $loc = $result->getHeaderLine('Location');
+ if ($loc !== '') {
+ return $loc;
+ }
+ $body = (string) $result->getBody();
+ if (preg_match('#action=[\'"]([^\'"]+)[\'"]#i', $body, $m)) {
+ return html_entity_decode($m[1], ENT_QUOTES);
+ }
+ if (preg_match('#https?://[^\s"\'<>]+#', $body, $m)) {
+ return $m[0];
+ }
+ // Auto-submit HTML form: callers may open as document; store empty payment_url and put HTML in payment_data.
+ return '';
+ }
+ if (is_object($result)) {
+ if (method_exists($result, 'getTargetUrl')) {
+ $url = (string) $result->getTargetUrl();
+ if ($url !== '') {
+ return $url;
+ }
+ }
+ if (method_exists($result, '__toString')) {
+ $s = (string) $result;
+ if (strpos($s, 'http') === 0) {
+ return $s;
+ }
+ }
+ }
+ $arr = self::to_array($result);
+ foreach (['payment_url', 'h5_url', 'code_url', 'mweb_url'] as $k) {
+ if (!empty($arr[$k]) && is_string($arr[$k])) {
+ return $arr[$k];
+ }
+ }
+ return '';
+ }
+
+ /**
+ * HTML body from alipay Response (form auto-submit) when no Location header.
+ *
+ * @param mixed $result
+ */
+ public static function extract_html_body($result): string {
+ if ($result instanceof \Psr\Http\Message\ResponseInterface) {
+ if ($result->getHeaderLine('Location') !== '') {
+ return '';
+ }
+ return (string) $result->getBody();
+ }
+ return '';
+ }
+
+ /**
+ * Emit SDK success()/fail() ResponseInterface (or string) and exit.
+ *
+ * @param mixed $response
+ */
+ public static function emit_response($response, string $fallback_body = 'success'): void {
+ if ($response instanceof \Psr\Http\Message\ResponseInterface) {
+ $status = $response->getStatusCode();
+ if ($status > 0 && !headers_sent()) {
+ http_response_code($status);
+ }
+ foreach ($response->getHeaders() as $name => $values) {
+ if (strtolower((string) $name) === 'transfer-encoding') {
+ continue;
+ }
+ foreach ($values as $value) {
+ header($name . ': ' . $value, false);
+ }
+ }
+ $body = (string) $response->getBody();
+ echo $body !== '' ? $body : $fallback_body;
+ exit;
+ }
+ if (is_string($response) && $response !== '') {
+ echo $response;
+ exit;
+ }
+ echo $fallback_body;
+ exit;
+ }
+
+ /**
+ * @return array
+ */
+ private static function settings(): array {
+ if (class_exists('\AKSRT\Settings\Settings')) {
+ return \AKSRT\Settings\Settings::get_all();
+ }
+ return [];
+ }
+
+ /**
+ * @return array
+ */
+ private static function logger_config(): array {
+ $enabled = defined('WP_DEBUG') && WP_DEBUG;
+ $file = '';
+ if ($enabled && function_exists('wp_upload_dir')) {
+ $uploads = wp_upload_dir();
+ if (empty($uploads['error']) && !empty($uploads['basedir'])) {
+ $dir = trailingslashit((string) $uploads['basedir']) . 'aksrt-pay';
+ if (!is_dir($dir)) {
+ wp_mkdir_p($dir);
+ }
+ $file = trailingslashit($dir) . 'pay.log';
+ }
+ }
+
+ return [
+ 'enable' => $enabled && $file !== '',
+ 'file' => $file,
+ 'level' => 'info',
+ 'type' => 'single',
+ 'max_file' => 30,
+ ];
+ }
+}
diff --git a/src/AKSRT/Settings/PaymentRuntime.php b/src/AKSRT/Settings/PaymentRuntime.php
index 85f2a32d..39ee4af0 100644
--- a/src/AKSRT/Settings/PaymentRuntime.php
+++ b/src/AKSRT/Settings/PaymentRuntime.php
@@ -336,10 +336,6 @@ class PaymentRuntime {
'pid' => sanitize_text_field((string) ($settings['epay_pid'] ?? '')),
'key' => sanitize_text_field((string) ($settings['epay_key'] ?? '')),
],
- 'alipay_extra' => [
- 'return_url' => esc_url_raw((string) ($settings['alipay_return_url'] ?? '')),
- 'notify_url' => esc_url_raw((string) ($settings['alipay_notify_url'] ?? '')),
- ],
];
}