Browse Source
feat(pay): 新增支付宝与微信支付功能
feat(pay): 新增支付宝与微信支付功能
- 集成 yansongda/pay 扩展包以支持支付宝和微信支付 - 实现支付宝扫码支付、回调通知、退款、查询及关闭订单功能 - 实现微信扫码支付、回调通知、退款、查询及关闭订单功能 - 添加支付服务类统一处理支付逻辑 - 新增支付控制器用于生成支付二维码 - 配置文件中增加支付宝和微信支付相关参数 - 路由文件中注册支付相关接口并应用用户认证中间件master
9 changed files with 584 additions and 3 deletions
-
3composer.json
-
210composer.lock
-
24plugin/piadmin/app/controller/v1/pay/PayController.php
-
14plugin/piadmin/app/route/v1/pay.php
-
28plugin/piadmin/app/service/pay/PayService.php
-
111plugin/piadmin/app/utils/pay/ali/AliPay.php
-
113plugin/piadmin/app/utils/pay/wechat/WechatPay.php
-
81plugin/piadmin/config/pay.php
-
3plugin/piadmin/config/route.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); |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -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, |
|||
]); |
|||
@ -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; |
|||
} |
|||
|
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -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 |
|||
]; |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue