thinkphp开发小程序之小程序发起微信支付

最近在学一套小程序商城,最近做到了小程序支付环节,分享一下我的心得。

首先,你需要有认证的小程序,并且已开通微信支付,我的是服务号,并且早已申请号了微信支付,现在开通小程序,直接申请绑定即可。

首先我们去下载微信支付SDK,微信只有一套支付用的SDK,集成了扫码,公众号等。

下载链接:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1

下载后我们在thinkphp5的根目录下面的extend下面建立wxpay文件夹,并且将下载好的sdk解压,将里面的核心文件lib下的所有文件复制到WxPay里。

然后在WxPay.Config.php文件里填入支付商户等信息,在这里要注意的是,里面的APPID是小程序的APPID,由于我是直接从我先前公众号支付的代码复制过来的,所以在测试出现appid and openid not match 报错返回信息。

接下来就是导入到thinkphp5里,因为官方SDK没有使用命名空间,所以我们使用tp5自导的Loader类来引入。

在使用的业务控制器中使用以下代码引入WxPay.Api.php:

我们看一下他require_once哪些文件:

require_once "WxPay.Exception.php";
require_once "WxPay.Config.php";
require_once "WxPay.Data.php";

可见,主要文件都已经被引入。所以我们就不需要引入其他文件了

Loader::import,使用前记得use think\Loader;

三个参数,第一个WxPay.WxPay为extend下面的WxPay文件夹+类名的第一个'.'之前的文件名

第二个参数,我们填写常量EXTEND_PATH,表明extend文件夹

第三个参数,就是类名,第一个'.'之后的后缀名称。

Loader::import('WxPay.WxPay',EXTEND_PATH,'.Api.php');
这样已经引入了。
接下来我们第一步,我的一个方法里面的代码:

    private function makeWxPreOrder($totalPrice)
    {
        //传递过来的参数为订单商品总价格
        /**
         *
         * 统一下单,WxPayUnifiedOrder中out_trade_no、body、total_fee、trade_type必填
         * appid、mchid、spbill_create_ip、nonce_str不需要填入
         * @param WxPayUnifiedOrder $inputObj
         * @param int $timeOut
         * @throws WxPayException
         * @return 成功时返回,其他抛异常
         */
        $wxOrderData = new \WxPayUnifiedOrder();
        //唯一订单号
        $wxOrderData->SetOut_trade_no($orderNo);
        //代表JSAPI模式,不要修改,公众号支付,H5,小程序都是这个
        $wxOrderData->SetTrade_type('JSAPI');
        //价格,单位为分
        $wxOrderData->SetTotal_fee($totalPrice * 100);
        //商品简介
        $wxOrderData->SetBody('零食商贩');
        //使用小程序用户的openid
        $wxOrderData->SetOpenid($openid);
        //异步回调验证路径,开发者自定义
        $wxOrderData->SetNotify_url('http://www.xxx.com/pay/notify');
        //这个是我又封装的一个生成签名的方法
        return $this->getPaySignature($wxOrderData);
    }

下面看生成签名的方法,就是上面说的封装生成 预支付信息

private function getPaySignature($wxOrderData)
    {
        $wxOrder = \WxPayApi::unifiedOrder($wxOrderData);
        // 失败时不会返回result_code
        if($wxOrder['return_code'] != 'SUCCESS' || $wxOrder['result_code'] !='SUCCESS'){
            Log::record($wxOrder,'error');
            Log::record('获取预支付订单失败','error');//这里为记录异常,根据自己业务选择处理
        }
        return $wxOrder;
    }

一切顺利,微信服务器返回代码如下:前两个信息我隐藏了,这是微信返回的预支付信息

appid: "XXXXXXXXXX"
mch_id: "XXXXXXX"
nonce_str: "6D1bLcdvtnCLaCCh"
prepay_id: "wx20171221124029ff0a820eff0678005613"
result_code: "SUCCESS"
return_code: "SUCCESS"
return_msg: "OK"
sign: "917038D942BEDEF0B8754D0828E52C0F"
trade_type: "JSAPI"

但是getPaySignature这个方法是不完整的,因为并未得到小程序所要拉起支付所需结果,我们来看一下小程序拉起支付所需,图片展示



