diff --git a/composer.json b/composer.json index c6db195..007daad 100644 --- a/composer.json +++ b/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. " diff --git a/composer.lock b/composer.lock index 09a795e..6e4b6c7 100644 --- a/composer.lock +++ b/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": [], diff --git a/plugin/piadmin/app/controller/v1/pay/PayController.php b/plugin/piadmin/app/controller/v1/pay/PayController.php new file mode 100644 index 0000000..585d140 --- /dev/null +++ b/plugin/piadmin/app/controller/v1/pay/PayController.php @@ -0,0 +1,24 @@ + '', + 'type' => 'wechat' + ]); + $qrurl = $payService->getQrUrl($params); + return success($qrurl); + } + + +} \ No newline at end of file diff --git a/plugin/piadmin/app/route/v1/pay.php b/plugin/piadmin/app/route/v1/pay.php new file mode 100644 index 0000000..4b49b7e --- /dev/null +++ b/plugin/piadmin/app/route/v1/pay.php @@ -0,0 +1,14 @@ +middleware([ + UserAuthorizationMiddleware::class, +]); diff --git a/plugin/piadmin/app/service/pay/PayService.php b/plugin/piadmin/app/service/pay/PayService.php new file mode 100644 index 0000000..d17b277 --- /dev/null +++ b/plugin/piadmin/app/service/pay/PayService.php @@ -0,0 +1,28 @@ + time().'', + 'description' => 'subject-测试', + 'amount' => [ + 'total' => $params['price'] * 100, + ], + ]; + if ($params['type'] == 'wechat') { + $qrurl = WechatPay::getQrUrl($order); + } else { + $qrurl = AliPay::getQrUrl($order); + } + return $qrurl; + } + +} \ No newline at end of file diff --git a/plugin/piadmin/app/utils/pay/ali/AliPay.php b/plugin/piadmin/app/utils/pay/ali/AliPay.php new file mode 100644 index 0000000..ced3bb8 --- /dev/null +++ b/plugin/piadmin/app/utils/pay/ali/AliPay.php @@ -0,0 +1,111 @@ +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; + } +} \ No newline at end of file diff --git a/plugin/piadmin/app/utils/pay/wechat/WechatPay.php b/plugin/piadmin/app/utils/pay/wechat/WechatPay.php new file mode 100644 index 0000000..c01c827 --- /dev/null +++ b/plugin/piadmin/app/utils/pay/wechat/WechatPay.php @@ -0,0 +1,113 @@ +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(); + } +} \ No newline at end of file diff --git a/plugin/piadmin/config/pay.php b/plugin/piadmin/config/pay.php new file mode 100644 index 0000000..1abbaf2 --- /dev/null +++ b/plugin/piadmin/config/pay.php @@ -0,0 +1,81 @@ + [ + '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 +]; \ No newline at end of file diff --git a/plugin/piadmin/config/route.php b/plugin/piadmin/config/route.php index 13195f7..55deb6b 100644 --- a/plugin/piadmin/config/route.php +++ b/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(){