refactor(payment): migrate SDK to yansongda pay

This commit is contained in:
2026-07-26 00:09:38 +08:00
parent 8fbde0785e
commit a8d99b200b
10 changed files with 1555 additions and 405 deletions
@@ -22,15 +22,17 @@ export const gatewaySchema = {
payment_codepay_enabled: false, payment_codepay_enabled: false,
payment_stripe_enabled: false, payment_stripe_enabled: false,
wechat_merchantid: '', wechat_merchantid: '',
wechat_jsapi: '', wechat_appid: '',
wechat_appsecret: '', wechat_payment_type: 'native',
wechat_api_v3_key: '',
wechat_private_key_path: '',
wechat_cert_path: '', wechat_cert_path: '',
wechat_key_path: '',
alipay_appid: '', alipay_appid: '',
alipay_privatekey: '', alipay_privatekey: '',
alipay_publickey: '', alipay_payment_type: 'page',
alipay_return_url: '', alipay_app_public_cert_path: '',
alipay_notify_url: '', alipay_public_cert_path: '',
alipay_root_cert_path: '',
paypal_client_id: '', paypal_client_id: '',
paypal_client_secret: '', paypal_client_secret: '',
paypal_currency: 'USD', paypal_currency: 'USD',
@@ -94,10 +96,14 @@ export const gatewaySchema = {
dependsOn: { key: 'payment_wechat_enabled', value: true }, dependsOn: { key: 'payment_wechat_enabled', value: true },
fields: [ fields: [
{ key: 'wechat_merchantid', label: '商户号', type: 'input', default: '' }, { key: 'wechat_merchantid', label: '商户号', type: 'input', default: '' },
{ key: 'wechat_jsapi', label: 'JSAPI密钥', type: 'input', default: '' }, { key: 'wechat_appid', label: 'AppID', type: 'input', default: '' },
{ key: 'wechat_appsecret', label: 'AppSecret', type: 'input', default: '' }, { key: 'wechat_payment_type', label: '支付方式', type: 'select', default: 'native', options: [
{ key: 'wechat_cert_path', label: '证书路径', type: 'input', default: '', help: '退款等接口需要,绝对路径。' }, { label: '扫码支付(Native)', value: 'native' },
{ key: 'wechat_key_path', label: '密钥路径', type: 'input', default: '' }, { 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: [ fields: [
{ key: 'alipay_appid', label: 'AppID', type: 'input', default: '' }, { key: 'alipay_appid', label: 'AppID', type: 'input', default: '' },
{ key: 'alipay_privatekey', label: '应用私钥', type: 'textarea', rows: 3, default: '' }, { key: 'alipay_privatekey', label: '应用私钥', type: 'textarea', rows: 3, default: '' },
{ key: 'alipay_publickey', label: '支付宝公钥', type: 'textarea', rows: 3, default: '' }, { key: 'alipay_payment_type', label: '支付方式', type: 'select', default: 'page', options: [
{ key: 'alipay_return_url', label: '同步回调地址', type: 'input', default: '' }, { label: '电脑网站支付(web)', value: 'page' },
{ key: 'alipay_notify_url', label: '异步回调地址', type: 'input', default: '' }, { 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},无需在此填写。' },
], ],
}, },
{ {
+1 -1
View File
@@ -5,7 +5,7 @@
"require": { "require": {
"php": ">=7.4", "php": ">=7.4",
"stripe/stripe-php": "^16.1", "stripe/stripe-php": "^16.1",
"yurunsoft/pay-sdk": "~3.0", "yansongda/pay": "^3.7",
"yurunsoft/yurun-oauth-login": "~3.0" "yurunsoft/yurun-oauth-login": "~3.0"
}, },
"config": { "config": {
Generated
+713 -18
View File
@@ -4,8 +4,337 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "8f8c952e2744521268680c9c6f7ad76a", "content-hash": "fe1258c91a142e623f464f5ddaef58f9",
"packages": [ "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", "name": "psr/http-message",
"version": "2.0", "version": "2.0",
@@ -109,6 +438,50 @@
}, },
"time": "2024-09-11T13:17:53+00:00" "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", "name": "stripe/stripe-php",
"version": "v16.6.0", "version": "v16.6.0",
@@ -169,45 +542,367 @@
"time": "2025-02-24T22:35:29+00:00" "time": "2025-02-24T22:35:29+00:00"
}, },
{ {
"name": "yurunsoft/pay-sdk", "name": "symfony/deprecation-contracts",
"version": "v3.1.7", "version": "v3.7.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Yurunsoft/PaySDK.git", "url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "e146a056f3aa1b68f64bbc5e261bf137b7723307" "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Yurunsoft/PaySDK/zipball/e146a056f3aa1b68f64bbc5e261bf137b7723307", "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d",
"reference": "e146a056f3aa1b68f64bbc5e261bf137b7723307", "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.5", "php": ">=8.1"
"yurunsoft/yurun-http": "~4.0|~5.0" },
"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": { "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", "type": "library",
"autoload": { "autoload": {
"files": [
"src/Functions.php"
],
"psr-4": { "psr-4": {
"Yurun\\PaySDK\\": "src/" "Yansongda\\Artful\\": "src"
} }
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
"license": [ "license": [
"MIT" "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": { "support": {
"issues": "https://github.com/Yurunsoft/PaySDK/issues", "homepage": "https://artful.yansongda.cn",
"source": "https://github.com/Yurunsoft/PaySDK/tree/v3.1.7" "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", "name": "yurunsoft/yurun-http",
+16 -7
View File
@@ -247,17 +247,26 @@ class SchemaPayment {
// Field-level dependsOn kept for contract audit / non-SPA consumers. // 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', '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_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_appid', 'label' => 'AppID', '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_payment_type', 'label' => '支付方式', 'type' => 'select', 'default' => 'native', 'options' => [
['key' => 'wechat_cert_path', 'label' => '证书路径', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_wechat_enabled', 'value' => true]], ['value' => 'native', 'label' => '扫码支付(Native)'],
['key' => 'wechat_key_path', 'label' => '密钥路径', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_wechat_enabled', 'value' => true]], ['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', '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_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_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_payment_type', 'label' => '支付方式', 'type' => 'select', 'default' => 'page', 'options' => [
['key' => 'alipay_return_url', 'label' => '同步回调地址', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_alipay_enabled', 'value' => true]], ['value' => 'page', 'label' => '电脑网站支付(web)'],
['key' => 'alipay_notify_url', 'label' => '异步回调地址', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_alipay_enabled', 'value' => true]], ['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', '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]], ['key' => 'paypal_client_id', 'label' => 'Client ID', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'payment_paypal_enabled', 'value' => true]],
+80 -1
View File
@@ -328,6 +328,10 @@ class AfterSale {
$update['resolved_at'] = current_time('mysql'); $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]); $updated = (bool) $wpdb->update($this->table(), $update, ['id' => $id]);
if ($updated) { if ($updated) {
$note = $operator_note !== '' ? $operator_note : sprintf(__('售后状态更新为:%s', 'aksrt'), $this->get_status_label($new_status)); $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); 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 { private function sync_order_status(array $record, string $new_status, string $note = ''): void {
if (!class_exists('AKSRT\\Payment\\OrderRepository') || !class_exists('AKSRT\\Payment\\Order')) { if (!class_exists('AKSRT\\Payment\\OrderRepository') || !class_exists('AKSRT\\Payment\\Order')) {
return; return;
@@ -807,13 +879,20 @@ class AfterSale {
return true; return true;
} }
$wpdb->update($this->table(), [ if (!$this->complete_refund($record)) {
return false;
}
$updated = (bool) $wpdb->update($this->table(), [
'status' => self::STATUS_COMPLETED, 'status' => self::STATUS_COMPLETED,
'operator_id' => $operator_id, 'operator_id' => $operator_id,
'operator_note' => sanitize_textarea_field($note), 'operator_note' => sanitize_textarea_field($note),
'updated_at' => current_time('mysql'), 'updated_at' => current_time('mysql'),
'resolved_at' => current_time('mysql'), 'resolved_at' => current_time('mysql'),
], ['id' => $ticket_id]); ], ['id' => $ticket_id]);
if (!$updated) {
return false;
}
update_post_meta($ticket_id, '_aksrt_as_progress', self::PROGRESS_DONE); update_post_meta($ticket_id, '_aksrt_as_progress', self::PROGRESS_DONE);
$this->sync_order_status($record, self::STATUS_COMPLETED, $note ?: __('售后已完成', 'aksrt')); $this->sync_order_status($record, self::STATUS_COMPLETED, $note ?: __('售后已完成', 'aksrt'));
+24 -8
View File
@@ -114,27 +114,29 @@ class Gateway {
return new \WP_REST_Response('gateway not found', 404); return new \WP_REST_Response('gateway not found', 404);
} }
$verify = $gateway->verify_callback($request); $verify = $gateway->verify_callback($request);
$verified = !empty($verify['success']); $verified = !empty($verify['success']);
$paid = $verified && ($verify['status'] ?? '') === 'paid'; $paid = $verified && ($verify['status'] ?? '') === 'paid';
$processed = $verified;
if ($paid) { 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 格式 // 网关各自要求的 ACK 格式
if ($gateway_id === 'wechatpay' && method_exists($gateway, 'output_callback_response')) { 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') { if ($gateway_id === 'alipay') {
// 支付宝异步通知要求返回纯文本 success,否则会重复通知 // 支付宝异步通知要求返回纯文本 success,否则会重复通知
header('Content-Type: text/plain; charset=utf-8'); header('Content-Type: text/plain; charset=utf-8');
echo $verified ? 'success' : 'fail'; echo $processed ? 'success' : 'fail';
exit; exit;
} }
// Stripe / PayPal webhook:200 表示已接收;验签失败返回 400 触发重试 // Stripe / PayPal webhook:200 表示已接收;处理失败返回 400 触发重试
return new \WP_REST_Response(['received' => $verified], $verified ? 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'] ?? '')); $order_number = $order_number ?: sanitize_text_field((string) ($verify['order_number'] ?? ''));
if (($verify['status'] ?? '') === 'paid' && $this->complete_order($gateway_id, $verify)) { if (($verify['status'] ?? '') === 'paid' && $this->complete_order($gateway_id, $verify)) {
$status = 'success'; $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')) { } elseif ($gateway_id === 'paypal' && method_exists($gateway, 'capture_order')) {
+176 -156
View File
@@ -1,5 +1,7 @@
<?php <?php
/** /**
* Alipay gateway via yansongda/pay (cert mode).
*
* @package AKSRT * @package AKSRT
*/ */
@@ -7,188 +9,206 @@ namespace AKSRT\Payment\Gateways;
defined('ABSPATH') || exit; defined('ABSPATH') || exit;
use Yansongda\Pay\Pay;
class AlipayGateway implements \AKSRT\Payment\GatewayInterface { class AlipayGateway implements \AKSRT\Payment\GatewayInterface {
public function get_id(): string { public function get_id(): string {
return 'alipay'; return 'alipay';
} }
public function get_name(): string { public function get_name(): string {
return __('支付宝', 'aksrt'); return __('支付宝', 'aksrt');
} }
public function is_configured(): bool { public function is_configured(): bool {
if (class_exists('\AKSRT\Settings\PaymentRuntime') if (class_exists('\AKSRT\Settings\PaymentRuntime')
&& !\AKSRT\Settings\PaymentRuntime::is_gateway_enabled('alipay')) { && !\AKSRT\Settings\PaymentRuntime::is_gateway_enabled('alipay')) {
return false; return false;
} }
$config = $this->get_config(); $c = $this->get_config();
return !empty($config['app_id']) && !empty($config['private_key']) && !empty($config['alipay_public_key']); 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 { public function create_payment(array $order): array {
if (!$this->is_configured()) { if (!$this->is_configured()) {
return ['success' => false, 'message' => __('支付宝未配置', 'aksrt')]; return ['success' => false, 'message' => __('支付宝未配置', 'aksrt')];
} }
$config = $this->get_config(); $order_number = (string) ($order['order_number'] ?? '');
$order_number = $order['order_number'] ?? ''; $amount = (float) ($order['amount'] ?? 0);
$amount = (float) ($order['amount'] ?? 0); $subject = (string) ($order['subject'] ?? __('订单支付', 'aksrt'));
$subject = $order['subject'] ?? __('订单支付', 'aksrt'); $payment_type = $this->get_config()['payment_type'];
$payment_type = $config['payment_type'] ?? 'page'; // page / wap
try { if ($order_number === '' || $amount <= 0) {
$sdk = $this->create_sdk($config); return ['success' => false, 'message' => __('订单参数无效', 'aksrt')];
}
if ($payment_type === 'wap') { try {
// 手机网站支付 YansongdaPayFactory::boot_alipay();
$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) $payload = [
$sdk->prepareExecute($request, $url, $data); 'out_trade_no' => $order_number,
$payment_url = $url; 'total_amount' => number_format($amount, 2, '.', ''),
'subject' => mb_substr($subject, 0, 256),
];
return [ $result = $payment_type === 'wap'
'success' => true, ? Pay::alipay()->wap($payload)
'payment_url' => $payment_url, : Pay::alipay()->web($payload);
'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 { $payment_url = YansongdaPayFactory::extract_payment_url($result);
$config = $this->get_config(); $html = YansongdaPayFactory::extract_html_body($result);
try { // Prefer Location / action URL; if only auto-submit HTML, expose as payment_data for frontend.
$sdk = $this->create_sdk($config); if ($payment_url === '' && $html !== '' && preg_match('#action=[\'"]([^\'"]+)[\'"]#i', $html, $m)) {
$params = $request->get_params(); $payment_url = html_entity_decode($m[1], ENT_QUOTES);
}
if ($sdk->verifyCallback($params)) { if ($payment_url === '' && $html === '') {
$trade_status = $params['trade_status'] ?? ''; return ['success' => false, 'message' => __('支付宝支付链接生成失败', 'aksrt')];
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')]; return [
} catch (\Exception $e) { 'success' => true,
return ['success' => false, 'message' => $e->getMessage()]; '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 { public function verify_callback(\WP_REST_Request $request): array {
if (!$this->is_configured()) { try {
return ['success' => false, 'message' => __('支付宝未配置', 'aksrt')]; 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 { $data_obj = Pay::alipay()->callback($callback_input);
$sdk = $this->create_sdk($config); $data = YansongdaPayFactory::to_array($data_obj);
$request = new \Yurun\PaySDK\AlipayApp\Params\Query\Request; $trade_status = (string) ($data['trade_status'] ?? '');
$request->businessParams->out_trade_no = $order_number; $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'] ?? ''; * Alipay async notify expects plain text success/fail (Gateway also handles this).
$status = 'pending'; */
if (in_array($trade_status, ['TRADE_SUCCESS', 'TRADE_FINISHED'])) { public function output_callback_response(array $result): void {
$status = 'paid'; try {
} elseif ($trade_status === 'TRADE_CLOSED') { if (!empty($result['success'])) {
$status = 'cancelled'; 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 [ public function query_status(string $order_number): array {
'success' => true, if (!$this->is_configured()) {
'status' => $status, return ['success' => false, 'message' => __('支付宝未配置', 'aksrt')];
'transaction_id' => $result['trade_no'] ?? '', }
];
}
return ['success' => false, 'message' => $sdk->getError() ?: __('查询失败', 'aksrt')]; try {
} catch (\Exception $e) { YansongdaPayFactory::boot_alipay();
return ['success' => false, 'message' => $e->getMessage()]; $result = Pay::alipay()->query([
} 'out_trade_no' => $order_number,
} ]);
$data = YansongdaPayFactory::to_array($result);
public function refund(string $order_number, float $amount): array { $trade_status = (string) ($data['trade_status'] ?? '');
if (!$this->is_configured()) { $status = 'pending';
return ['success' => false, 'message' => __('支付宝未配置', 'aksrt')]; 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 { public function refund(string $order_number, float $amount, string $request_id = ''): array {
$sdk = $this->create_sdk($config); if (!$this->is_configured()) {
return ['success' => false, 'message' => __('支付宝未配置', 'aksrt')];
}
$request = new \Yurun\PaySDK\AlipayApp\Params\Refund\Request; try {
$request->businessParams->out_trade_no = $order_number; YansongdaPayFactory::boot_alipay();
$request->businessParams->refund_amount = number_format($amount, 2, '.', ''); $refund_request_id = sanitize_key($request_id !== '' ? $request_id : $order_number . '_refund');
$request->businessParams->refund_reason = __('用户申请退款', 'aksrt'); $result = Pay::alipay()->refund([
$request->businessParams->out_request_no = $order_number . '_refund_' . time(); '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 [ * @return array{app_id:string,private_key:string,app_public_cert:string,alipay_public_cert:string,alipay_root_cert:string,payment_type:string}
'success' => true, */
'refund_id' => $result['trade_no'] ?? '', private function get_config(): array {
'message' => __('退款成功', 'aksrt'), $all = class_exists('\AKSRT\Settings\Settings')
]; ? \AKSRT\Settings\Settings::get_all()
} : [];
$type = sanitize_key((string) ($all['alipay_payment_type'] ?? 'page'));
return ['success' => false, 'message' => $sdk->getError() ?: __('退款失败', 'aksrt')]; if (!in_array($type, ['page', 'wap'], true)) {
} catch (\Exception $e) { $type = 'page';
return ['success' => false, 'message' => $e->getMessage()]; }
} return [
} 'app_id' => (string) ($all['alipay_appid'] ?? ''),
'private_key' => (string) ($all['alipay_privatekey'] ?? ''),
private function get_config(): array { 'app_public_cert' => (string) ($all['alipay_app_public_cert_path'] ?? ''),
if (!class_exists('\AKSRT\Settings\Settings')) { 'alipay_public_cert' => (string) ($all['alipay_public_cert_path'] ?? ''),
return []; 'alipay_root_cert' => (string) ($all['alipay_root_cert_path'] ?? ''),
} 'payment_type' => $type,
$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);
}
} }
+246 -197
View File
@@ -1,5 +1,7 @@
<?php <?php
/** /**
* WeChat Pay gateway via yansongda/pay (API v3).
*
* @package AKSRT * @package AKSRT
*/ */
@@ -7,236 +9,283 @@ namespace AKSRT\Payment\Gateways;
defined('ABSPATH') || exit; defined('ABSPATH') || exit;
use Yansongda\Pay\Pay;
class WechatPayGateway implements \AKSRT\Payment\GatewayInterface { class WechatPayGateway implements \AKSRT\Payment\GatewayInterface {
public function get_id(): string { public function get_id(): string {
return 'wechatpay'; return 'wechatpay';
} }
public function get_name(): string { public function get_name(): string {
return __('微信支付', 'aksrt'); return __('微信支付', 'aksrt');
} }
public function is_configured(): bool { public function is_configured(): bool {
if (class_exists('\AKSRT\Settings\PaymentRuntime') if (class_exists('\AKSRT\Settings\PaymentRuntime')
&& !\AKSRT\Settings\PaymentRuntime::is_gateway_enabled('wechatpay')) { && !\AKSRT\Settings\PaymentRuntime::is_gateway_enabled('wechatpay')) {
return false; return false;
} }
$config = $this->get_config(); $c = $this->get_config();
return !empty($config['mch_id']) && !empty($config['key']); return $c['mch_id'] !== ''
} && $c['appid'] !== ''
&& $c['api_v3_key'] !== ''
&& $c['private_key'] !== ''
&& $c['cert_path'] !== '';
}
public function create_payment(array $order): array { public function create_payment(array $order): array {
if (!$this->is_configured()) { if (!$this->is_configured()) {
return ['success' => false, 'message' => __('微信支付未配置', 'aksrt')]; return ['success' => false, 'message' => __('微信支付未配置', 'aksrt')];
} }
$config = $this->get_config(); $order_number = (string) ($order['order_number'] ?? '');
$order_number = $order['order_number'] ?? ''; $amount = (float) ($order['amount'] ?? 0);
$amount = (float) ($order['amount'] ?? 0); $subject = (string) ($order['subject'] ?? __('订单支付', 'aksrt'));
$subject = $order['subject'] ?? __('订单支付', 'aksrt'); $payment_type = $this->get_config()['payment_type'];
$payment_type = $config['payment_type'] ?? 'native'; // native / jsapi / h5 $total_fen = (int) round($amount * 100);
try { if ($order_number === '' || $total_fen <= 0) {
$sdk = $this->create_sdk($config); return ['success' => false, 'message' => __('订单参数无效', 'aksrt')];
$total_fee = (int) ($amount * 100); // 转为分 }
switch ($payment_type) { try {
case 'jsapi': YansongdaPayFactory::boot_wechat();
$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; $payload = [
$request->out_trade_no = $order_number; 'out_trade_no' => $order_number,
$request->total_fee = $total_fee; 'description' => mb_substr($subject, 0, 127),
$request->fee_type = 'CNY'; 'amount' => [
$request->notify_url = home_url('/wp-json/aksrt/v1/payment/wechatpay/notify'); 'total' => $total_fen,
$request->spbill_create_ip = $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1'; '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()) { $data = YansongdaPayFactory::to_array($result);
$result_data = [ $payment_data = array_merge($data, ['order_number' => $order_number]);
'success' => true,
'payment_data' => array_merge($result, ['order_number' => $order_number]),
'message' => __('微信支付订单已创建', 'aksrt'),
];
if ($payment_type === 'native' && !empty($result['code_url'])) { $result_data = [
$result_data['payment_url'] = $result['code_url']; 'success' => true,
} elseif ($payment_type === 'h5' && !empty($result['h5_url'])) { 'payment_data' => $payment_data,
$result_data['payment_url'] = $result['h5_url']; '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')]; return $result_data;
} catch (\Exception $e) { } catch (\Throwable $e) {
return ['success' => false, 'message' => $e->getMessage()]; return ['success' => false, 'message' => $e->getMessage()];
} }
} }
public function verify_callback(\WP_REST_Request $request): array { public function verify_callback(\WP_REST_Request $request): array {
$config = $this->get_config(); try {
YansongdaPayFactory::boot_wechat();
try { // Pass raw body + headers so callback works under WP REST (php://input may be drained).
$sdk = $this->create_sdk($config); $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,
]);
// 读取原始请求体 $data = YansongdaPayFactory::to_array($callback);
$xml = file_get_contents('php://input'); // Decrypted resource is nested under resource (array) after CallbackPlugin.
$data = \Yurun\PaySDK\Lib\XML::fromString($xml); $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)) { $order_number = (string) ($resource['out_trade_no'] ?? $data['out_trade_no'] ?? '');
return [ $transaction_id = (string) ($resource['transaction_id'] ?? $data['transaction_id'] ?? '');
'success' => true, $trade_state = (string) ($resource['trade_state'] ?? $data['trade_state'] ?? '');
'order_number' => $data['out_trade_no'] ?? '', $event_type = (string) ($data['event_type'] ?? '');
'amount' => (($data['total_fee'] ?? 0) / 100),
'transaction_id' => $data['transaction_id'] ?? '',
'status' => ($data['result_code'] ?? '') === 'SUCCESS' ? 'paid' : 'pending',
];
}
return ['success' => false, 'message' => __('微信支付回调签名验证失败', 'aksrt')]; $total_fen = (int) YansongdaPayFactory::dig($resource, 'amount.total', 0);
} catch (\Exception $e) { if ($total_fen <= 0) {
return ['success' => false, 'message' => $e->getMessage()]; $total_fen = (int) YansongdaPayFactory::dig($data, 'amount.total', 0);
} }
}
/** $paid = ($trade_state === 'SUCCESS') || ($event_type === 'TRANSACTION.SUCCESS');
* 输出微信支付回调响应
*/
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 { // When event is pay success, resource has trade_state SUCCESS.
if (!$this->is_configured()) { if ($event_type !== '' && $event_type !== 'TRANSACTION.SUCCESS' && $trade_state !== 'SUCCESS') {
return ['success' => false, 'message' => __('微信支付未配置', 'aksrt')]; $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; if (!headers_sent()) {
$request->out_trade_no = $order_number; 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()) { try {
$trade_state = $result['trade_state'] ?? ''; YansongdaPayFactory::boot_wechat();
$status = 'pending'; $action = $this->query_action();
if ($trade_state === 'SUCCESS') { $result = Pay::wechat()->query([
$status = 'paid'; 'out_trade_no' => $order_number,
} elseif (in_array($trade_state, ['CLOSED', 'REVOKED'])) { '_action' => $action,
$status = 'cancelled'; ]);
} elseif ($trade_state === 'REFUND') { $data = YansongdaPayFactory::to_array($result);
$status = 'refunded';
}
return [ $trade_state = (string) ($data['trade_state'] ?? '');
'success' => true, $status = 'pending';
'status' => $status, if ($trade_state === 'SUCCESS') {
'transaction_id' => $result['transaction_id'] ?? '', $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')]; return [
} catch (\Exception $e) { 'success' => true,
return ['success' => false, 'message' => $e->getMessage()]; '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 { public function refund(string $order_number, float $amount, string $request_id = ''): array {
if (!$this->is_configured()) { if (!$this->is_configured()) {
return ['success' => false, 'message' => __('微信支付未配置', 'aksrt')]; return ['success' => false, 'message' => __('微信支付未配置', 'aksrt')];
} }
$config = $this->get_config(); try {
YansongdaPayFactory::boot_wechat();
$action = $this->query_action();
try { $query = Pay::wechat()->query([
$sdk = $this->create_sdk($config); '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 $refund_request_id = sanitize_key($request_id !== '' ? $request_id : 'REF_' . $order_number);
$query = new \Yurun\PaySDK\Weixin\OrderQuery\Request; $result = Pay::wechat()->refund([
$query->out_trade_no = $order_number; '_action' => 'refund_' . $action,
$query_result = $sdk->execute($query); '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 [
return ['success' => false, 'message' => __('未找到支付记录', 'aksrt')]; '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; private function query_action(): string {
$request->out_trade_no = $order_number; $map = [
$request->out_refund_no = 'REF_' . $order_number . '_' . time(); 'native' => 'native',
$request->total_fee = (int) ($query_result['total_fee'] ?? $refund_fee); 'h5' => 'h5',
$request->refund_fee = $refund_fee; ];
$request->refund_desc = __('用户申请退款', 'aksrt'); $type = $this->get_config()['payment_type'];
return $map[$type] ?? 'native';
$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);
}
} }
@@ -0,0 +1,275 @@
<?php
/**
* Shared yansongda/pay config bootstrap for WeChat / Alipay gateways.
*
* @package AKSRT
*/
namespace AKSRT\Payment\Gateways;
defined('ABSPATH') || exit;
use Yansongda\Pay\Pay;
/**
* Builds Pay::config() payloads from aksrt_settings and boots the SDK.
*/
class YansongdaPayFactory {
/**
* Boot Pay with a partial channel config (wechat and/or alipay).
*
* @param array<string,mixed> $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<string,mixed>
*/
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<string,mixed>
*/
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<string,mixed>
*/
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<string,mixed> $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<string,mixed>
*/
private static function settings(): array {
if (class_exists('\AKSRT\Settings\Settings')) {
return \AKSRT\Settings\Settings::get_all();
}
return [];
}
/**
* @return array<string,mixed>
*/
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,
];
}
}
-4
View File
@@ -336,10 +336,6 @@ class PaymentRuntime {
'pid' => sanitize_text_field((string) ($settings['epay_pid'] ?? '')), 'pid' => sanitize_text_field((string) ($settings['epay_pid'] ?? '')),
'key' => sanitize_text_field((string) ($settings['epay_key'] ?? '')), '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'] ?? '')),
],
]; ];
} }