所以我们需要对以上数据进行签名等处理,修改后的两个方法如下:

    private function getPaySignature($wxOrderData)
    {
        $wxOrder = \WxPayApi::unifiedOrder($wxOrderData);
        // 失败时不会返回result_code
        if($wxOrder['return_code'] != 'SUCCESS' || $wxOrder['result_code'] !='SUCCESS'){
            Log::record($wxOrder,'error');  //修改成自己的异常处理
            Log::record('获取预支付订单失败','error');
        }
        $signature $this->sign($wxOrder);
        return $signature;
    }

    //按照文档要求生成签名,传递给小程序,让小程序拉起支付
    private function sign($wxOrder)
    {
        $jsApiPayData = new \WxPayJsApiPay();
        //传入小程序appid
        $jsApiPayData->SetAppid(\WxPayConfig::APPID);
        //按照文档,要求是字符串类型
        $jsApiPayData->SetTimeStamp((string)time());
        //生成随机字符串
        $rand = md5(time() . mt_rand(0, 1000));
        $jsApiPayData->SetNonceStr($rand);
        //拼接prepay_id,要注意拼接
        $jsApiPayData->SetPackage('prepay_id=' . $wxOrder['prepay_id']);
        //签名方式md5
        $jsApiPayData->SetSignType('md5');
        //然后调用sdk自带的MakeSign方法生成签名
        $sign = $jsApiPayData->MakeSign();
        //然后在使用sdk自带方法获取到上面的我们赋值到成员属性生成的数组
        $rawValues = $jsApiPayData->GetValues();
        //然后我们在数组上加上生成的签名
        $rawValues['paySign'] = $sign;
        //删除appid,因为返回给客户端没有用,所以消除
        unset($rawValues['appId']);
        //返回
        return $rawValues;
    }


至此,返回结果如下,正好小程序wx.requestPayment(OBJECT)拉起支付所需参数:

nonceStr: "e323026d6254dd19f15561450fdecfb6"
package: "prepay_id=wx20171221143203d3e4d388b40964188996"
paySign: "39D08C08CEFCE485928927A812DD74BB"
signType: "md5"
timeStamp: "1513837924"

小程序中代码如下:

  //获取token值
  getToken:function(){
      wx.login({
      success: function (res) {
        if (res.code) {
          var code = res.code
          wx.request({
            url: 'http://www.xcx.com/api/v1/token/user?XDEBUG_SESSION_START=16697',
            data:{
              code:code
            },
            method:'POST',
            success: function (res) {
              console.log(res.data),
                wx.setStorageSync('super_token', res.data.token)
            }
          })
        } else {
          console.log('获取用户登录态失败!' + res.errMsg)
        }
      }
    });
  },
  //测试订单信息
  subOrder: function () {
    var super_token = wx.getStorageSync('super_token');
    var that = this;
    if (!super_token){
      console.log('获取缓存token失败,请检查')
    };
    wx.request({
      url: baseUrl + '/order',
      header:{
        token:super_token
      },
      data:{
        products:[
          {
            "product_id": 1,
            "count": 1
          },
          {
            "product_id": 3,
            "count": 2
          }
        ]
      },
      method:'POST',
      success:function(res){
        console.log(res.data);
        if(res.data.pass){
          wx.setStorageSync('order_id', res.data.order_id);
          that.getPreOrder(super_token,res.data.order_id);
        }else{
          console.log('订单创建失败');
        }
      }
    })
  },
  //订单发起支付
  getPreOrder:function(token,orderID){
    if(token){
      wx.request({
        url: baseUrl + '/pay/pre_order?XDEBUG_SESSION_START=11697',
        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.errMsg);
            },
            'fail': function (error) {
              console.log(error);
            }
          })
        }
      })
    }
  }

业务控制器完整代码我贴出来

<?php
/**
 * Created by YuanPan.
 * User: YuanPan
 * Date: 2017/12/20
 * Time: 16:34
 */

namespace app\api\service;


use app\lib\enum\OrderStatusEnum;
use app\lib\exception\OrderException;
use app\lib\exception\TokenException;
use think\Exception;
use app\api\service\Order as OrderService;
use app\api\model\Order as OrderModel;
use think\Loader;
use think\Log;

Loader::import('WxPay.WxPay',EXTEND_PATH,'.Api.php');

//订单业务
class Pay
{
    private $orderID;
    private $orderNo;
    //构造方法接受order的id
    public function __construct($orderID)
    {
        //不允许传递空
        if(!$orderID){
            throw new Exception('订单号不允许为空');
        }
        //赋值成员属性
        $this->orderID = $orderID;
    }

    public function pay()
    {
        //再一次对库存量检测
        //订单号可能不存在
        //订单号和当前用户不匹配
        //订单号已经被支付过了
        $this->checkOrderValid();
        $orderservice = new OrderService();
        $status = $orderservice->checkOrderStock($this->orderID);
        if(!$status['pass']){
            return $status;
        }
        return $this->makeWxPreOrder($status['orderPrice']);
    }

