Browse Source

feat(pay): 新增支付宝与微信支付功能

- 集成 yansongda/pay 扩展包以支持支付宝和微信支付
- 实现支付宝扫码支付、回调通知、退款、查询及关闭订单功能
- 实现微信扫码支付、回调通知、退款、查询及关闭订单功能
- 添加支付服务类统一处理支付逻辑
- 新增支付控制器用于生成支付二维码
- 配置文件中增加支付宝和微信支付相关参数
- 路由文件中注册支付相关接口并应用用户认证中间件
master
zhangf@suq.cn 17 hours ago
parent
commit
61983ecbca
  1. 3
      composer.json
  2. 210
      composer.lock
  3. 24
      plugin/piadmin/app/controller/v1/pay/PayController.php
  4. 14
      plugin/piadmin/app/route/v1/pay.php
  5. 28
      plugin/piadmin/app/service/pay/PayService.php
  6. 111
      plugin/piadmin/app/utils/pay/ali/AliPay.php
  7. 113
      plugin/piadmin/app/utils/pay/wechat/WechatPay.php
  8. 81
      plugin/piadmin/config/pay.php
  9. 3
      plugin/piadmin/config/route.php

3
composer.json

@ -40,7 +40,8 @@
"ext-dom": "*",
"webman/redis-queue": "^2.1",
"ext-curl": "*",
"ext-openssl": "*"
"ext-openssl": "*",
"yansongda/pay": "~3.0"
},
"suggest": {
"ext-event": "For better performance. "

210
composer.lock

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "b4809669623ee48ba422c769b1b0cceb",
"content-hash": "e790d656225abb33ded4bf69b52206b7",
"packages": [
{
"name": "aliyuncs/oss-sdk-php",
@ -7894,6 +7894,214 @@
"source": "https://github.com/x2nx/webman-migrate/tree/v0.0.8"
},
"time": "2025-04-21T06:22:21+00:00"
},
{
"name": "yansongda/artful",
"version": "v1.1.3",
"source": {
"type": "git",
"url": "https://github.com/yansongda/artful.git",
"reference": "ddc203ef34ab369a5a31df057a0fda697d3ed855"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yansongda/artful/zipball/ddc203ef34ab369a5a31df057a0fda697d3ed855",
"reference": "ddc203ef34ab369a5a31df057a0fda697d3ed855",
"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": "^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,请安装,任选其一"
},
"type": "library",
"autoload": {
"files": [
"src/Functions.php"
],
"psr-4": {
"Yansongda\\Artful\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "yansongda",
"email": "me@yansongda.cn"
}
],
"description": "Artful 是一个简单易用的 API 请求框架 PHP Api RequesT Framwork U Like。",
"keywords": [
"api",
"artful",
"framework",
"request"
],
"support": {
"homepage": "https://artful.yansongda.cn",
"issues": "https://github.com/yansongda/artful/issues",
"source": "https://github.com/yansongda/artful"
},
"time": "2025-07-24T09:39:17+00:00"
},
{
"name": "yansongda/pay",
"version": "v3.7.18",
"source": {
"type": "git",
"url": "https://github.com/yansongda/pay.git",
"reference": "813c01e7abed94d2c5ac1a3abdfb87316d78c276"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yansongda/pay/zipball/813c01e7abed94d2c5ac1a3abdfb87316d78c276",
"reference": "813c01e7abed94d2c5ac1a3abdfb87316d78c276",
"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": "2025-08-13T14:28:14+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"
}
],
"packages-dev": [],

24
plugin/piadmin/app/controller/v1/pay/PayController.php

@ -0,0 +1,24 @@
<?php
namespace plugin\piadmin\app\controller\v1\pay;
use plugin\piadmin\app\base\BaseController;
use plugin\piadmin\app\service\pay\PayService;
class PayController extends BaseController
{
/**
* 创建新的订单
*/
public function getQrUrl(PayService $payService)
{
$params = requestOnly([
'price' => '',
'type' => 'wechat'
]);
$qrurl = $payService->getQrUrl($params);
return success($qrurl);
}
}

14
plugin/piadmin/app/route/v1/pay.php

@ -0,0 +1,14 @@
<?php
use plugin\piadmin\app\controller\v1\pay\PayController;
use plugin\piadmin\app\middleware\UserAuthorizationMiddleware;
use Webman\Route;
// 需要登录的接口
Route::group('/piadmin/v1/pay', function () {
//获取支付二维码
Route::post('/getQrUrl', [PayController::class, 'getQrUrl']);
})->middleware([
UserAuthorizationMiddleware::class,
]);

28
plugin/piadmin/app/service/pay/PayService.php

@ -0,0 +1,28 @@
<?php
namespace plugin\piadmin\app\service\pay;
use plugin\piadmin\app\base\BaseService;
use plugin\piadmin\app\utils\pay\ali\AliPay;
use plugin\piadmin\app\utils\pay\wechat\WechatPay;
class PayService extends BaseService
{
public function getQrUrl($params)
{
$order = [
'out_trade_no' => time().'',
'description' => 'subject-测试',
'amount' => [
'total' => $params['price'] * 100,
],
];
if ($params['type'] == 'wechat') {
$qrurl = WechatPay::getQrUrl($order);
} else {
$qrurl = AliPay::getQrUrl($order);
}
return $qrurl;
}
}

111
plugin/piadmin/app/utils/pay/ali/AliPay.php

@ -0,0 +1,111 @@
<?php
namespace plugin\piadmin\app\utils\pay\ali;
use plugin\piadmin\app\utils\pay\PayUtils;
use Yansongda\Pay\Pay;
class AliPay {
public static function getQrUrl($order)
{
Pay::config(config('plugin.piadmin.pay'));
$result = Pay::alipay()->scan($order);
return [
'code_url' => $result['qr_code'],
'qr' => PayUtils::generateQrCode($result['qr_code'])
];
}
public static function notify()
{
Pay::config(config('plugin.piadmin.pay'));
$result = Pay::alipay()->callback();
$result = $result->toArray();
$data = [
'pay_type' => 2
];
$data['is_paid'] = ($result['trade_status'] ?? '') == 'TRADE_SUCCESS';
$data['order_no'] = $result['out_trade_no'] ?? '';
$data['transaction_id'] = $result['trade_no'] ?? '';
$data['payer_open_id'] = $result['buyer_open_id'] ?? '';
if (!empty($result['gmt_payment'])) {
$data['success_time'] = new \DateTime($result['gmt_payment']);
} else {
$data['success_time'] = null;
}
$data['total'] = $result['total_amount'];
$data['pay_total'] = $result['buyer_pay_amount'] ?? '';
$data['currency'] = 'CNY';
$data['pay_notify_message'] = json_encode($result);
// $orderService = app()->make(OrderService::class);
// return $orderService->orderPayNotify($data);
}
public static function refund($order)
{
Pay::config(config('plugin.piadmin.pay'));
$result = Pay::alipay()->refund($order);
return $result->toArray();
}
public static function query($order_no)
{
Pay::config(config('plugin.piadmin.pay'));
// todo 应当支持更多订单类型
$order = [
'out_trade_no' => $order_no,
'_action' => 'scan', // 查询扫码订单
];
$result = Pay::alipay()->query($order);
return $result->toArray();
}
public static function closeOrder(mixed $orderNo)
{
Pay::config(config('plugin.piadmin.pay'));
$order = [
'out_trade_no' => $orderNo,
'_action' => 'scan', // 查询扫码订单
];
$result = Pay::alipay()->close($order);
return $result->toArray();
}
/**
* 校对第三方订单信息
* @param $order
* @param $transactionId
* @return bool
*/
public static function checkTransactionOrder($order, $transactionId): bool
{
if (empty($order) || empty($transactionId)) {
return false;
}
$transOrder = self::query($order['no']);
if (empty($transOrder)) {
return false;
}
// 校验订单号
if ($transOrder['out_trade_no'] != $order['no']) {
return false;
}
// 校验订单状态
if ($transOrder['trade_status'] != 'TRADE_SUCCESS') {
return false;
}
if (env('APP_DEBUG')) {
if ($transOrder['total_amount'] != 0.01) {
return false;
}
} else {
// 校验订单金额
if ($transOrder['total_amount'] != $order['total_price']) {
return false;
}
}
return true;
}
}

113
plugin/piadmin/app/utils/pay/wechat/WechatPay.php

@ -0,0 +1,113 @@
<?php
namespace plugin\piadmin\app\utils\pay\wechat;
use Yansongda\Pay\Pay;
class WechatPay {
public static function getQrUrl($order)
{
Pay::config(config('plugin.piadmin.pay'));
$result = Pay::wechat()->scan($order);
return [
'code_url' => $result['code_url'],
'qr' => $result['code_url']
];
}
public static function notify()
{
Pay::config(config('plugin.piadmin.pay'));
$result = Pay::wechat()->callback();
$result = $result->toArray();
$data = [
'pay_type' => 1
];
$data['is_paid'] = ($result['resource']['ciphertext']['trade_state'] ?? '') == 'SUCCESS';
$data['order_no'] = $result['resource']['ciphertext']['out_trade_no'];
$data['transaction_id'] = $result['resource']['ciphertext']['transaction_id'];
$data['success_time'] = new \DateTime($result['resource']['ciphertext']['success_time']);
$data['payer_open_id'] = $result['resource']['ciphertext']['payer']['openid'] ?? '';
$data['total'] = $result['resource']['ciphertext']['amount']['total'];
$data['pay_total'] = $result['resource']['ciphertext']['amount']['payer_total'];
$data['currency'] = $result['resource']['ciphertext']['amount']['currency'];
$data['pay_notify_message'] = json_encode($result);
// $orderService = app()->make(OrderService::class);
// return $orderService->orderPayNotify($data);
}
public static function refund($order)
{
Pay::config(config('plugin.piadmin.pay'));
$result = Pay::wechat()->refund($order);
return $result->toArray();
}
public static function query($order_no)
{
Pay::config(config('plugin.piadmin.pay'));
$order = [
'out_trade_no' => $order_no,
'_action' => 'native', // 查询 Native 支付
];
$result = Pay::wechat()->query($order);
return $result->toArray();
}
/**
* 校对第三方订单信息
* @param $order
* @param $transactionId
* @return bool
*/
public static function checkTransactionOrder($order, $transactionId): bool
{
if (empty($order) || empty($transactionId)) {
return false;
}
$transOrder = self::query($order['no']);
if (empty($transOrder)) {
return false;
}
// 校验订单号
if ($transOrder['out_trade_no'] != $order['no']) {
return false;
}
// 校验订单状态
if ($transOrder['trade_state'] != 'SUCCESS') {
return false;
}
if (env('APP_DEBUG')) {
if ($transOrder['amount']['total'] / 100 != 0.01) {
return false;
}
} else {
// 校验订单金额
if ($transOrder['amount']['total'] / 100 != $order['total_price']) {
return false;
}
}
return true;
}
/**
* 关闭订单
* @param $orderNo
* @return array
*/
public static function closeOrder($orderNo)
{
Pay::config(config('plugin.piadmin.pay'));
$order = [
'out_trade_no' => $orderNo,
'_action' => 'native', // 查询 Native 支付
];
$result = Pay::wechat()->close($order);
return $result->toArray();
}
}

81
plugin/piadmin/config/pay.php

@ -0,0 +1,81 @@
<?php
use Yansongda\Pay\Pay;
$domain = env('APP_DOMAIN', '');
return [
'alipay' => [
'default' => [
// 「必填」支付宝分配的 app_id
'app_id' => env('ALIPAY_APP_ID'),
// 「必填」应用私钥 字符串或路径
// 在 https://open.alipay.com/develop/manage 《应用详情->开发设置->接口加签方式》中设置
'app_secret_cert' => env('ALIPAY_SECRET'),
// 「必填」应用公钥证书 路径
// 设置应用私钥后,即可下载得到以下3个证书
'app_public_cert_path' => env('ALIPAY_APP_PUBLIC_CERT'),
// 「必填」支付宝公钥证书 路径
'alipay_public_cert_path' => env('ALIPAY_PUBLIC_CERT'),
// 「必填」支付宝根证书 路径
'alipay_root_cert_path' => env('ALIPAY_ROOT_CERT'),
'return_url' => 'https://yansongda.cn/alipay/return',
'notify_url' => env('PAY_NOTIFY') . '/alipay',
// 「选填」第三方应用授权token
'app_auth_token' => '',
// 「选填」服务商模式下的服务商 id,当 mode 为 Pay::MODE_SERVICE 时使用该参数
'service_provider_id' => '',
// 「选填」默认为正常模式。可选为: MODE_NORMAL, MODE_SANDBOX, MODE_SERVICE
'mode' => Pay::MODE_NORMAL,
]
],
'wechat' => [
'default' => [
// 「必填」商户号,服务商模式下为服务商商户号
// 可在 https://pay.weixin.qq.com/ 账户中心->商户信息 查看
'mch_id' => '1602594016',
// 「选填」v2商户私钥
'mch_secret_key_v2' => 'CCA3Wgrb7rT7PiV4qL3MfCPhWzaK3qp6',
// 「必填」v3 商户秘钥
// 即 API v3 密钥(32字节,形如md5值),可在 账户中心->API安全 中设置
'mch_secret_key' => 'hKvKJvTFjKrMxJjXqHFKQkBhcxeUs3xJ',
// 「必填」商户私钥 字符串或路径
// 即 API证书 PRIVATE KEY,可在 账户中心->API安全->申请API证书 里获得
// 文件名形如:apiclient_key.pem
'mch_secret_cert' => env('WX_API_KEY'),
// 「必填」商户公钥证书路径
// 即 API证书 CERTIFICATE,可在 账户中心->API安全->申请API证书 里获得
// 文件名形如:apiclient_cert.pem
'mch_public_cert_path' => env('WX_API_CERT'),
// 「必填」微信回调url
// 不能有参数,如?号,空格等,否则会无法正确回调
'notify_url' => env('PAY_NOTIFY') . '/wechat',
// 「选填」公众号 的 app_id
// 可在 mp.weixin.qq.com 设置与开发->基本配置->开发者ID(AppID) 查看
'mp_app_id' => 'wxfdf40162614e30d1',
// 「选填」小程序 的 app_id
'mini_app_id' => '',
// 「选填」app 的 app_id
'app_id' => 'wxfdf40162614e30d1',
// 「选填」服务商模式下,子公众号 的 app_id
'sub_mp_app_id' => '',
// 「选填」服务商模式下,子 app 的 app_id
'sub_app_id' => '',
// 「选填」服务商模式下,子小程序 的 app_id
'sub_mini_app_id' => '',
// 「选填」服务商模式下,子商户id
'sub_mch_id' => '',
// 「选填」(适用于 2024-11 及之前开通微信支付的老商户)微信支付平台证书序列号及证书路径,强烈建议 php-fpm 模式下配置此参数
// 「必填」微信支付公钥ID及证书路径,key 填写形如 PUB_KEY_ID_0000000000000024101100397200000006 的公钥id,
// 见 https://pay.weixin.qq.com/doc/v3/merchant/4013053249
'wechat_public_cert_path' => [
// '45F59D4DABF31918AFCEC556D5D2C6E376675D57' => __DIR__.'/Cert/wechatPublicKey.crt',
'PUB_KEY_ID_0116025940162025032500389200002634' => env('wx_pub_key'),
],
// 「选填」默认为正常模式。可选为: MODE_NORMAL, MODE_SERVICE
'mode' => Pay::MODE_NORMAL,
]
],
// 订单超时
'order_out_time' => 15 * 60
];

3
plugin/piadmin/config/route.php

@ -8,7 +8,8 @@ require_once config('plugin.piadmin.piadmin.path').'/app/route/v1/route.php';
require_once config('plugin.piadmin.piadmin.path').'/app/route/v1/attachment.php';
// 引入v1代理端路由
require_once config('plugin.piadmin.piadmin.path').'/app/route/v1/proxy.php';
// 引入v1支付模块路由
require_once config('plugin.piadmin.piadmin.path').'/app/route/v1/pay.php';
// 处理404路由
Route::fallback(function(){

Loading…
Cancel
Save