美文网首页
php对接微信小程序支付

php对接微信小程序支付

作者: 小小的一只_01 | 来源:发表于2018-11-17 15:49 被阅读0次

    注:个人注册微信小程序不支持微信支付功能

    开发前流程:

    开发流程

    注意:支付按钮要加上锁避免用户多次点击

    • 1.用户点击下单按钮
    • 2.微信小程序用wx.login方法获取用户登录凭证code,code有效期为五分钟
    • 3.微信小程序调用服务器接口,传入code,money
    • 4.服务器接收到code,money后,调用微信api的code2Session方法进行登录凭证校验,获取到用户唯一标识open_id
    • 5.服务器验证open_id,并创建支付订单
    • 6.服务器将数据进行签名调用统一下单api,获取到prepay_id
    • 7.服务器再次生成签名信息,返回微信小程序
    • 8.微信小程序调用wx.requestPayment方法调用微信支付窗口
    • 9.服务器异步接收支付通知结果,进行签名验证,并校验用户和返回的订单金额是否与商户的订单金额一致,修改数据库并生成日志

    签名流程:

    • 1.参数名ASCII码从小到大排序
    • 2.格式化参数,将数组转换成key1=valve1&key2=value2...的形式
    • 3.追加key,key1=valve1&key2=value2...&key=*******
    • 4.md5加密
    • 5.转化为大写

    异步回调处理流程

    支付异步回调处理流程

    开发流程代码

    • 1.用户点击下单按钮

    wxml代码

    <view class="container">
      <text class="name">当前选择:《不抱怨的世界》 ¥0.01</text>
      <button class="pay" hidden="{{!pay}}" catch:tap="onTap">确认支付</button>
      <button class="pay2" hidden="{{pay}}">确认支付</button>
    </view>
    

    wxss代码

    .container {
      padding: 50rpx;
    }
    
    .pay {
      margin-top: 30rpx;
      color: #fff;
      background-color: #1fb922;
    }
    
    .pay2 {
      margin-top: 30rpx;
      color: #fff;
      background-color: #dedede;
    }
    

    js代码

    Page({
    
      /**
       * 页面的初始数据
       */
      data: {
        pay: true,
      },
    
      onTap () {
        let _self = this;
        _self._togglePay();
        wx.login({
          success(res) {
            if (res.code) {
              //发起网络请求
              wx.request({
                url: 'http://api.djny.com/v1/pay/pay-sign',
                method: "POST",
                data: {
                  code: res.code,
                  money:0.01
                },
                header: {
                  "content-type": "application/x-www-form-urlencoded"
                },
                success(res) {
                  var params = res.data.data;
                  wx.requestPayment({
                    'timeStamp': String(params['timeStamp']),
                    'nonceStr': params['nonceStr'],
                    'package': params['package'],
                    'signType': params['signType'],
                    'paySign': params['paySign'],
                    'success': function (res) {
                      wx.showToast({
                        title: '支付成功',
                        icon: 'success',
                        duration: 2000
                      })
                      _self._togglePay();
                    },
                    'fail': function (res) {
                      wx.showToast({
                        title: '支付失败',
                        icon: 'none',
                        duration: 2000
                      })
                      _self._togglePay();
                    },
                  })
                }
              })
            } else {
              this._togglePay();
              console.log('登录失败!' + res.errMsg)
            }
          }
        })
    
      },
    
      _togglePay() {
        this.setData({
          pay: !this.data.pay
        });
      }
    })
    
    • 2.基本配置
    'wx_pay' => [
            'app_id' => 'wx2**********2965c', //小程序appid
            'app_secret' => '55913********************6574c6b', //小程序secret
            'mch_id' => '15******71', //商户平台商户号
            'key' => 'sQm*******************aVQkca', //商户平台密钥key
            'notify_url' => 'http://www.test.cn/v1/notify-pay', //支付异步回调地址
            'name' => '测试支付', //商品简单描述
        ]
    
    • 3.工具类方法
    /**
     * curl get
     * @param $url 请求路径
     * @param array $params 参数
     * @return mixed
     */
    public static function get($url, array $params)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url . "?" . http_build_query($params));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 获取数据返回
        curl_setopt($ch, CURLOPT_BINARYTRANSFER, true); // 在启用 CURLOPT_RETURNTRANSFER 时候将获取数据返回
        $output = curl_exec($ch);
        curl_close($ch);
        return $output;
    }
    
    /**
     * curl post xml
     * @param $xml 参数
     * @param $url 请求地址
     * @param int $second 设置超时
     * @return mixed
     */
    public static function postXml($xml, $url, $second = 60)
    {
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); //严格校验
        //设置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
    
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
        curl_setopt($ch, CURLOPT_TIMEOUT, 40);
        set_time_limit(0);
    
        //运行curl
        $data = curl_exec($ch);
        curl_close($ch);
        return $data;
    }
    
    /**
     * array to xml
     * @param $arr
     * @return string
     */
    public static function arrayToXml($arr)
    {
        $xml = "<xml>";
        foreach ($arr as $key => $val) {
            if (is_array($val)) {
                $xml .= "<" . $key . ">" . _arrayToXml($val) . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }
    
    /**
     * xml to array
     * @param $xml
     * @return mixed
     */
    public static function xmlToArray($xml)
    {
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
    
        $xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
    
        $val = json_decode(json_encode($xmlstring), true);
    
        return $val;
    }
    
    /**
     * 生产随机字符串 默认32位
     * @param int $length
     * @return string
     */
    public static function randStr($length = 32)
    {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }
    
    /**
     * 格式化参数 array to key1=valve1&key2=value2
     * @param $params
     * @param $url_encode
     * @return string
     */
    public static function formatParams($params, $url_encode)
    {
        if (!$params) {
            return '';
        }
    
        $paramUrl = "";
    
        ksort($params);
    
        foreach ($params as $k => $v) {
            if ($url_encode) {
                $v = urlencode($v);
            }
    
            $paramUrl .= $k . "=" . $v . "&";
        }
    
        if (strlen($paramUrl) > 0) {
            $paramUrl = substr($paramUrl, 0, strlen($paramUrl) - 1);
        }
    
        return $paramUrl;
    }
    
    • 4.控制层代码
    /**
     * 获取支付签名
     */
    public function actionPaySign()
    {
        $code = Yii::$app->request->post('code');
        $money = Yii::$app->request->post('money');
    
        if (!$code || $money <= 0) {
            $this->render_json(self::STATUS_CODE_CLIENT_ERROR, '参数错误');
        }
    
        $wxPay = new WxPay();
    
        $openId = $wxPay->getOpenId($code);
    
        if (!$openId) {
            $this->render_json(self::STATUS_CODE_CLIENT_ERROR, $wxPay->getError());
        }
    
        $userInfo = WxMember::findOne(['openid' => $openId]);
    
        if (!$userInfo) {
            $this->render_json(self::STATUS_CODE_CLIENT_ERROR, '用户信息获取失败');
        }
    
        $wxPayOrder = new WxPayorder();
    
        if (!$wxPayOrder->createPayOrder($userInfo, $money)) {
            $this->render_json(self::STATUS_CODE_CLIENT_ERROR, $wxPayOrder->getFirstError('id'));
        }
    
        $paySign = $wxPay->paySign($openId, $wxPayOrder->out_trade_no, $wxPayOrder->order_amount);
    
        if (!$paySign) {
            $this->render_json(self::STATUS_CODE_CLIENT_ERROR, $wxPay->getError());
        }
    
        $this->render_json(self::STATUS_CODE_SUCCESS, '成功', $paySign);
    }
    
    /**
     * 小程序支付异步回调
     */
    public function actionNotifyPay()
    {
        $xml = file_get_contents('php://input', 'r');
        $resData = Utility::xmlToArray($xml);
        $wxPay = new WxPay();
    
        if ($wxPay->checkNotifySign($resData)) {
            $wxPayOrder = new WxPayorder();
            $result = $wxPayOrder->updateNotifyPayOrder($resData['return_code'], $resData['openid'], $resData['out_trade_no'], $resData['total_fee'] / 100, $resData['transaction_id'], $resData);
    
            if ($result) {
                $resStr = $wxPay->notifyReturnSuccess();
            } else {
                $resStr = $wxPay->notifyReturnFail($wxPayOrder->getFirstError('id'));
            }
        } else {
            $resStr = $wxPay->notifyReturnFail('签名验证失败');
        }
        echo $resStr;
    }
    
    • 5.微信支付接口类
    class WxPay
    {
        /**
         * 小程序appid
         * @var string
         */
        private $_appId;
    
        /**
         * 小程序secret
         * @var string
         */
        private $_appSecret;
    
        /**
         * 商户平台商户id
         * @var string
         */
        private $_mchId;
    
        /**
         * 商户平台密钥key
         * @var string
         */
        private $_key;
    
        /**
         * 支付回掉地址
         * @var string
         */
        private $_notifyUrl;
    
        /**
         * 获取用户唯一标识open_id api 接口地址
         * @var string
         */
        private $_code2SessionApiUrl;
    
        /**
         * 统一下单api 接口地址
         * @var string
         */
        private $_unifiedOrderApiUrl;
    
        /**
         * 错误信息
         * @var string
         */
        public $errorInfo;
    
        /**
         * WxPay constructor.
         */
        public function __construct()
        {
            $this->_appId = Yii::$app->params['wx_pay']['app_id'];
            $this->_appSecret = Yii::$app->params['wx_pay']['app_secret'];
            $this->_mchId = Yii::$app->params['wx_pay']['mch_id'];
            $this->_key = Yii::$app->params['wx_pay']['key'];
            $this->_notifyUrl = Yii::$app->params['wx_pay']['notify_url'];
            $this->_code2SessionApiUrl = 'https://api.weixin.qq.com/sns/jscode2session';
            $this->_unifiedOrderApiUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
        }
    
        /**
         * 添加错误信息
         * @param $err
         */
        public function setError($err)
        {
            $this->errorInfo = $err;
        }
    
        /**
         * 获取错误信息
         * @return string
         */
        public function getError()
        {
            return $this->errorInfo;
        }
    
        /**
         * 获取用户唯一标识open_id
         * @param $code
         * @return string
         */
        public function getOpenId($code)
        {
            if (!$code) {
                return '';
            }
    
            $params = [
                'appid' => $this->_appId,
                'secret' => $this->_appSecret,
                'js_code' => $code,
                'grant_type' => 'authorization_code'
            ];
    
            $resJson = Curl::get($this->_code2SessionApiUrl, $params);
    
            $res = json_decode($resJson);
    
            if (isset($res->errcode)) {
                $this->errorInfo = '获取用户open_id失败';
                return '';
            }
    
            return $res->openid;
        }
    
        /**
         * 获取支付签名
         * @param $open_id
         * @param $order_no
         * @param $money
         * @return array
         */
        public function paySign($open_id, $order_no, $money)
        {
            if (!$open_id || !$order_no || !$money) {
                $this->setError('参数错误');
                return [];
            }
    
            $prepay_id = $this->_unifiedorder($open_id, $order_no, $money);
    
            if (!$prepay_id) {
                return [];
            }
    
            $params = array(
                'appId' => $this->_appId,
                'timeStamp' => time(),
                'nonceStr' => Utility::randStr(),
                'package' => 'prepay_id=' . $prepay_id,
                'signType' => 'MD5'
            );
    
            $params['paySign'] = $this->_getSign($params);
    
            return $params;
        }
    
        /**
         * 异步签名验证
         * @param $data
         * @return bool
         */
        public function checkNotifySign($data)
        {
            if (!$data) {
                return false;
            }
    
            $sign = $data['sign'];
    
            unset($data['sign']);
    
            if ($sign == $this->_getSign($data)) {
                return true;
            }
    
            return false;
        }
    
        /**
         * 异步回调处理成功时返回内容
         * @param $msg
         * @return string
         */
        public function notifyReturnSuccess($msg = 'OK')
        {
            return '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[' . $msg . ']]></return_msg></xml>';
        }
    
        /**
         * 异步回调处理失败时返回内容
         * @param $msg
         * @return string
         */
        public function notifyReturnFail($msg = 'FAIL')
        {
            return '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[' . $msg . ']]></return_msg></xml>';
        }
    
        /**
         * 统一下单
         * @param $open_id
         * @param $order_no
         * @param $money
         * @return string
         */
        private function _unifiedOrder($open_id, $order_no, $money)
        {
            $params = [
                'appid' => $this->_appId,
                'mch_id' => $this->_mchId,
                'nonce_str' => Utility::randStr(),
                'body' => Yii::$app->params['wx_pay']['name'], //商品简单描述
                'out_trade_no' => $order_no, //商户系统内部订单号
                'total_fee' => $money * 100, //订单总金额,单位为分
                'spbill_create_ip' => Utility::getClientIp(), //用户端ips
                'notify_url' => $this->_notifyUrl, //通知地址
                'trade_type' => 'JSAPI', //交易类型
                'openid' => $open_id //用户标识
            ];
    
            $params['sign'] = $this->_getSign($params);
    
            $xmlData = Utility::arrayToXml($params);
    
            $returnXml = Curl::postXml($xmlData, $this->_unifiedOrderApiUrl);
    
            $returnArr = Utility::xmlToArray($returnXml);
    
            if ($returnArr['return_code'] == 'FAIL') {
                $this->setError($returnArr['return_msg']);
                return '';
            }
    
            return $returnArr['prepay_id'];
    
        }
    
    
        /**
         * 获取签名
         * @param $params
         * @return string
         */
        private function _getSign($params)
        {
            if (!$params) {
                return '';
            }
    
            //step1: 排序
            ksort($params);
    
            //step2:格式化参数
            $paramUrl = Utility::formatParams($params, false);
    
            //step3:追加key
            $paramUrl = $paramUrl . '&key=' . $this->_key;
    
            //step4: md5加密
            $paramUrl = md5($paramUrl);
    
            //step5:转化为大写
            $sign = strtoupper($paramUrl);
    
            return $sign;
        }
    }
    
    • 6.模型层
    class WxPayorder extends \common\models\base\BaseMain
    {
        /**
         * 充值类型
         */
        const TYPE_XCX = 1;
        const TYPE_GZH = 2;
    
        /**
         * 充值状态: 1未审核 2失败 3成功 4失效 5其他 6处理中
         */
        const STATUS_WAITING = 1;
        const STATUS_FAILED = 2;
        const STATUS_SUCCESS = 3;
        const STATUS_LOSE = 4;
        const STATUS_OTHER = 5;
        const STATUS_PROCESSING = 6;
    
        /**
         * 充值时最大金额 单位 元
         */
        const MONEY_MAX_RECHARGE = 5000;
    
        /**
         * 账户最大金额 单位 元
         */
        const MONEY_MAX_ACCOUNT = 6000;
    
        /**
         * @inheritdoc
         */
        public static function tableName()
        {
            return '{{%wx_payorder}}';
        }
    
        /**
         * @inheritdoc
         */
        public function rules()
        {
            return [
                [['memberid', 'out_trade_no', 'type', 'status', 'order_amount', 'createtime'], 'required', 'on' => 'insert'],
                [['status', 'operation_time'], 'required', 'on' => 'updateStatus'],
                [['payamount', 'remark', 'operation_time'], 'required', 'on' => 'updatePaySuccess'],
                [['remark', 'operation_time'], 'required', 'on' => 'updatePayFail'],
                [['memberid', 'type', 'status', 'operation_time', 'createtime'], 'integer'],
                [['payamount', 'order_amount'], 'number'],
                [['type', 'status'], 'required'],
                [['out_trade_no'], 'string', 'max' => 50],
                [['remark'], 'string', 'max' => 100],
            ];
        }
    
        /**
         * @inheritdoc
         */
        public function attributeLabels()
        {
            return [
                'id' => 'ID',
                'memberid' => '会员ID',
                'out_trade_no' => '订单号',
                'payamount' => '充电金额',
                'type' => '终端充值类型(1小程序2公众号)',
                'status' => '充值状态(1未审核2失败3成功4失效5其他)',
                'order_amount' => '订单充值金额',
                'remark' => '备注',
                'operation_time' => '响应操作时间(充值成功或失败时需要更新时间)',
                'createtime' => '创建时间',
            ];
        }
    
        /**
         * before validate
         * @return bool
         */
        public function beforeValidate()
        {
            parent::afterValidate();
    
            if ($this->getIsNewRecord()) {
                $this->createtime = time();
            } else {
                $this->operation_time = time();
            }
    
            return true;
        }
    
        /**
         * 添加订单
         * @param $userInfo 用户对象
         * @param $money
         * @return bool
         */
        public function createPayOrder($userInfo, $money)
        {
            $moneyArr = [0.01, 10, 50, 100, 200, 500, 1000];
    
            if (!in_array($money, $moneyArr)) {
                $this->addError('id', '充值金额有误');
                return false;
            }
    
            if ($userInfo->balance >= self::MONEY_MAX_RECHARGE) {
                $this->addError('id', '账户金额已经上限,不能再充值了');
                return false;
            }
    
            if ($userInfo->balance + $money >= self::MONEY_MAX_ACCOUNT) {
                $this->addError('id', '充值金额已经上限');
                return false;
            }
    
            $this->memberid = $userInfo->id;
            $this->out_trade_no = $this->_getOrderId($userInfo->id);
            $this->type = self::TYPE_XCX;
            $this->status = self::STATUS_WAITING;
            $this->order_amount = $money;
            $this->setScenario('insert');
    
            if (!$this->save()) {
                $this->addError('id', '创建订单失败');
                return false;
            }
    
    
            return true;
        }
    
    
        /**
         * 支付成功修改数据库
         * @param $result 支付状态
         * @param $open_id open_id
         * @param $out_trade_no 客户订单号
         * @param $money 金额
         * @param $wx_trade_no 微信支付订单号
         * @param $data
         * @return bool
         * @throws \Exception
         */
        public function updateNotifyPayOrder($result, $open_id, $out_trade_no, $money, $wx_trade_no, $data)
        {
            //记录异步通知日志
            $wxPayOrderLog = new WxPayorderLog();
            $wxPayOrderLog->setScenario('insert');
            $wxPayOrderLog->open_id = $open_id;
            $wxPayOrderLog->type = WxPayorderLog::TYPE_XCX;
            $wxPayOrderLog->out_trade_no = $out_trade_no;
            $wxPayOrderLog->money = $money;
            $wxPayOrderLog->wx_trade_no = $wx_trade_no;
            $wxPayOrderLog->remark = '异步请求';
            $wxPayOrderLog->remark_back = json_encode($data);
    
            if (!$wxPayOrderLog->save()) {
                $this->addError('id', '订单日志保存失败');
                return false;
            }
    
            //订单查询
            $wxPayOrder = WxPayorder::findOne(['out_trade_no' => $out_trade_no]);
    
            if (!$wxPayOrder) {
                $this->addError('id', '订单不存在');
                return false;
            }
    
            //如果订单支付状态为成功直接返回
            if ($wxPayOrder->status == WxPayorder::STATUS_SUCCESS) {
                return true;
            }
    
            //订单支付状态为未处理
            if ($wxPayOrder->status == WxPayorder::STATUS_WAITING) {
    
                //修订订单状态为处理中
                $wxPayOrder->status = WxPayorder::STATUS_PROCESSING;
    
                $wxPayOrder->setScenario('updateStatus');
    
                if (!$wxPayOrder->save()) {
                    $this->addError('id', '订单状态修改失败');
                    return false;
                }
    
                //验证用户
                $wxMember = WxMember::findOne(['openid' => $open_id]);
    
                if (!$wxMember) {
                    $this->addError('id', '用户不存在');
                    return false;
                }
    
                if ($wxMember->id != $wxPayOrder->memberid) {
                    $this->addError('id', '用户信息有误');
                    return false;
                }
    
                //验证金额
                if ($wxPayOrder->order_amount != $money) {
                    $this->addError('id', '订单金额有误');
                    return false;
                }
    
                //判断订单支付状态
                if ($result == 'SUCCESS') {
    
                    //修改订单状态为成功
                    $wxPayOrder->status = self::STATUS_SUCCESS;
    
                    $wxPayOrder->setScenario('updateStatus');
                    if (!$wxPayOrder->save()) {
                        $this->addError('id', '订单状态修改失败');
                        return false;
                    }
    
                    //修改订单信息
                    $wxPayOrder->payamount = $wxPayOrder->order_amount;
                    $wxPayOrder->remark = '支付成功';
    
                    $wxPayOrder->setScenario('updatePaySuccess');
                    if (!$wxPayOrder->save()) {
                        $this->addError('id', '订单信息保存失败');
                        return false;
                    }
    
                    //保存账户余额表
                    $wxMember->balance = $wxMember->balance + $wxPayOrder->payamount;
    
                    if (!$wxMember->save()) {
                        $this->addError('id', '用户账户信息保存失败');
                        return false;
                    }
    
                    return true;
                } else {
    
                    //保存订单表
                    $wxPayOrder->status = self::STATUS_FAILED;
                    $wxPayOrder->remark = '支付失败';
                    $wxPayOrder->setScenario('updatePayFail');
    
                    if (!$wxPayOrder->save()) {
                        $this->addError('id', '数据错误');
                        return false;
                    }
                }
            }
    
            $this->addError('id', '订单处理失败');
            return false;
        }
    
        /**
         * 获取用户唯一订单号 最大32位
         * @param $userId
         * @return string
         */
        private function _getOrderId($userId)
        {
            return 'XCX' . (time() . $userId . rand(1000, 9000) . rand(1000, 9000));
        }
    }
    

    相关文章

      网友评论

          本文标题:php对接微信小程序支付

          本文链接:https://www.haomeiwen.com/subject/xqzbfqtx.html