1、概述
支付功能是软件服务的关键一环,需要用户、支付应用、服务商和支付平台共同参与完成。为此,支付平台开放基础支付API,并提供常用开发语言SDK供开发者使用;支付服务商和软件开发者基于API和SDK根据业务场景开发具体支付业务。但是,从基础支付API、SDK到最终实现支付的过程比较复杂,需要一套简洁的实现框架和更高层的工具。本文以微信支付V3为基础,设计一套前后端分离支付框架,并提供开源实现(奇辰Open-API),做到支付功能开箱即用。
1.1支付服务参与者
- 支付平台:微信支付、支付宝等第三方平台;
- 服务商:具备基于支付平台基础功能提供支付服务的商家;
- 应用:由服务商提供或者自研的具备支付功能应用,包括H5、小程序、APP等;
- 用户:使用支付应用功能的人。
本文档主要面向服务商或者具备自研能力的应用开发者。
1.2难点关键技术
- 通用支付流程架构:管理员支付配置,用户从前端使用支付功能,后端闭环支付业务流程。
- 开箱即用API:通过简单的API调用即可完成业务所需支付功能。
2、通用支付架构
设计前后端分离通用支付架构如下图所示:
基于前后端分离开源奇辰Open-API框架的后端支付服务实现后端支付业务,由开源框架后端服务完成和第三方微信支付平台的调用和回调工作,开发者不必再关注这方面的工作,可以把重心放在支付业务侧; 基于开源前端基础框架实现所需的支付应用和支付后台。在这个支付架构下,管理员通过支付台调用后端支付业务进行支付设置;用户支付应用端使用支付功能。
3、微信支付V3开源PHP实现框架
3.1支付设置
微信支付V3所有接口功能基于证书进行实现,有两类证书需要加以区分:商户API证书是每个微信商户自身的证书,另一类是微信支付平台证书;关于两类证书详情请参考微信支付官方文档。在 开源奇辰Open-API框架已经对证书管理进行了封装,开发者不再需要进行证书相关业务的开发。
- 先设置微信支付商户号、商户密钥,上传商户API证书三个文件;
- 完成前面设置,可以直接在后台获取微信支付平台证书。每个商户对应的微信支付平台证书不同,开源框架已经封装了微信支付平台证书管理,开发者不再需要关注此项功能开发。
3.2支付调用
管理员完成微信支付设置后,前端应用可以调用封装好的接口发起支付。
- 调用接口
调用方式:HTTP、HTTPS
api: pay/wx/pay
HTTP请求方法:POST
- 接口实现
后端封装的接口实现如下:
public function pay(Request $request)
{
$instance = Init::getInstance();
$conf = ConfModel::first();
$resp = $instance
->v3->pay->transactions->jsapi
->post(['json' => [
'mchid' => $conf->mchid,
'out_trade_no' => Sn::build_order_no(),
'appid' => $conf->miniapp_appid,
'description' => '奇辰Open-API微信支付',
'notify_url' => env('HOST_URL') . '/pay/wx/notify',
'amount' => [
'total' => 1,
'currency' => 'CNY'
],
'payer' => [
'openid' => $request->input('openid')
]
]]);
$jsapi_result = json_decode($resp->getBody()->getContents());
// 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
$merchantPrivateKeyFilePath = 'file://' . env('ATTACHMENT_ROOT') . $conf->key_file;
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
$params = [
'appId' => $conf->miniapp_appid,
'timeStamp' => (string)Formatter::timestamp(),
'nonceStr' => Formatter::nonce(),
'package' => 'prepay_id=' . $jsapi_result->prepay_id,
];
$params += ['paySign' => Rsa::sign(
Formatter::joinedByLineFeed(...array_values($params)),
$merchantPrivateKeyInstance
), 'signType' => 'RSA']; $res = ResultTool::success();
$res['data']['params'] = $params;
return $res;
}
代码第3行调用微信支付初始化函数获取微信支付实例,第12行设置支付回调接口,第18行设置接口参数传入的微信openid。接口返回前端调起支付所需的js参数,如下:
pay() {
let user_info = uni.getStorageSync('user_info')
wxPay({
openid: user_info.openid
}).then(res => {
if (res.errorCode == 200) {
let params = res.data
wx.requestPayment({
timeStamp: params.timeStamp,
nonceStr: params.nonceStr,
package: params.package,
signType: params.signType,
paySign: params.paySign,
success(res) {
console.log(res)
},
fail(res) {
console.log(res)
}
})
}
})
}
前端wxPay函数在第9-13行获取后端返回的支付参数params,前端调用wx.requrestPayment函数调起支付如下图所示:
3.3支付回调
支付完成后,前面支付设置的回调接口能收到相关支付信息。
- 回调接口
调用方式:HTTP、HTTPS
api: pay/wx/notify
HTTP请求方法:POST
- 接口实现
后端封装的接口实现如下:
public function callback(Request $request)
{
$inWechatpaySignature = $request->header('Wechatpay-Signature'); // 请根据实际情况获取
$inWechatpayTimestamp = $request->header('Wechatpay-Timestamp'); // 请根据实际情况获取
$inWechatpaySerial = $request->header('Wechatpay-Serial'); // 请根据实际情况获取
$inWechatpayNonce = $request->header('Wechatpay-Nonce'); // 请根据实际情况获取
$inBody = file_get_contents('php://input'); // 请根据实际情况获取,例如: file_get_contents('php://input'); $conf = ConfModel::first();
$apiv3Key = $conf->signkey; // 在商户平台上设置的APIv3密钥 // 根据通知的平台证书序列号,查询本地平台证书文件,
// 假定为 `/path/to/wechatpay/inWechatpaySerial.pem`
$platformPublicKeyInstance = Rsa::from('file://' . env('ATTACHMENT_ROOT') . 'wxpay/' . 'wechatpay_' . $inWechatpaySerial . '.pem', Rsa::KEY_TYPE_PUBLIC); // 检查通知时间偏移量,允许5分钟之内的偏移
$timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
$verifiedStatus = Rsa::verify(
// 构造验签名串
Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
$inWechatpaySignature,
$platformPublicKeyInstance
);
if ($timeOffsetStatus && $verifiedStatus) {
// 转换通知的JSON文本消息为PHP Array数组
$inBodyArray = (array)json_decode($inBody, true);
// 使用PHP7的数据解构语法,从Array中解构并赋值变量
['resource' => [
'ciphertext' => $ciphertext,
'nonce' => $nonce,
'associated_data' => $aad
]] = $inBodyArray;
// 加密文本消息解密
$inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);
// 把解密后的文本转换为PHP Array数组
$inBodyResourceArray = (array)json_decode($inBodyResource, true);
// print_r($inBodyResourceArray);// 打印解密后的结果
} echo 'success';
}
在回调接口的第36行$inBodyResourceArray里面可以开发业务所需要的支付回调功能。
4、更多
开源项目:Open-Api
更多信息:www.lokei.cn
发表评论 取消回复