一、官方流程图
小程序支付的交互图如下:
二、小程序调用登录接口获取code,传递给商户服务器获取用户的openID
1.小程序调用wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
2.开发者服务器以code换取 用户唯一标识openid 和 会话密钥session_key。
小程序端:
getToken: function () {
//调用登录接口
wx.login({
success: function (res) {
var code = res.code;
wx.request({
url: 商户服务器接口地址,
data: {
code: code
},
method: 'POST',
success: function (res) {
wx.setStorageSync('token', res.data.token); //存在小程序缓存中
},
fail: function (res) {
console.log(res.data);
}
})
}
})
}
服务端:
我们通过小程序提交的code,和小程序的APPID以及APPSECRET和拼接下列的url,并用curl进行get请求。
https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
返回的数据是一个json对象,我门通过使用json_decode(JSON,true)解析为数组,数据包括用户的openID以及session_key,获取到了后我们应该将openID存入数据库中,它代表着用户的身份,那么令牌应该怎么生成呢。
三、token的生成以及缓存
我们根据一个用户表将id和openid联系起来,对应openID的id则是用户的uid,我们可以这么封装
//要缓存的数据数组
$cacheValue = $result; //包含openID和session_key
$cacheValue['uid'] =$uid; //用户id
$cacheValue['scope'] =ScopeEnum::User; //用户权限级别
缓存的方式我们可以选择redis,memcache, 文件缓存等等,采用键值对(key-value)的方式进行存储,记得设置好过期时间。这里的key我们用token来赋值,token可以通过这样的方式进行生成:
//获取32位随机字符串
$str = getRandChar(32); //自定义方法生成32位随机串
//三组字符串进行md5加密
$timeStamp =$_SERVER['REQUEST_TIME_FLOAT'];
//salt
$salt = config('secure.token_salt'); //随机字符串
//返回token
return md5($str.$timeStamp.$salt);
这种算法基本保障了token的唯一性。因为值是我们获取到的openID和session_key所在的数组,所以需要将数组转成json才能存进去。以后的代码当我们需要openID或者uid等时可以直接通过取缓存的方式来取。
四、调用统一下单接口,获取prepay_id,再次签名
4.1 下载微信JS-SDK
(https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1)
解压打开进入lib文件夹中。
我们需要将lib中的文件放到我们的框架中,例如我使用的是tp5,就放到extend下,最好是在extend下建个子文件夹。其中WxPay.Api.php是入口,WxPay.Config.php是配置文件。下好后需要改动一些地方。在WxPay.Config.php中修改下列的东西改成你的。
然后在WxPay.Api.php中require一下WxPay.Notify.php。
在某个控制器或者服务层的代码先是用Loader::import()引入WxPay.Api.php,相当于五个都引入了。
4.2 调用统一下单api
在我们引入了上面那个文件后,先实例化这个类WxPayUnifiedOrder,把需要的参数通过调用对应的方法传入。
//调用微信支付统一下单接口
$wxOrderData = new \WxPayUnifiedOrder();
//设置相关参数
$wxOrderData->SetOut_trade_no($this->orderNO);
$wxOrderData->SetTrade_type('JSAPI');
$wxOrderData->SetTotal_fee($totalPrice * 100); //这里的价格单位是分
$wxOrderData->SetBody('Mc');
$wxOrderData->SetOpenid($openid);
$wxOrderData->SetNotify_url(config('secure.pay_back_url'));//支付回调
参数设置好了之后,就直接调用SDK的方法了
$wxOrder = \WxPayApi::unifiedOrder($wxOrderData);
4.3 再次签名
// 提交JSAPI输入对象
$jsApiPayData = new \WxPayJsApiPay();
//设置appid
$jsApiPayData->SetAppid(config('wx.app_id'));
//timeStamp
$jsApiPayData->SetTimeStamp((string)time());
//随机串
$randStr = md5(time().mt_rand(0,1000));
$jsApiPayData->SetNonceStr($randStr);
//数据报
$jsApiPayData->SetPackage('prepay_id='.$wxOrder['prepay_id']);
//类型
$jsApiPayData->SetSignType('MD5');
//生成签名
$sign = $jsApiPayData->MakeSign();
//获得签名数组
$signData = $jsApiPayData->GetValues();
//增加字段paySign
$signData['paySign']=$sign;
//删除signData中的app_Id字段
unset($signData['appId']);
return $signData;
再次签名完成后,就把五个参数返回给小程序。
五、小程序获取五个参数后,鉴权调起支付
小程序端:
pay: function () {
var token = wx.getStorageSync('token');
var that = this;
wx.request({
url: baseUrl + '/order',
header: {
token: token
},
data: { //产品的数据
products:
[
{
product_id: 1, count: 1
},
{
product_id: 2, count: 1
}
]
},
method: 'POST',
success: function (res) {
console.log(res.data);
if (res.data.pass) {
wx.setStorageSync('order_id', res.data.order_id);
that.getPreOrder(token, res.data.order_id); //调用getPreOrder
}
else {
console.log('订单未创建成功');
}
}
})
},
getPreOrder: function (token, orderID) {
if (token) {
wx.request({
url: baseUrl + '/pay/pre_order',
method: 'POST',
header: {
token: token
},
data: {
id: orderID
},
success: function (res) {
var preData = res.data;
console.log(preData);
wx.requestPayment({ //请求支付
timeStamp: preData.timeStamp.toString(),
nonceStr: preData.nonceStr,
package: preData.package,
signType: preData.signType,
paySign: preData.paySign,
success: function (res) {
console.log(res.data);
},
fail: function (error) {
console.log(error);
}
})
}
})
}
},
六、支付回调
我们需要重写WxPayNotify类的NotifyProcess方法,这里记得Loader::impor()引入那个入口类。
/**
*
* 回调方法入口,子类可重写该方法
* 注意:
* 1、微信回调超时时间为2s,建议用户使用异步处理流程,确认成功之后立刻回复微信服务器
* 2、微信服务器在调用失败或者接到回包为非确认包的时候,会发起重试,需确保你的回调是可以重入
* @param array $data 回调解释出的参数
* @param string $msg 如果回调处理失败,可以将错误信息输出到该方法
* @return true 回调出来完成不需要继续回调,false回调处理未完成需要继续回调
*/
public function NotifyProcess($data, &$msg)
{
//TODO 用户基础该类之后需要重写该方法,成功的时候返回true,失败返回false
return true;
}