版权声明: https://blog.csdn.net/qq_27987023/article/details/86506415
小程序支付原理实际上跟微信公众号普通支付原理类似,复制企鹅的原话就是:
商户系统先调用统一下单接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易回话标识后再按扫码、JSAPI、APP、小程序等不同场景生成交易串调起支付,具体API接口请查看"API列表"
所以现在基本明确了两个步骤,首先需要生成预支付交易单,其次根据预支付订单去唤起相对应的支付,小程序唤起微信支付的内置api是wx.requestPayment,参数为
所以我们现在的目标就是根据微信文档的各种签名算法算出小程序支付所需要的各种参数,现在假如我已经在系统中生成了一个唯一订单号:
<!--小程序订单详情页,已包含订单号,产品,价格等各类信息,这个根据自身业务需求进行生成-->
<view class="flex-box chackout-right-detail">
<text bindtap="payOrder" class="flow-btn">去付款</text>
</view>
payOrder: function (e) {
let _this = this,
orderid = _this.data.orderid; //订单号
wx.showLoading({ title: '正在处理...', });
App._post_form('order/pay', { orderid }, function (result) {
if (result.code === -10) {
App.showError(result.msg);
return false;
}
// 发起微信支付
wx.requestPayment({
timeStamp: result.data.timeStamp,
nonceStr: result.data.nonceStr,
package: 'prepay_id=' + result.data.prepay_id,
signType: 'MD5',
paySign: result.data.paySign,
success: function (res) {
_this.getOrderDetail(order_id);
},
fail: function () {
App.showError('订单未支付');
setTimeout(function () {
wx.reLaunch({
url: '../order/index',
})
}, 2000)
},
});
});
},
/*
* _post_form,showError是封装的post请求和弹窗,这个可自行构建,实际调用的就是 wx.request
*/
服务端接收到订单号,根据最开始说的流程,去获取唤起小程序支付的各种参数:
public function _initialize()
{
parent::_initialize();
$this->user = $this->get_user_info();
$this->config = array(
'app_id' => '', //小程序appid
'mchid' => '', //商户号
'apikey' => '' //秘钥
);
}
public function pay($order_id)
{
//获取订单详情
$order_detail = Db::name("order")->where("order_id",$order_id)->find();
//更新订单支付信息
$update_data = [
'pay_code' => "miniAppPay",
'pay_name' => "微信小程序支付",
];
Db::name("order")->where("order_id",$order_id)->update($update_data);
$wxParams = $this->unifiedorder($order_detail['order_sn'], $this->user['xcx_openid'], $order_detail['order_amount']);
return $this->renderSuccess($wxParams);
}
public function unifiedorder($order_sn, $openid, $total_fee)
{
// 当前时间
$time = time();
// 生成随机字符串
$nonceStr = md5($time . $openid);
// API参数
$params = [
'appid' => $this->config['app_id'],
'attach' => 'test',
'body' => $order_sn,
'mch_id' => $this->config['mchid'],
'nonce_str' => $nonceStr,
'notify_url' => base_url().url('api/xcxnotify/notify'), // 异步通知地址
'openid' => $openid,
'out_trade_no' => $order_sn,
'spbill_create_ip' => \request()->ip(),
'total_fee' => $total_fee * 100, // 价格:单位分
'trade_type' => 'JSAPI',
];
// 生成签名
$params['sign'] = $this->makeSign($params);
// 请求API
$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
$result = $this->postXmlCurl($this->toXml($params), $url);
$prepay = $this->fromXml($result);
// 请求失败
if ($prepay['return_code'] === 'FAIL') {
return $this->renderError($prepay['err_code_des']);
}
if ($prepay['result_code'] === 'FAIL') {
return $this->renderError($prepay['err_code_des']);
}
// 生成 nonce_str 供前端使用
$paySign = $this->makePaySign($params['nonce_str'], $prepay['prepay_id'], $time);
return [
'prepay_id' => $prepay['prepay_id'],
'nonceStr' => $nonceStr,
'timeStamp' => (string)$time,
'paySign' => $paySign,
'order_sn' => $order_sn
];
}
/**
* 以post方式提交xml到对应的接口url
* @param $xml
* @param $url
* @param int $second
* @return mixed
*/
public function postXmlCurl($xml, $url, $second = 30){
$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
$data = curl_exec($ch);
curl_close($ch);
return $data;
}
/**
* 输出xml字符
* @param $values
* @return bool|string
*/
public function toXml($values)
{
if (!is_array($values)
|| count($values) <= 0
) {
return false;
}
$xml = "<xml>";
foreach ($values as $key => $val) {
if (is_numeric($val)) {
$xml .= "<" . $key . ">" . $val . "</" . $key . ">";
} else {
$xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
}
}
$xml .= "</xml>";
return $xml;
}
/**
* 将xml转为array
* @param $xml
* @return mixed
*/
public function fromXml($xml)
{
// 禁止引用外部xml实体
libxml_disable_entity_loader(true);
return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
}
/**
* 生成签名
* @param $values
* @return string 本函数不覆盖sign成员变量,如要设置签名需要调用SetSign方法赋值
*/
public function makeSign($values){
//签名步骤一:按字典序排序参数
ksort($values);
$string = $this->toUrlParams($values);
//签名步骤二:在string后加入KEY
$string = $string . '&key=' . $this->config['apikey'];
//签名步骤三:MD5加密
$string = md5($string);
//签名步骤四:所有字符转为大写
$result = strtoupper($string);
return $result;
}
/**
* 生成paySign
* @param $nonceStr
* @param $prepay_id
* @param $timeStamp
* @return string
*/
public function makePaySign($nonceStr, $prepay_id, $timeStamp)
{
$data = [
'appId' => $this->config['app_id'],
'nonceStr' => $nonceStr,
'package' => 'prepay_id=' . $prepay_id,
'signType' => 'MD5',
'timeStamp' => $timeStamp,
];
//签名步骤一:按字典序排序参数
ksort($data);
$string = $this->toUrlParams($data);
//签名步骤二:在string后加入KEY
$string = $string . '&key=' . $this->config['apikey'];
//签名步骤三:MD5加密
$string = md5($string);
//签名步骤四:所有字符转为大写
$result = strtoupper($string);
return $result;
}
/**
* 格式化参数格式化成url参数
* @param $values
* @return string
*/
public function toUrlParams($values)
{
$buff = '';
foreach ($values as $k => $v) {
if ($k != 'sign' && $v != '' && !is_array($v)) {
$buff .= $k . '=' . $v . '&';
}
}
return trim($buff, '&');
}
/**
* 支付通知
*/
public function notify()
{
if (!$xml = file_get_contents('php://input')) {
$this->returnCode(false, 'Not found DATA');
}
// 将服务器返回的XML数据转化为数组
$data = $this->fromXml($xml);
// 保存微信服务器返回的签名sign
$dataSign = $data['sign'];
// sign不参与签名算法
unset($data['sign']);
// 生成签名
$sign = $this->makeSign($data);
$wx_total_fee = $data['total_fee'];
// 判断签名是否正确 判断支付状态
if (($sign === $dataSign) && ($data['return_code'] == 'SUCCESS') && ($data['result_code'] == 'SUCCESS')) {
$order_sn = $data['out_trade_no'];
$order_amount = M('order')->where(['master_order_sn'=>"$order_sn"])->whereOr(['order_sn'=>"$order_sn"])->sum('order_amount');
if ((string)($order_amount * 100) == (string)$wx_total_fee) {
//更新订单状态,自身逻辑,不做赘述
}
}
}
提交,完事