Laravel实现微信小程序支付
索引
[hide]
准备工作
微信小程序支付流程
首先我们需要大致了解微信小程序的支付的流程,主要分为如下几个步骤。这里以一次下单购买商品的场景为例。
- 用户在小程序中选购商品,点击提交订单。
- 小程序请求业务服务器,传入订单相关信息。
- 业务服务器将订单信息写入数据库,然后请求微信支付统一下单API,传入将订单相关数据和小程序相关id及签名。
- 微信支付统一下单API返回下单结果,支付订单号,签名等。业务服务器将这些信息返回给小程序。
- 小程序拉起微信支付,用户输入密码支付。
- 小程序获得支付结果,请求业务服务器更新订单数据。
- (可选)微信支付服务器访问业务服务器的回调URL,更新支付结果。
阅读文档
在开始开发前,请首先认真阅读微信支付官方文档及EasyWechat文档的微信支付部分。
配置EasyWechat
EasyWechat组件封装了包括微信支付在内的微信相关API的大部分功能。请参考EasyWechat文档进行安装,如果使用Laravel,请参考这篇文档。安装完成后请参考微信支付入门文档配置相关信息。
请注意
虽然微信支付提供了沙箱模式以方便开发者调试。然而,要使用沙箱模式,必须首先在微信支付商户后台配置测试样例,并且测试过程中的支付数值等需要符合测试样例。除此之外,沙箱模式还有各种文档中没有提到的坑,例如使用的key和正常情况下不同等等。因此建议放弃沙箱模式,直接使用一分钱进行调试。
订单数据表设计
下面展示了与支付相关的订单表所需数据:
字段名 | 类型 | 注释 |
id | 自增(主键) | 主键 |
order_sn | string | 商户订单号(下文有详细说明) |
user_id | integer(外键) | 用户id |
price | float | 订单价格 |
prepay_id | string | 微信支付预付单号(下文有详细说明) |
status | integer | 订单状态(待付款;已付款;取消;) |
开始支付
此时我们假设微信小程序已携带订单相关数据请求业务服务器下单接口,且业务服务器已经将订单数据写入数据库,准备开始支付。
微信支付统一下单
生成商户订单号
商户订单号是商户系统中用于标识一个订单的唯一编号。要求32个字符内,只能包含数字、大小写字母及_-|*。我使用的商户订单号格式为当前时间(精确到毫秒)加上6位随机数。代码如下:
public function generateSn(){ $date = Carbon::now('Asia/Shanghai'); $rnd = rand(100000,999999); return $date->format('YmdHisu').$rnd; }
调用统一下单接口
如下面示例代码所示,由业务服务器调用由EasyWechat封装好的统一下单接口
$result = $this->app->order->unify([ 'appid' => env('MINIAPPID'), 'body' => $title,//eg.'腾讯充值中心-QQ会员充值' 'out_trade_no' => $order_sn,//'20150806125346', 'total_fee' => $amount,//88, 'trade_type' => 'JSAPI', 'openid' => $openid,//'oUpF8uMuAJO_M2pxb1Q9zNjWeS6o', //'spbill_create_ip' => '123.12.12.123', // 可选,如不传该参数,SDK 将会自动获取相应 IP 地址 'notify_url' => 'https://api.example.com/v1/wechat/pay_callback', // 支付结果通知网址,如果不设置则会使用配置里的默认地址 ]);
参数说明如下:
appid
:微信小程序的appid,示例代码中的appid从env中读取。body
:付款项目名称,例如“腾讯充值中心-QQ会员充值”。out_trade_no
:上一步骤中生成的商户系统订单号。total_fee
:付款金额,单位为分。trade_type
:写JSAPI即可。openid
:当前微信用户的openid。notify_url
:支付结果异步通知回调URL,如果不设置则会使用配置里的默认地址。微信服务器会携带支付结果访问此URL,主要用于在交易无法实时完成的情况下使业务服务器能够获取支付结果。
如果请求成功(不一定代表下单成功),返回结果如下:
{ "xml": { "return_code": "SUCCESS", "return_msg": "OK", "appid": "wx2421b1c4390ec4sb", "mch_id": "10000100", "nonce_str": "IITRi8Iabbblz1J", "openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeSs6o", "sign": "7921E432F65EB8ED0CE9755F0E86D72F2", "result_code": "SUCCESS", "prepay_id": "wx201411102639507cbf6ffd8b0779950874", "trade_type": "JSAPI" } }
字段说明如下:
appid
:微信小程序的appid。mch_id
:微信支付商户id。nonce_str
:微信返回的随机字符串,后面会用到。openid
:当前微信用户的openid。sign
:签名值。这个签名应该是EasyWechat生成的,我没有使用。result_code
:下单结果,值为SUCCESS
或FAIL
。trade_type
:交易类型,值为JSAPI
。prepay_id
:微信生成的预支付会话标识,用于后续接口调用中使用,该值有效期为2小时。可以将其理解为微信支付订单号。
处理下单返回数据
通过调用统一下单接口,我们获得了上文所述数据。在将相关数据返回给小程序之前,我们需要做一些处理。
生成UNIX时间戳
$timestamp = (string)time();
生成签名(paySign)
生成签名所需的数据如下:
appid
:微信小程序appidnonce_str
:上文所述下单接口返回的随机字符串package
:将prepay_id=
与prepay_id拼接而成的字符串signType
:签名算法,通常为MD5timeStamp
:上文所述的UNIX时间戳key
:微信支付商户key
将上述数据的字段名及值参考下面方式拼接,并计算MD5,即可得到签名。下面为php代码:
$paySign = md5("appId=".$appid."&nonceStr=".$nonce_str."&package=prepay_id=".$prepay_id."&signType=MD5&timeStamp=".$timeStamp."&key=".$WXPAYMCHKEY);
完成后将下单接口的返回结果连同时间戳及签名返回给小程序。
小程序拉起支付
wx.requestPayment({ timeStamp: result.timestamp, nonceStr: result.order_data.nonce_str, package: "prepay_id=" + result.order_data.prepay_id, signType: 'MD5', paySign: result.pay_sign, success: function (res) { wx.showToast({ title: "支付成功!", duration: 800, }); page.updateOrderStatus(); }, fail: function (res) { wx.showToast({ title: '错误', duration: 3000 }) } })
wx.requestPayment()
函数的主要参数如下:
timeStamp
:上文所述的UNIX时间戳nonceStr
:统一下单接口返回的随机字符串package
:”prepay_id=” 和prepay_id拼接而成的字符串signType
: ‘MD5’paySign
: 上文所述的paySign签名
调用wx.requestPayment()
后,微信小程序会弹出支付界面,用户完成支付后即可在success
或fail
回调内接收支付结果
完成支付
至此支付完成,小程序及业务系统可根据支付结果继续执行相应的业务逻辑。