php对接微信小程序支付

2018-11-17  本文已影响0人  小小的一只_01

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

开发前流程:

开发流程

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

签名流程:

异步回调处理流程

支付异步回调处理流程

开发流程代码

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
    });
  }
})
'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' => '测试支付', //商品简单描述
    ]
/**
 * 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;
}
/**
 * 获取支付签名
 */
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;
}
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;
    }
}
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));
    }
}
上一篇下一篇

猜你喜欢

热点阅读