?? 申請商戶號
- 申請地址: https://pay.weixin.qq.com/
- 如果你還沒有微信商戶號,請點擊上面的鏈接進(jìn)行申請,如果已經(jīng)有了,可以跳過這一步
?? 申請商戶證書
- 首先點擊
賬戶中心
?API安全
?申請API證書
- 申請詳細(xì)步驟: https://kf.qq.com/faq/161222NneAJf161222U7fARv.html
?? 設(shè)置APIv3密鑰
- 首先點擊
賬戶中心
?API安全
?設(shè)置APIv3密鑰
?設(shè)置
- 會看到有兩個密鑰,分別是
APIv2密鑰
和APIv3密鑰
,由于APIv2密鑰
已經(jīng)逐漸廢棄了,所以只需要申請APIv3密鑰
即可 - 密鑰可由數(shù)字大小寫字母組合,輸入任意的
32
位字符,該密鑰需要保存好,供后面使用
// 生成32位的APIv3隨機密鑰
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
echo substr(str_shuffle($chars), 0, $length);
?? 下載 SDK 開發(fā)包
- 微信官方提供了
JAVA
、PHP
、GO
三種語言版本的開發(fā)庫,請根據(jù)自己開發(fā)語言選擇 - JAVA語言: wechatpay-java ?推薦?、wechatpay-apache-httpclient
- PHP語言: wechatpay-php ?推薦?、wechatpay-guzzle-middleware
- GO語言: wechatpay-go ?推薦?
- 由于
php
實現(xiàn)支付相對簡單,所以我將以php
作為支付的講解 - 首先使用
composer
安裝sdk
# 初始化文件夾
composer init
# 推薦使用 PHP 包管理工具 Composer 安裝 SDK
composer require wechatpay/wechatpay
?? 下載平臺證書
- 平臺證書跟上面申請的商戶證書不是同一個東西,在后期請求中,平臺證書和商戶證書都要帶上
- 上面命令執(zhí)行完之后,會有一個
vendor/bin/CertificateDownloader.php
文件 - 如果你是第一次申請平臺證書,需要執(zhí)行命令:
php CertificateDownloader.php -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath}
-
-k:
apiv3
秘鑰,上面自己設(shè)置的32位數(shù)的密鑰 - -m: 商戶號,微信商戶平臺可以查詢
-
-f: 微信商戶API私鑰文件目錄,也就是第二步申請商戶證書里面生成的
apiclient_key.pem
路徑 -
-s: 證書序列號,在
賬戶中心
?API安全
?管理證書
中可以看見,如果有多個證書,找到自己正在使用的證書序列號 - -o: 生成后的證書保存地址
cd vendor/bin/
php CertificateDownloader.php -k 241xxxxxxxxxxxxxxxxx44 -m 1xxxxxxx1 -f ../../cert/merchant/apiclient_key.pem -s Wxxxxxxxxxxxxxxxx4 -o ../../cert/wechatpay/
?? 關(guān)聯(lián) AppID 賬號
- 因為使用的是微信支付,所以用戶支付后,需要通過微信號通知用戶支付的一些信息,所以需要在商戶號下至少關(guān)聯(lián)一個公眾號
?? 開通 H5 支付
- 點擊
產(chǎn)品中心
?我的產(chǎn)品
?H5支付
?點擊開通
- 開通后,選擇
開發(fā)配置
?H5支付域名
申請?zhí)砑?H5支付域名
- 申請支付域名需要先做好產(chǎn)品的頁面,申請的時候需要有頁面的截圖,截圖中還要
截取到域名
,支付的審核算是很嚴(yán)格的,如果申請不過,駁回后再申請,審核通過的時間會越來越長,所以最好一次性就把材料收集好,另外還要域名的備案的IPC
截圖 -
IPC
備案查詢地址: https://beian.miit.gov.cn/ - 關(guān)于域名的填寫,如果只填寫域名不填寫具體域名路徑,微信在支付的時候就只會校驗域名,這也是最方便的,因為域名下有多個項目有支付功能的話,就不需要重復(fù)添加了
文章來源:http://www.zghlxwxcb.cn/news/detail-584277.html
?? H5支付流程
-
H5
支付是在微信以外的瀏覽器使用的,如果是微信內(nèi)的話,使用的是jsapi
支付 - 所以一般用戶進(jìn)入頁面的第一件事,就是檢測用戶使用的環(huán)境是微信瀏覽器還是其他瀏覽器
- 前端傳一些用戶挑選商品后的參數(shù),并請求后端處理接口,后端應(yīng)該將一些參數(shù)進(jìn)行入庫,順便請求
H5
支付接口 - 接口應(yīng)該返回跳轉(zhuǎn)鏈接
h5_url
,如果你想用戶付款之后到結(jié)果頁面,需要添加redirect_url
參數(shù),這個參數(shù)一定要用encodeURIComponent
進(jìn)行處理 - 由于官方在
jssapi
支付中說明,不要相信前端的success
結(jié)果,所以需要在結(jié)果頁中,讓用戶自動觸發(fā)查詢結(jié)果,因此需要返回后端生成的訂單號,用作在結(jié)果頁的用戶手動點擊查詢
// 判斷是否微信瀏覽器
function isWeChat() {
var ua = navigator.userAgent.toLowerCase();
if (ua.match(/MicroMessenger/i) == 'micromessenger') {
return true;
} else {
return false;
}
}
if(isWeChat()) {
// 是微信中打開的產(chǎn)品頁面
alert('微信內(nèi)不支持h5支付,請在外部瀏覽器打開頁面');
} else {
// 非微信內(nèi)打開的產(chǎn)品頁面,請求接口,獲取支付的跳轉(zhuǎn)鏈接
// 前端用戶選的產(chǎn)品,以及產(chǎn)品的金額,傳一些參數(shù)過去
let params = {
total: 2, // 單位:元
description: 'Image形象店-深圳騰大-QQ公仔' // 產(chǎn)品的介紹
// ....更多入庫參數(shù)
};
$.getJSON('后端接口地址/h5?' + $.param(params) + '&callback=?', function(res) {
// 拉起微信支付界面,成功后會跳轉(zhuǎn)到redirect_url鏈接
$(location).attr("href", res.data.h5_url + "&redirect_url=" + encodeURIComponent(`https://xxxxxx/finish?out_trade_no=${res.data.out_trade_no}`))
});
}
<?php
// 僅僅用作展示,不可直接復(fù)制使用
require_once('../vendor/autoload.php');
use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Util\PemUtil;
// 接受參數(shù),相當(dāng)于原生的$_GET
$input = $request->only(['name', 'total', 'description', 'phone']);
// 生成商戶訂單號
$out_trade_no = getOutTradeNo();
// 處理金額
// 由于微信使用的是分作為單位,所以前端傳的是元的話,需要轉(zhuǎn)換一下
$total = $input['total'] * 100;
// 商戶號
$merchantId = '1xxxxxx1';
// 從本地文件中加載「商戶API私鑰」,「商戶API私鑰」會用來生成請求的簽名
$merchantPrivateKeyFilePath = 'file://../cert/merchant/apiclient_key.pem';
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
// 「商戶API證書」的「證書序列號」
$merchantCertificateSerial = '1xxxxxxxxxxxxxxxxxxxxx91';
// 從本地文件中加載「微信支付平臺證書」,用來驗證微信支付應(yīng)答的簽名
$platformCertificateFilePath = 'file://../cert/wechatpay/wechatpay_4xxxxxxxxxxxxxxxxxxx9.pem';
$platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
// 從「微信支付平臺證書」中獲取「證書序列號」
$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
// 構(gòu)造一個 APIv3 客戶端實例
$instance = Builder::factory([
'mchid' => $merchantId,
'serial' => $merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerial => $platformPublicKeyInstance,
],
]);
try {
$resp = $instance
->chain('v3/pay/transactions/h5')
->post(['json' => [
'mchid' => $merchantId, // 商戶號
'out_trade_no' => $out_trade_no, // 商戶訂單號
'appid' => '********換成跟商戶號綁定的公眾號APPID**********',
'description' => $input['description'], //商品描述
'notify_url' => 'https://xxxxx/notify', // 用戶支付后的回調(diào)地址,在這里修改訂單的狀態(tài)
'amount' => [
'total' => $total, // 微信處理的單位是分
'currency' => 'CNY'
],
'scene_info' => [
'payer_client_ip' => getClientIP(), // 有些框架有自帶獲取獲取客戶端IP
'h5_info' => [
'type' => 'Wap'
]
]
]]);
// 如果請求成功,需要將一些參數(shù)進(jìn)行入庫,這里僅作演示,非正式數(shù)據(jù)入庫
$response = Db::table('order')->insert([
'name' => $input['name'],
'description' => $input['description'],
'total' => $input['total'],
'phone' => $input['phone'],
'trade_state' => 'START',
]);
// 入庫成功后,將跳轉(zhuǎn)鏈接和訂單號傳給前端,前端拿到跳轉(zhuǎn)地址跳轉(zhuǎn)即可
if($response) {
return jsonp([
'code' => 200,
'msg' => '操作成功',
'data' => [
'out_trade_no' => $out_trade_no,
'h5_url' => json_decode($resp->getBody(), true)['h5_url']
]
]);
} else {
return jsonp([
'code' => 100,
'msg' => '操作失敗'
]);
}
} catch (\Exception $e) {
// 進(jìn)行錯誤處理
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
}
}
// 生成唯一商戶訂單號,訂單號不能超過32位,并且在同一個商戶下訂單號不能重復(fù)
// 如果并發(fā)不高,基本這樣生成就可以,不會有重復(fù)的情況出現(xiàn)的
function getOutTradeNo()
{
$out_trade_no = date('ymdHis') . mt_rand(1000, 9999) . uniqid();
return mb_substr($out_trade_no, 0, 32);
}
// 獲取客戶端的IP
function getClientIP()
{
if (@$_SERVER["HTTP_ALI_CDN_REAL_IP"]) {
$ip = $_SERVER["HTTP_ALI_CDN_REAL_IP"];
} elseif (@$_SERVER["HTTP_X_FORWARDED_FOR"] ?: false) {
$ips = explode(',', $_SERVER["HTTP_X_FORWARDED_FOR"]);
$ip = $ips[0];
} elseif (@$_SERVER["HTTP_CDN_SRC_IP"] ?: false) {
$ip = $_SERVER["HTTP_CDN_SRC_IP"];
} elseif (getenv('HTTP_CLIENT_IP')) {
$ip = getenv('HTTP_CLIENT_IP');
} elseif (getenv('HTTP_X_FORWARDED')) {
$ip = getenv('HTTP_X_FORWARDED');
} elseif (getenv('HTTP_FORWARDED_FOR')) {
$ip = getenv('HTTP_FORWARDED_FOR');
} elseif (getenv('HTTP_FORWARDED')) {
$ip = getenv('HTTP_FORWARDED');
} else {
$ip = $_SERVER['REMOTE_ADDR'];
}
$ip = str_replace(['::ffff:', '[', ']'], ['', '', ''], $ip);
return $ip;
}
<?php
// 回調(diào)處理,當(dāng)用戶支付訂單后,微信會請求該接口,也就是上面在notify_url中填寫的接口
// 在這里我們可以修改訂單的狀態(tài)啥的
public function notify()
{
// 獲取參數(shù)
$inBody = file_get_contents('php://input');
// APIv3密鑰
$apiv3Key = 'xxxxxxxxxxxx';
// 轉(zhuǎn)換通知的JSON文本消息為PHP Array數(shù)組
$inBodyArray = (array)json_decode($inBody, true);
// 加密文本消息解密
$inBodyResource = AesGcm::decrypt(
$inBodyArray['resource']['ciphertext'],
$apiv3Key,
$inBodyArray['resource']['nonce'],
$inBodyArray['resource']['associated_data']
);
// 把解密后的文本轉(zhuǎn)換為PHP Array數(shù)組
$inBodyResourceArray = (array)json_decode($inBodyResource, true);
try {
// 獲取訂單信息
$order = Db::table('order')->where('out_trade_no', $inBodyResourceArray['out_trade_no'])->first();
Db::startTrans();
if ($order) {
// 修改order訂單的狀態(tài)
Db::table('order')->where('id', $order['id'])->update([
'openid' => $inBodyResourceArray['payer']['openid'],
'trade_state' => $inBodyResourceArray['trade_state']
]);
Db::table('payment')->insert([
'out_trade_no' => $inBodyResourceArray['out_trade_no'],
'transaction_id' => $inBodyResourceArray['transaction_id'],
'trade_type' => $inBodyResourceArray['trade_type'],
'trade_state' => $inBodyResourceArray['trade_state'],
'trade_state_desc' => $inBodyResourceArray['trade_state_desc'],
'total_amount' => $inBodyResourceArray['amount']['total'],
'bank_type' => $inBodyResourceArray['bank_type'],
'success_time' => strtotime($inBodyResourceArray['success_time'])
]);
Db::commit();
} else {
Db::rollback();
}
} catch (\Exception $e) {
Db::rollback();
}
}
?? 開通 JSAPI 支付
- 點擊
產(chǎn)品中心
?我的產(chǎn)品
?JSAPI支付
?點擊開通
- 開通后,選擇
開發(fā)配置
?JSAPI支付域名
申請?zhí)砑?JSAPI支付域名
- 關(guān)于申請支付域名的流程基本都差不多要求也差不多,看上面的
H5支付域名
申請就行,這里就不過多贅述了
文章來源地址http://www.zghlxwxcb.cn/news/detail-584277.html
?? JSAPI 支付流程
-
JSAPI
支付是在微信內(nèi)的瀏覽器使用的,如果用戶是在微信外打開的話,需要提醒去微信內(nèi)打開頁面 -
JSAPI
支付需要使用微信內(nèi)置的WeixinJSBridge.invoke
方法 - 由于
JSAPI
調(diào)用支付需要用到用戶的openid
,所以需要想方設(shè)法在用戶調(diào)用JSAPI
之前獲取到openid
,點擊查看獲取 openid 的官方文檔 - 獲取用戶
openid
,需要先獲取code
,這個經(jīng)常做微信業(yè)務(wù)的人都知道,那么如何在用戶無感知的情況下就獲取到openid
呢 - 思路就是,一般支付最少會有3個頁面,這里標(biāo)注為
a
、b
、c
三個頁面,通常是在a
頁面挑選商品,在b
頁面確認(rèn)商品,也就是付款頁面,c
頁面查詢支付狀態(tài) - 由于
code
的存在時間只有5分鐘,所以注定code
獲得后不能長時間不使用,也就是說用戶一旦在某個頁面超過5分鐘,這個code
就失效了,因此最好的方法就是獲取code
后,立馬獲取openid
- 那么就應(yīng)該設(shè)計成從
a
頁面先跳轉(zhuǎn)到獲取code
頁面再跳轉(zhuǎn)到b
頁面,而在b
頁面的一開始就去請求接口,獲取用戶的openid
即可 - 跳轉(zhuǎn)到
b
頁面后,鏈接后自動帶上code
參數(shù),鏈接應(yīng)該是https://xxxx/b.html?code=xxxxxxxx
// a頁面,僅做邏輯演示,更加具體的邏輯需要自己完善
// 判斷是否微信瀏覽器
function isWeChat() {
var ua = navigator.userAgent.toLowerCase();
if (ua.match(/MicroMessenger/i) == 'micromessenger') {
return true;
} else {
return false;
}
}
if(!isWeChat()) {
// 非微信內(nèi)打開的產(chǎn)品頁面
alert('微信外不支持JSAPI支付,請在微信中打開頁面');
return false;
}
// 用戶挑選完商品后跳轉(zhuǎn),這里appid需要上面跟商戶綁定的公眾號appid
// 微信授權(quán)分為靜默授權(quán)和非靜默授權(quán),其中非靜默授權(quán),需要用戶點擊確認(rèn)授權(quán)后,才可以獲取code,
// 因為這里主打一個用戶無感知,而且我們只需要openid即可,所以我們只需要使用靜默授權(quán)即可
// 靜默授權(quán)可以獲取用戶更多的信息,比如頭像、昵稱等,而靜默授權(quán)只能獲取openid,這點需要注意,具體情況選擇不同
// 非靜默授權(quán)
// $(location).attr('href', `https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxxxxxxxxxx&redirect_uri=${encodeURIComponent('https://xxxx/b.html')}&response_type=code&scope=snsapi_userinfo#wechat_redirect`)
// 靜默授權(quán)
$(location).attr('href', `https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxxxxxxxxxx&redirect_uri=${encodeURIComponent('https://xxxx/b.html')}&response_type=code&scope=snsapi_base#wechat_redirect`)
// b頁面,僅做邏輯演示,更加具體的邏輯需要自己完善
let openid = '';
// 獲取code, 請求接口獲取openid
function getParamUrl(name, url) {
if (!url) url = location.href;
if (url.indexOf('?') == -1) return '';
try {
var re = new RegExp("" + name + "=([^&?]*)", "ig");
return ((url.match(re)) ? (decodeURIComponent(url.match(re)[0].substr(name.length + 1))) : '');
} catch (_e) {
return '';
}
}
let code = getParamUrl('code');
$.getJSON('后端接口地址/openid?callback=?', function(res) {
if(res.code == 200) {
openid = res.data;
} else {
console.error(res.msg);
}
})
// 用戶確定訂單后,拉起支付
let params = {
total: 2, // 單位:元
description: 'Image形象店-深圳騰大-QQ公仔', // 產(chǎn)品的介紹
openid: openid //用戶的openid
// ....更多入庫參數(shù)
};
$.getJSON('后端接口地址/jssapi?' + $.param(params) + '&callback=?', function(res) {
WeixinJSBridge.invoke('getBrandWCPayRequest', {
'appId': res.data.sign.appId,
'timeStamp': res.data.sign.timeStamp,
'nonceStr': res.data.sign.nonceStr,
'package': res.data.sign.package,
'signType': res.data.sign.signType,
'paySign': res.data.sign.paySign
}, function (response) {
if (response.err_msg == "get_brand_wcpay_request:ok") {
$(location).attr("href", `https://xxxxxx/finish?out_trade_no=${res.data.out_trade_no}`)
} else {
// 有些用戶調(diào)起了支付,但是未付款取消的處理方式,你可以給他簡單簡單提示
toast('支付異常取消')
// 當(dāng)然有些用戶是誤操作,你可以提醒二次支付
if(confirm('檢測到你操作有誤,是否重新支付?')) {
WeixinJSBridge.invoke('getBrandWCPayRequest', {
'appId': res.data.sign.appId,
'timeStamp': res.data.sign.timeStamp,
'nonceStr': res.data.sign.nonceStr,
'package': res.data.sign.package,
'signType': res.data.sign.signType,
'paySign': res.data.sign.paySign
}, function (response) {
if (response.err_msg == "get_brand_wcpay_request:ok") {
$(location).attr("href", `https://xxxxxx/finish?out_trade_no=${res.data.out_trade_no}`)
}
})
}
}
});
});
<?php
// 獲取用戶的openid
$input = $request->only(['code']);
$response = getCurl("https://api.weixin.qq.com/sns/oauth2/access_token?appid={$this->appid}&secret={$this->secret}&code={$input['code']}&grant_type=authorization_code");
$openid = json_decode($response, true)['openid'];
// 返回openid
return jsonp([
'code' => 200,
'msg' => '獲取成功',
'data' => $openid
]);
// 封裝的GET請求
function getCurl($url, $timeout = 5)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
<?php
// 僅僅用作展示,不可直接復(fù)制使用
require_once('../vendor/autoload.php');
use WeChatPay\Builder;
use WeChatPay\Formatter;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Util\PemUtil;
// 接受參數(shù),相當(dāng)于原生的$_GET,這里會比h5支付多一個openid
$input = $request->only(['openid', 'name', 'total', 'description', 'phone']);
// 生成商戶訂單號
$out_trade_no = getOutTradeNo();
// 處理金額
// 由于微信使用的是分作為單位,所以前端傳的是元的話,需要轉(zhuǎn)換一下
$total = $input['total'] * 100;
// 商戶號
$merchantId = '1xxxxxx1';
// 從本地文件中加載「商戶API私鑰」,「商戶API私鑰」會用來生成請求的簽名
$merchantPrivateKeyFilePath = 'file://../cert/merchant/apiclient_key.pem';
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
// 「商戶API證書」的「證書序列號」
$merchantCertificateSerial = '1xxxxxxxxxxxxxxxxxxxxx91';
// 從本地文件中加載「微信支付平臺證書」,用來驗證微信支付應(yīng)答的簽名
$platformCertificateFilePath = 'file://../cert/wechatpay/wechatpay_4xxxxxxxxxxxxxxxxxxx9.pem';
$platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
// 從「微信支付平臺證書」中獲取「證書序列號」
$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
// 構(gòu)造一個 APIv3 客戶端實例
$instance = Builder::factory([
'mchid' => $merchantId,
'serial' => $merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerial => $platformPublicKeyInstance,
],
]);
try {
// 調(diào)用 transactions/jsapi 接口后會生成prepay_id
$resp = $this->instance()
->chain('v3/pay/transactions/jsapi')
->post(['json' => [
'mchid' => $merchantId, // 商戶號
'out_trade_no' => $out_trade_no, // 商戶訂單號
'appid' => '********換成跟商戶號綁定的公眾號APPID**********',
'description' => $input['description'], //商品描述
'notify_url' => 'https://xxxxx/notify', // 用戶支付后的回調(diào)地址,在這里修改訂單的狀態(tài)
'amount' => [
'total' => $total,
'currency' => 'CNY'
],
'payer' => [
'openid' => $input['openid']
]
]]);
// 需要根據(jù)prepay_id去生成加密的信息
$prepay_id = json_decode($resp->getBody(), true)['prepay_id'];
$sign = getSign($prepay_id);
// 如果請求成功,需要將一些參數(shù)進(jìn)行入庫,這里僅作演示,非正式數(shù)據(jù)入庫
$response = Db::table('order')->insert([
'openid' => $input['openid'],
'name' => $input['name'],
'description' => $input['description'],
'total' => $input['total'],
'phone' => $input['phone'],
'trade_state' => 'START',
]);
// 入庫成功后,將跳轉(zhuǎn)鏈接和訂單號傳給前端,前端拿到跳轉(zhuǎn)地址跳轉(zhuǎn)即可
if($response) {
return jsonp([
'code' => 200,
'msg' => '操作成功',
'data' => [
'out_trade_no' => $out_trade_no,
'sign' => $sign
]
]);
} else {
return jsonp([
'code' => 100,
'msg' => '操作失敗'
]);
}
} catch (\Exception $e) {
// 進(jìn)行錯誤處理
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
}
}
// 獲取加密參數(shù)
function getSign($prepay_id)
{
$merchantPrivateKeyInstance = Rsa::from($this->merchantPrivateKeyFilePath);
$params = [
'appId' => $this->appid,
'timeStamp' => (string)Formatter::timestamp(),
'nonceStr' => Formatter::nonce(),
'package' => 'prepay_id=' . $prepay_id,
];
$params += ['paySign' => Rsa::sign(
Formatter::joinedByLineFeed(...array_values($params)),
$merchantPrivateKeyInstance
), 'signType' => 'RSA'];
return $params;
}
?? 通用微信支付庫封裝
- 由于直接使用微信的支付庫,代碼非常的勻余,所以封裝了一個微信支付庫
- 由于只針對一些業(yè)務(wù)的
api
封裝,所以肯定不全,需要的可以自己添加需要的api
- 微信支付API接口列表: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/index.shtml
<?php
/**
* User: tinygeeker
* Desc: 微信支付庫封裝
* Date: 2023/08/10
*/
namespace App;
use App\Helper;
use WeChatPay\Builder;
use WeChatPay\Formatter;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Util\PemUtil;
class WxPay
{
// appid
private $appid;
// 商戶號
private $merchantId;
// 商戶API私鑰
private $merchantPrivateKeyFilePath;
// 證書序列號
private $merchantCertificateSerial;
// 微信支付平臺證書
private $platformCertificateFilePath;
/**
* @param $appid
* @param $merchantId
* @param $merchantCertificateSerial
*/
public function __construct($appid = '', $merchantId = '', $merchantCertificateSerial = '')
{
$this->appid = $appid ?: '換成自己的APPID';
$this->merchantId = $merchantId ?: '換成自己的商戶號';
$this->merchantCertificateSerial = $merchantCertificateSerial ?: '換成自己的證書序列號';
$this->merchantPrivateKeyFilePath = 'file:///common/cert/merchant/apiclient_key.pem'; // 換成自己的
$this->platformCertificateFilePath = 'file:///common/cert/wechatpay/wechatpay_xxx.pem'; // 換成自己的
}
/**
* @return \WeChatPay\BuilderChainable
*/
protected function instance()
{
$merchantPrivateKeyInstance = Rsa::from($this->merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
$platformPublicKeyInstance = Rsa::from($this->platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
$platformCertificateSerial = PemUtil::parseCertificateSerialNo($this->platformCertificateFilePath);
$instance = Builder::factory([
'mchid' => $this->merchantId,
'serial' => $this->merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerial => $platformPublicKeyInstance,
],
]);
return $instance;
}
public function getSign($prepay_id)
{
$merchantPrivateKeyInstance = Rsa::from($this->merchantPrivateKeyFilePath);
$params = [
'appId' => $this->appid,
'timeStamp' => (string)Formatter::timestamp(),
'nonceStr' => Formatter::nonce(),
'package' => 'prepay_id=' . $prepay_id,
];
$params += ['paySign' => Rsa::sign(
Formatter::joinedByLineFeed(...array_values($params)),
$merchantPrivateKeyInstance
), 'signType' => 'RSA'];
return $params;
}
public function checkOutTradeNo($out_trade_no)
{
try {
$resp = $this->instance()
->v3->pay->transactions->outTradeNo->_out_trade_no_
->get([
// Query 參數(shù)
'query' => ['mchid' => $this->merchantId],
// 變量名 => 變量值
'out_trade_no' => $out_trade_no,
]);
return $resp->getBody();
} catch (\Exception $e) {
// 進(jìn)行錯誤處理
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;
}
}
// h5下單
public function h5($total, $out_trade_no, $description, $notify_url)
{
try {
$resp = $this->instance()
->chain('v3/pay/transactions/h5')
->post(['json' => [
'mchid' => $this->merchantId,
'out_trade_no' => $out_trade_no,
'appid' => $this->appid,
'description' => $description,
'notify_url' => $notify_url,
'amount' => [
'total' => $total,
'currency' => 'CNY'
],
'scene_info' => [
'payer_client_ip' => Helper::getClientIp(),
'h5_info' => [
'type' => 'Wap'
]
]
]]);
return $resp->getBody();
} catch (\Exception $e) {
// 進(jìn)行錯誤處理
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;
}
}
// jsapi下單
public function jsapi($openid, $total, $out_trade_no, $description, $notify_url)
{
try {
$resp = $this->instance()
->chain('v3/pay/transactions/jsapi')
->post(['json' => [
'mchid' => $this->merchantId,
'out_trade_no' => $out_trade_no,
'appid' => $this->appid,
'description' => $description,
'notify_url' => $notify_url,
'amount' => [
'total' => $total,
'currency' => 'CNY'
],
'payer' => [
'openid' => $openid
]
]]);
return $resp->getBody();
} catch (\Exception $e) {
// 進(jìn)行錯誤處理
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;
}
}
// todo... 更多接口可根據(jù)官方文檔列表自行添加
}
<?php
/**
* User: tinygeeker
* Desc: 工具庫
* Date: 2023/08/10
*/
namespace App;
class Helper
{
/**
* @return array|mixed|string|string[]
*/
static public function getClientIP()
{
if (@$_SERVER["HTTP_ALI_CDN_REAL_IP"]) {
$ip = $_SERVER["HTTP_ALI_CDN_REAL_IP"];
} elseif (@$_SERVER["HTTP_X_FORWARDED_FOR"] ?: false) {
$ips = explode(',', $_SERVER["HTTP_X_FORWARDED_FOR"]);
$ip = $ips[0];
} elseif (@$_SERVER["HTTP_CDN_SRC_IP"] ?: false) {
$ip = $_SERVER["HTTP_CDN_SRC_IP"];
} elseif (getenv('HTTP_CLIENT_IP')) {
$ip = getenv('HTTP_CLIENT_IP');
} elseif (getenv('HTTP_X_FORWARDED')) {
$ip = getenv('HTTP_X_FORWARDED');
} elseif (getenv('HTTP_FORWARDED_FOR')) {
$ip = getenv('HTTP_FORWARDED_FOR');
} elseif (getenv('HTTP_FORWARDED')) {
$ip = getenv('HTTP_FORWARDED');
} else {
$ip = $_SERVER['REMOTE_ADDR'];
}
$ip = str_replace(['::ffff:', '[', ']'], ['', '', ''], $ip);
return $ip;
}
/**
* @param $length
* @param $type
* @return false|string
*/
static public function createRandomStr($length = 32, $type = 0)
{
switch ($type) {
case 1:
$chars = '0123456789';
break;
case 2:
$chars = 'abcdefghijklmnopqrstuvwxyz';
break;
case 3:
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
break;
case 4:
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
break;
case 5:
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
break;
default:
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
break;
}
return substr(str_shuffle($chars), 0, $length);
}
/**
* @param $url
* @param $timeout
* @return bool|string
*/
static public function getCurl($url, $timeout = 5)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
/**
* @param $url
* @param $data
* @param $header
* @param $timeout
* @return bool|string
*/
static public function postCurl($url, $data, $header = [], $timeout = 5)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
if ($header) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
}
到了這里,關(guān)于php 開發(fā)微信 h5 支付 APIv3 接入超詳細(xì)流程的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!