2018年做視頻網(wǎng)站網(wǎng)站seo站長(zhǎng)工具
技術(shù)說(shuō)明
1.前端:uniapp、vue3
2.接口:PHP8、ThinkPHP8、MySQL8.0
3.微信支付- PHP,官方示例文檔
4.示例代碼的模型及業(yè)務(wù)自己進(jìn)行調(diào)整,不要一味的復(fù)制粘貼!!!
流程說(shuō)明
1.小程序調(diào)用接口--獲取拉起支付所用參數(shù),生成訂單
2.拉起微信支付
3.支付完成-更改訂單狀態(tài)
參數(shù)說(shuō)明
1.appid - 小程序id
2.mchid -- 商戶號(hào)ID
3.certificate_serial -- 證書序列號(hào)
4.api_v3_key -- 支付密鑰(v3)
5.apiclient_key.pem --?商戶API私鑰文件,根據(jù)微信支付下載器下載即可
6.cert.pem --?微信支付平臺(tái)證書文件(注意:此文件必須是手動(dòng)下載的,具體下載方式下方有說(shuō)明!!!)
其他說(shuō)明
1.本示例采用微信支付sdk
2.實(shí)際情況根據(jù)業(yè)務(wù)進(jìn)行調(diào)整;
3.通知回調(diào)(未能正確返回)
4.其他沒(méi)毛病。
項(xiàng)目示例
1.安裝微信支付 wechatpay -- sdk
composer require wechatpay/wechatpay
2.下載微信支付平臺(tái)證書文件
(1)下載微信支付平臺(tái)證書下載器
(2)進(jìn)行詳情頁(yè)(微信支付平臺(tái)證書下載器)
(3)下載CertificateDownloader.php,點(diǎn)擊下方紅框,直接下載文件就行,文件位置隨便放,只要能用php命令運(yùn)行就行
(4)下載證書,直接復(fù)制下面命令,改參數(shù)即可。
? ? ? ? -k 支付密鑰(上方參數(shù)4)
? ? ? ? -m 商戶號(hào)(上方參數(shù)2)
? ? ? ? -f 商戶密鑰(上方參數(shù)5,需要完整路徑)
? ? ? ? -s 證書序列號(hào)(上方參數(shù)3)
? ? ? ? -o 生成證書地址(需要本地完整路徑)
php -f ./CertificateDownloader.php -- -k 4202c8***** -m 16***** -f /****/apiclient_key.pem -s 25***** -o /*****/cert/
3.封裝支付類(完整示例如下)
<?phpnamespace app\common\controller;use WeChatPay\Builder;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;
use WeChatPay\Util\PemUtil;/*** @note 微信支付操作*/
class WechatPay
{protected string $spAppid; // 小程序appidprotected string $spAppSecret; // 小程序密鑰protected string $merchantId; // 商戶號(hào)protected string $certificateSerial; // 證書序列號(hào)protected string $apiV3Key; // APIv3密鑰protected object $instance; // 實(shí)例protected string $merchantPrivateKeyFilePath;public function __construct(){$this->spAppid = config('wechat.sp.appid');$this->spAppSecret = config('wechat.sp.secret');$this->merchantId = config('wechat.pay.mchid');$this->certificateSerial = config('wechat.pay.certificate_serial');$this->apiV3Key = config('wechat.pay.api_v3_key');// 從本地文件中加載「商戶API私鑰」,「商戶API私鑰」會(huì)用來(lái)生成請(qǐng)求的簽名$this->merchantPrivateKeyFilePath = root_path() . 'wxcert/apiclient_key.pem';if (!file_exists($this->merchantPrivateKeyFilePath)) throw new \Exception('商戶API私鑰文件不存在');$merchantPrivateKeyFilePath = 'file://' . $this->merchantPrivateKeyFilePath;$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);// 從本地文件中加載「微信支付平臺(tái)證書」,用來(lái)驗(yàn)證微信支付應(yīng)答的簽名$platformCertificateFilePath = root_path() . 'wxcert/cert.pem';if (!file_exists($platformCertificateFilePath)) throw new \Exception('微信支付平臺(tái)證書文件不存在');$platformCertificateFilePath = 'file://' . $platformCertificateFilePath;$platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);// 從「微信支付平臺(tái)證書」中獲取「證書序列號(hào)」$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);// 構(gòu)造一個(gè) APIv3 客戶端實(shí)例$this->instance = Builder::factory(['mchid' => $this->merchantId, // 商戶號(hào)'serial' => $this->certificateSerial, //「商戶API證書」的「證書序列號(hào)」'privateKey' => $merchantPrivateKeyInstance,'certs' => [$platformCertificateSerial => $platformPublicKeyInstance,],]);}/*** @note 獲取微信支付預(yù)交易訂單* @param string $openid 用戶openid* @param string $out_trade_no 訂單號(hào)* @param string $notify_url 回調(diào)地址* @param float $price 價(jià)格* @param string $desc 描述*/public function spPrepayId(string $openid, string $out_trade_no, string $notify_url, float $price = 0.01, string $desc = '訂單'){$prepay_id = '';try {$resp = $this->instance->chain('/v3/pay/transactions/jsapi')->post(['json' => ['mchid' => $this->merchantId,'out_trade_no' => $out_trade_no,'appid' => $this->spAppid,'description' => $desc,'notify_url' => $notify_url,'amount' => ['total' => $price * 100,'currency' => 'CNY'],'payer' => ['openid' => $openid]]]);$res = json_decode($resp->getBody());$prepay_id = $res->prepay_id;} catch (\Exception $e) {// 進(jìn)行錯(cuò)誤處理echo $e->getMessage(), PHP_EOL;;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;}echo $e->getTraceAsString(), PHP_EOL;}return $prepay_id;}/*** @note 生成簽名* @param string $prepay_id 預(yù)交易訂單* @param string $nonceStr 隨機(jī)字符串* @param string $timeStamp 時(shí)間戳* @return string*/public function makeSign(string $prepay_id, string $nonceStr, string $timeStamp): string{if (!file_exists($this->merchantPrivateKeyFilePath)) return '';$merchantPrivateKeyFilePath = 'file://' . $this->merchantPrivateKeyFilePath;$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath);$params = ['appId' => $this->spAppid,'timeStamp' => $timeStamp,'nonceStr' => $nonceStr,'package' => 'prepay_id=' . $prepay_id,];$params += ['paySign' => Rsa::sign(Formatter::joinedByLineFeed(...array_values($params)),$merchantPrivateKeyInstance), 'signType' => 'RSA'];return $params['paySign'] ?? '';}/*** @note 回調(diào)通知,參數(shù)解密* @param string $inWechatpaySignature 微信支付平臺(tái)簽名* @param string $inWechatpayTimestamp 微信支付平臺(tái)時(shí)間戳* @param string $inWechatpayNonce 微信支付平臺(tái)隨機(jī)串* @param string $inBody 通知內(nèi)容* @param string $inWechatpaySerial 平臺(tái)證書序列號(hào)* @param string $inRequestID 請(qǐng)求ID* @return array*/public function notifyDecrypt(string $inWechatpaySignature, string $inWechatpayTimestamp, string $inWechatpayNonce, string $inBody, string $inWechatpaySerial, string $inRequestID = ''): array{// 根據(jù)通知的平臺(tái)證書序列號(hào),查詢本地平臺(tái)證書文件,$platformCertificateFilePath = root_path() . 'wxcert/cert.pem';if (!file_exists($platformCertificateFilePath)) throw new \Exception('微信支付平臺(tái)證書文件不存在');$platformCertificateFilePath = 'file://' . $platformCertificateFilePath;$platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);// 檢查通知時(shí)間偏移量,允許5分鐘之內(nèi)的偏移$timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);$verifiedStatus = Rsa::verify(// 構(gòu)造驗(yàn)簽名串Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),$inWechatpaySignature,$platformPublicKeyInstance);if ($timeOffsetStatus && $verifiedStatus) {// 轉(zhuǎn)換通知的JSON文本消息為PHP Array數(shù)組$inBodyArray = (array)json_decode($inBody, true);// 使用PHP7的數(shù)據(jù)解構(gòu)語(yǔ)法,從Array中解構(gòu)并賦值變量['resource' => ['ciphertext' => $ciphertext,'nonce' => $nonce,'associated_data' => $aad]] = $inBodyArray;// 加密文本消息解密$inBodyResource = AesGcm::decrypt($ciphertext, $this->apiV3Key, $nonce, $aad);// 把解密后的文本轉(zhuǎn)換為PHP Array數(shù)組return (array)json_decode($inBodyResource, true);}return [];}/*** @note 加密消息解密*/public function decryptMsg($encryptedData, $iv, $sessionKey): array|string{$pc = new WxBizDataCrypt($this->spAppid, $sessionKey);$errCode = $pc->decryptData($encryptedData, $iv, $data);if ($errCode == 0) {return $data;}return [];}}
4.封裝接口(完整示例如下)
<?phpnamespace app\api\controller\sp;use think\response\Json;class Activity
{/*** @note 生成訂單*/public function prepayId(): void{$activityId = $this->request->post('ac_id/d', 1);if (empty($activityId)) $this->error('賽事錯(cuò)誤,請(qǐng)重試!');$openid = $this->request->post('openid/s', '');if (empty($openid)) $this->error('支付用戶獲取失敗,請(qǐng)重試!');$model = new ActivityModel();$activity = $model->findOrEmpty($activityId)->toArray();if (empty($activity)) $this->error('get Err');if ($activity['status'] != 1) $this->error('get Err!');// 訂單信息$orderInfo = ['activity_id' => $activityId,'openid' => $openid,'number' => 'order' . date('YmdHis') . rand(1000, 9999),'money' => $activity['price'],'type' => 1,'status' => 0];// 生成訂單$pay = new WechatPay();$notify_url = env('domain') . 'index.php/api/sp.Activity/notify';$prepayId = $pay->spPrepayId($openid, $orderInfo['number'], $notify_url);if (empty($prepayId)) $this->error('訂單生成失敗,請(qǐng)重試!');$orderInfo['prepay_id'] = $prepayId;$order = new Order();$order->save($orderInfo);$timeStamp = (string)time();$orderInfo['timeStamp'] = $timeStamp;$nonceStr = getRandStr(32);$orderInfo['nonceStr'] = $nonceStr;$orderInfo['package'] = 'prepay_id=' . $prepayId;$orderInfo['paySign'] = $pay->makeSign($prepayId, $nonceStr, $timeStamp);$this->success('get Success', ['order' => $orderInfo]);}/*** @note 支付回調(diào)*/public function notify(): Json{$inWechatpaySignature = request()->header('Wechatpay-Signature', ''); // header中獲取簽名$inWechatpayTimestamp = request()->header('Wechatpay-Timestamp', ''); // header中獲取時(shí)間戳$inWechatpaySerial = request()->header('Wechatpay-Serial', ''); // header中獲取證書序列號(hào)$inWechatpayNonce = request()->header('Wechatpay-Nonce', ''); // header中獲取隨機(jī)字符串$inRequestID = request()->header('Request-ID', ''); // 請(qǐng)根據(jù)實(shí)際情況獲取$inBody = file_get_contents('php://input'); // 請(qǐng)根據(jù)實(shí)際情況獲取,例如: file_get_contents('php://input');$pay = new WechatPay();$res = $pay->notifyDecrypt($inWechatpaySignature, $inWechatpayTimestamp, $inWechatpayNonce, $inBody, $inWechatpaySerial, $inRequestID);if (!empty($res)) {// 進(jìn)行訂單數(shù)據(jù)修改$order = new Order();// 查詢訂單數(shù)據(jù)$orderInfo = $order->where('number', $res['out_trade_no'])->find();if (!empty($orderInfo)){$result = $order->where('id',$orderInfo['id'])->save(['transaction_id' => $res['transaction_id'],'status' => $res['trade_state'] == 'SUCCESS' ? 1 : 0,'trade_type' => $res['trade_type'],'trade_state_desc' => $res['trade_state_desc'],'bank_type' => $res['bank_type'],'success_time' => $res['success_time']]);cache(':order_' . $res['out_trade_no'], $result, 3600);}return json(['code' => 'SUCCESS']);}return json(['message' => '失敗', 'code' => 'FAIL']);}}
5.uniapp示例
<template><view class="box"><view><up-button text="立即支付" type="primary" @click="toPay"></up-button> </view><up-toast ref="uToastRef"></up-toast></view>
</template><script setup>import {onLoad} from '@dcloudio/uni-app'import {ref,} from 'vue';import {getPrepayId} from '@/utils/api/order.js'const uToastRef = ref(null)// 點(diǎn)擊支付const toPay = () => {getPrepayId({openid: ''}).then((res) => {if (res.code == 1) {const order = res.data.orderuni.requestPayment({provider: 'wxpay',timeStamp: order.timeStamp, // 時(shí)間戳nonceStr: order.nonceStr, // 隨機(jī)字符串,長(zhǎng)度為32個(gè)字符以下package: order.package, // 統(tǒng)一下單接口返回的 prepay_id 參數(shù)值,提交格式如:prepay_id=***signType: 'RSA', // 簽名算法,應(yīng)與后臺(tái)下單時(shí)的值一致paySign: order.paySign, // 簽名success: function(res) {console.log('success:' + JSON.stringify(res));},fail: function(err) {console.log('fail:' + JSON.stringify(err),);}});} else {uToastRef.value.error(res.msg)}})}
</script><style lang="scss">.box {width: 100%;}
</style>