由网络副手--寻路人于2021.09.06 10:51:00发布在 新企业微信支付整个流程-小程序支付 阅读1924 评论0 喜欢0 一、前置条件小程序、商户信息、商户证书、序列号都申请好 二、涉及文档 1、[微信开发者文档--进入小程序文档][1] 2、[微信V3文档简介-了解即可][2] 3、[支付相关官方程序包][3] 4、[平台证书生成生成包][4] 三、微信官方支付流程设计 [点击查看文档--前身大体了解流程即可--主要看V3][5] ![请输入图片描述][6] 四、程序设计 1. 客户端小程序---->2. 服务端(预下单)--->输出小程序吊起支付信息---->3.拉起支付---->4.支付成功异步回调 4.1 程序开发前,物料准备 4.1.1 安装扩展包 composer require wechatpay/wechatpay 4.1.2 生成平台使用证书 1、下载 https://github.com/wechatpay-apiv3/wechatpay-php.git 2、composer install 3、生成证书 ./bin/CertificateDownloader.php -k 'APIV3的秘钥' -m '商户号' -f '商户生成的证书apiclient_key.pem' -s '商户证书序列号' -o /www/生成平台证书目录/ 4.1.3 把程商户平台生成的证书、上面代码生成的证书,都拷贝到服务器自定义证书目录,便于后面用. 4.3 封装微信所有功能模块化类 * +---------------------------------------------------------------------- * | @Date : 2021/9/5 3:14 PM * +---------------------------------------------------------------------- */ namespace App\Services\Wx; use App\Log\Log; use WeChatPay\Builder; use WeChatPay\Crypto\AesGcm; use WeChatPay\Crypto\Rsa; use WeChatPay\Formatter; use WeChatPay\Util\PemUtil; class WxPayService { public static function weChatPayFactory() { // 商户号,假定为`1000100` $merchantId = env('MCH_ID'); // 商户私钥,文件路径假定为 `/path/to/merchant/apiclient_key.pem` $merchantPrivateKeyFilePath = 'file://'.env('CERT_KEY_PATH');// 注意 `file://` 开头协议不能少 // 加载商户私钥 $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE); $merchantCertificateSerial = env('CERT_CODE');// API证书不重置,商户证书序列号就是个常量 // // 也可以使用openssl命令行获取证书序列号 // // openssl x509 -in /path/to/merchant/apiclient_cert.pem -noout -serial | awk -F= '{print $2}' // // 或者从以下代码也可以直接加载 // // 「商户证书」,文件路径假定为 `/path/to/merchant/apiclient_cert.pem` // $merchantCertificateFilePath = 'file:///path/to/merchant/apiclient_cert.pem';// 注意 `file://` 开头协议不能少 // // 解析「商户证书」序列号 // $merchantCertificateSerial = PemUtil::parseCertificateSerialNo($merchantCertificateFilePath); // 「平台证书」,可由下载器 `./bin/CertificateDownloader.php` 生成并假定保存为 `/path/to/wechatpay/cert.pem` $platformCertificateFilePath = 'file://'.env('CERT_BUILD_PEM');// 注意 `file://` 开头协议不能少 // 加载「平台证书」公钥 $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC); // 解析「平台证书」序列号,「平台证书」当前五年一换,缓存后就是个常量 $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath); // 工厂方法构造一个实例 $instance = Builder::factory([ 'mchid' => $merchantId, 'serial' => $merchantCertificateSerial, 'privateKey' => $merchantPrivateKeyInstance, 'certs' => [ $platformCertificateSerial => $platformPublicKeyInstance, ], // APIv2密钥(32字节)--不使用APIv2可选 // 'secret' => 'exposed_your_key_here_have_risks',// 值为占位符,如需使用APIv2请替换为实际值 // 'merchant' => [// --不使用APIv2可选 // // 商户证书 文件路径 --不使用APIv2可选 // 'cert' => $merchantCertificateFilePath, // // 商户API私钥 文件路径 --不使用APIv2可选 // 'key' => $merchantPrivateKeyFilePath, // ], ]); return $instance; } /** * @desc 预下单 * @param $openid * @param $orderId * @param $money * @param $goodsDesc * @return string * @author bravedu */ public static function prePayOrder($openid, $orderId, $money, $goodsDesc): string { $res = self::weChatPayFactory() ->chain('v3/pay/transactions/jsapi') ->postAsync([ 'json' => [ 'appid' => env('APP_ID'), 'mchid' => env('MCH_ID'), 'description' => $goodsDesc, 'out_trade_no' => $orderId, 'notify_url' => env('NOTIFY_URL'), 'amount' => [ 'total' => $money, 'currency' => 'CNY', ], 'payer' => [ 'openid' => $openid, ], ], ]) ->then(static function($response) { // 正常逻辑回调处理 $res = $response->getBody()->getContents(); $res = json_decode($res, true); if (!empty($res['prepay_id'])) { return $res['prepay_id']; } return ''; }) ->otherwise(static function($e) { // 异常错误处理 // if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) { // $r = $e->getResponse(); // echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL; // echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL; // } throw new \Exception($e); }) ->wait(); return $res; } /** * @desc 支付签名 * @param $prePayId * @return array * @author bravedu */ public static function wxMiniPaySign($prePayId) { $merchantPrivateKeyFilePath = 'file://'.env('CERT_KEY_PATH'); $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath); $params = [ 'appId' => env('APP_ID'), 'timeStamp' => (string)Formatter::timestamp(), 'nonceStr' => Formatter::nonce(), 'package' => 'prepay_id='.$prePayId, ]; $params += ['paySign' => Rsa::sign( Formatter::joinedByLineFeed(...array_values($params)), $merchantPrivateKeyInstance ), 'signType' => 'RSA']; return $params; } /** * @desc 支付回调解析 * @param array $requestHeader * @return array * @author bravedu { "mchid":"xxxx", "appid":"wx34556777", "out_trade_no":"wx955734508174274188888", "transaction_id":"42000011602021090614168888", "trade_type":"JSAPI", "trade_state":"SUCCESS", "trade_state_desc":"支付成功", "bank_type":"OTHERS", "attach":"", "success_time":"2021-09-06T01:27:02+08:00", "payer":{ "openid":"oh0oj5Dxi8JAFJ-188888_kHbqE" }, "amount":{ "total":1, "payer_total":1, "currency":"CNY", "payer_currency":"CNY" } } */ public static function payNotifyParse($requestHeader) { try { Log::info("header", $requestHeader); $inWechatpaySignature = $requestHeader['wechatpay-signature'][0];// 请根据实际情况获取 $inWechatpayTimestamp = $requestHeader['wechatpay-timestamp'][0];// 请根据实际情况获取 $inWechatpaySerial = $requestHeader['wechatpay-serial'][0];// 请根据实际情况获取 $inWechatpayNonce = $requestHeader['wechatpay-nonce'][0];// 请根据实际情况获取 $inBody = file_get_contents('php://input'); $apiv3Key = env('MCH_KEY_V3');// 在商户平台上设置的APIv3密钥 // 根据通知的平台证书序列号,查询本地平台证书文件, // 假定为 `/path/to/wechatpay/inWechatpaySerial.pem` $platformPublicKeyInstance = Rsa::from('file://' . env('CERT_BUILD_PEM'), Rsa::KEY_TYPE_PUBLIC); // 检查通知时间偏移量,允许5分钟之内的偏移 $timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp); Log::info("reqestParams", [ 'formatterTime' => Formatter::timestamp(), 'inWechatpayTimestamp' => $inWechatpayTimestamp, 'absTimeOffsetStatus' => $timeOffsetStatus, 'inWechatpayNonce' => $inWechatpayNonce, 'inBody' => $inBody, ]); $verifiedStatus = Rsa::verify( // 构造验签名串 Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody), $inWechatpaySignature, $platformPublicKeyInstance ); Log::info("reqestParams-verifiedStatus", [ 'verifiedStatus' => $verifiedStatus, ]); if ($timeOffsetStatus && $verifiedStatus) { $inBodyArray = (array)json_decode($inBody, true); ['resource' => [ 'ciphertext' => $ciphertext, 'nonce' => $nonce, 'associated_data' => $aad ]] = $inBodyArray; $inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad); $inBodyResourceArray = (array)json_decode($inBodyResource, true); Log::info("payNotifyParse", $inBodyResourceArray); // print_r($inBodyResourceArray);// 打印解密后的结果 return $inBodyResourceArray; } return []; }catch (\Exception $e) { Log::error("payNotifyParse-try-cache", [ 'message' => $e->getMessage(), 'line' => $e->getLine(), 'file' => $e->getFile(), 'code' => $e->getCode() ]); return []; } } } 4.4 预支付环节代码编写 /** * @desc 预支付 * @param array * @return array * @author bravedu { "appid": "wx5eed18cd582888", "mch_id": "161363888", "nonce_str": "RatCYb7JmTOS8888", "prepay_id": "wx311448544553724c1736ee8c62830888", "sign": "FCBE024FBE1F14DD924E7B929DB1792C5326C2BF7265C17EDBDE7B9D18888", "trade_type": "JSAPI" } */ public function preOrder($userId, $money, $type, $sign = '') { //商品信息组装 $prodInfo = [ 'goodsSign' => '', //商品标识-ID,用于防止支付篡改金额 'goodsName' => '', //商品名称 'goodsMoney' => 0, //商品金额 ]; //白名单支付金额1分 if (in_array($openId, explode(',', env('FREE_PAY_USERS')))) { $prodInfo['goodsMoney'] = 1; } try { DB::beginTransaction(); $this->payOrderModel->create( [ 'user_id' => $userId, 'goods_sign' => $prodInfo['goodsSign'], 'money' => $prodInfo['goodsMoney'], 'mch_id' => env('MCHID', ''), 'out_trade_no' => $outTradeNo, 'status' => 0, 'pay_type' => $type, // 1vip 2 coin 'created_at' => date('Y-m-d H:i:s') ] ); } catch (\Exception $e) { DB::rollBack(); Log::error('preOrder_fail', ['exception' => json_encode($e), 'msg' => 'DB统一下单失败']); throw new ApiException('下单失败, 请重试', 20006); } try { //预下单 $preOrderId = WxPayService::prePayOrder($openId, $outTradeNo, $prodInfo['goodsMoney'], $prodInfo['goodsName']); $resp = WxPayService::wxMiniPaySign($preOrderId); DB::commit(); return $resp; } catch (ApiException $e) { DB::rollBack(); throw new ApiException('下单失败, 请重试', 20009); } } 4.5 支付回调服务 /** * @desc 回调 * @param $reqHeader * @return bool * @author bravedu */ public function notify($reqHeader) { Log::DEBUG("begin_notify"); try { $payRes = WxPayService::payNotifyParse($reqHeader); if (empty($payRes['out_trade_no'])) { Log::error('notify-result--支付回调失败', ['result' => $payRes]); } //支付成功逻辑处理 $this->handleNotifyBusiness($payRes['out_trade_no']); //通知微信服务xml 数据 $obj = [ 'return_code' => 'SUCCESS/FAIL', 'return_msg' => 'OK 或者 空', ]; die($obj->toXml()); } catch (\Exception $e) { Log::error('notify-try-cache', ['exception' => json_encode($e), 'msg' => '回调中异常']); return false; } Log::DEBUG("begin_end"); return true; } [1]: https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml [2]: https://wechatpay-api.gitbook.io/wechatpay-api-v3/ [3]: https://github.com/wechatpay-apiv3/wechatpay-php [4]: https://github.com/wechatpay-apiv3/wechatpay-php/blob/main/bin/README.md [5]: https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_sl_api.php?chapter=7_4&index=3 [6]: https://pay.weixin.qq.com/wiki/doc/apiv3/assets/img/pay/wechatpay/6_2.png 赞 0 分享 赏 您可以选择一种方式赞助本站 支付宝扫码赞助 BraveDu 署名: 网络副手~寻路人