    private function makeWxPreOrder($totalPrice)
    {
        $openid = Token::getCurrentTokenVar('openid');
        if(!$openid){
            throw new TokenException();
        }
        /**
         *
         * 统一下单,WxPayUnifiedOrder中out_trade_no、body、total_fee、trade_type必填
         * appid、mchid、spbill_create_ip、nonce_str不需要填入
         * @param WxPayUnifiedOrder $inputObj
         * @param int $timeOut
         * @throws WxPayException
         * @return 成功时返回,其他抛异常
         */
        $wxOrderData = new \WxPayUnifiedOrder();
        //唯一订单号
        $wxOrderData->SetOut_trade_no($this->orderNo);
        //代表JSAPI模式,不要修改,公众号支付,H5,小程序都是这个
        $wxOrderData->SetTrade_type('JSAPI');
        //价格,单位为分
        $wxOrderData->SetTotal_fee($totalPrice * 100);
        //商品简介
        $wxOrderData->SetBody('零食商贩');
        //使用小程序用户的openid
        $wxOrderData->SetOpenid($openid);
        //异步回调验证路径
        $wxOrderData->SetNotify_url(config('secure.pay_back_url'));
        //这个是我又封装的一个生成签名的方法
        return $this->getPaySignature($wxOrderData);
    }

    private function getPaySignature($wxOrderData)
    {
        $wxOrder = \WxPayApi::unifiedOrder($wxOrderData);
        // 失败时不会返回result_code
        if($wxOrder['return_code'] != 'SUCCESS' || $wxOrder['result_code'] !='SUCCESS'){
            Log::record($wxOrder,'error');
            Log::record('获取预支付订单失败','error');
//            throw new Exception('获取预支付订单失败');
        }
        //将prepay_id存入对应订单数据库
        $this->recordPreOrder($wxOrder);
        $signature =  $this->sign($wxOrder);
        return $signature;
    }

    //按照文档要求生成签名,传递给小程序,让小程序拉起支付
    private function sign($wxOrder)
    {
        $jsApiPayData = new \WxPayJsApiPay();
        //传入小程序appid
        $jsApiPayData->SetAppid(\WxPayConfig::APPID);
        //按照文档,要求是字符串类型
        $jsApiPayData->SetTimeStamp((string)time());
        //生成随机字符串
        $rand = md5(time() . mt_rand(0, 1000));
        $jsApiPayData->SetNonceStr($rand);
        //拼接prepay_id,要注意拼接
        $jsApiPayData->SetPackage('prepay_id=' . $wxOrder['prepay_id']);
        //签名方式md5
        $jsApiPayData->SetSignType('md5');
        //然后调用sdk自带的MakeSign方法生成签名
        $sign = $jsApiPayData->MakeSign();
        //然后在使用sdk自带方法获取到上面的我们赋值到成员属性生成的数组
        $rawValues = $jsApiPayData->GetValues();
        //然后我们在数组上加上生成的签名
        $rawValues['paySign'] = $sign;
        //删除appid,因为返回给客户端没有用,所以消除
        unset($rawValues['appId']);
        //返回
        return $rawValues;
    }

    private function recordPreOrder($wxOrder)
    {
        OrderModel::where(['id'=>$this->orderID])->update(['prepay_id'=>$wxOrder['prepay_id']]);
    }
    private function checkOrderValid()
    {
        $order = OrderModel::get(['id'=>$this->orderID]);
        if(!$order){
            throw new OrderException();
        }
        if(!Token::isValidOperate($order->user_id))
        {
            throw new TokenException(
                [
                    'msg' => '订单与用户不匹配',
                    'errorCode' => 10003
                ]);
        }
        if($order->status != OrderStatusEnum::UNPAID){
            throw new OrderException([
                'msg' => '订单已支付过啦',
                'errorCode' => 80003,
                'code' => 400
            ]);
        }
        $this->orderNo = $order->order_no;
        return true;
    }

}

然后控制器使用:

    public function getPreOrder($id='')
    {
        (new IdMustInt())->goCheck();
        $pay = new Pay($id);
        return $pay->pay();
    }


如果支付成功,小程序端的res.errMsg == 'requestPayment:ok'

success	requestPayment:ok	调用支付成功
fail	requestPayment:fail cancel	用户取消支付
fail	requestPayment:fail (detail message)	调用支付失败,其中 detail message 为后台返回的详细失败原因


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值