前言
统一支付是JSAPI/NATIVE/APP各种支付场景下生成支付订单,返回预支付订单号的接口,目前微信支付所有场景均使用这一接口。下面介绍的是其中APP的支付的配置与实现流程
配置
1.首先登录微信开放平台,注册账号。
https://open.weixin.qq.com/
2.点击创建移动应用
3.获取到移动应用的APPID
4.打开前端uniapp的项目,在主目录下找到manifest.json文件->APP模块配置->Payment->微信支付->将微信开放平台的应用APPID填写在对应的位置
5.根据微信开放平台提供的工具对项目包的包名进行签名的获取
下载链接:https://developers.weixin.qq.com/doc/oplatform/Downloads/Android_Resource.html#%E7%AD%BE%E5%90%8D%E7%94%9F%E6%88%90%E5%B7%A5%E5%85%B7
安装完毕是这样的:输入您的应用包名进行签名的获取即可
6.申请开通微信支付
微信商户号绑定APPID
选择我的产品,申请开通APP、JSAPI支付
选择账户中心->api安全,进行证书与密钥的申请,v2/v3都可以申请,调用的api不同,并不冲突
到此为止关于app微信支付的配置就基本完成了。
流程实现(后端)(PHP)
- 创建Wechatpay.php文件,放到指定文件目录下(我是放到了extend目录)
<?php | |
class Wechatpay{ | |
/** | |
* 模拟提交参数,支持https提交 可用于各类api请求 | |
* @param string $url : 提交的地址 | |
* @param array $data :POST数组 | |
* @param string $method : POST/GET,默认GET方式 | |
* @return mixed | |
*/ | |
function curl_https($url, $xml='', $useCert=false){ | |
$ch = curl_init(); | |
//设置超时 | |
curl_setopt($ch, CURLOPT_TIMEOUT, 30); | |
curl_setopt($ch,CURLOPT_URL, $url); | |
//设置header | |
curl_setopt($ch, CURLOPT_HEADER, FALSE); | |
//要求结果为字符串且输出到屏幕上 | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); | |
if(stripos($url,"https://")!==FALSE){ | |
curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1); | |
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); | |
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); | |
}else{ | |
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE); | |
curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验 | |
} | |
if($useCert == true){ | |
//设置证书 | |
//使用证书:cert 与 key 分别属于两个.pem文件 | |
curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM'); | |
curl_setopt($ch,CURLOPT_SSLCERT,""); | |
curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM'); | |
curl_setopt($ch,CURLOPT_SSLKEY,""); | |
} | |
//post提交方式 | |
curl_setopt($ch, CURLOPT_POST, TRUE); | |
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); | |
//运行curl | |
$data = curl_exec($ch); | |
//返回结果 | |
if($data){ | |
curl_close($ch); | |
return $data; | |
} else { | |
$error = curl_errno($ch); | |
curl_close($ch); | |
//echo $error; | |
} | |
} | |
/** | |
* | |
* 拼接签名字符串 | |
* @param array $urlObj | |
* | |
* @return 返回已经拼接好的字符串 | |
*/ | |
function ToUrlParams($urlObj) | |
{ | |
$buff = ""; | |
foreach ($urlObj as $k => $v) | |
{ | |
if($k != "sign"){ | |
$buff .= $k . "=" . $v . "&"; | |
} | |
} | |
$buff = trim($buff, "&"); | |
return $buff; | |
} | |
//数组转XML | |
function arrayToXml($arr) | |
{ | |
$xml = "<xml>"; | |
foreach ($arr as $key=>$val) | |
{ if (is_numeric($val)){ | |
$xml.="<".$key.">".$val."</".$key.">"; | |
}else{ | |
$xml.="<".$key."><![CDATA[".$val."]]></".$key.">"; | |
} | |
} | |
$xml.="</xml>"; | |
return $xml; | |
} | |
//将XML转为array | |
function xmlToArray($xml) | |
{ | |
//禁止引用外部xml实体 | |
libxml_disable_entity_loader(true); | |
$values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); | |
return $values; | |
} | |
/** | |
* 获取随机字符串 | |
* @return mixed | |
*/ | |
function getRandString($len=12,$str='ABCDEFGHIJKLMNOPQRSTUVWYXZabcdefghijklmnopqrstuvwyxz1234567890'){ | |
$strlen=strlen($str)-1; | |
$string=''; | |
for ($i=0; $i < $len; $i++) { | |
$r=rand(1,$strlen); | |
$string=$string.$str[$r]; | |
} | |
return $string; | |
} | |
} |
2.定义公共变量
private $config = array( | |
'appid_app' => "wx******", /*微信开放平台上的应用id*/ | |
'mch_id' => "*******", /*微信申请成功之后邮件中的商户id*/ | |
'api_key' => "*************", /*在微信商户平台上自己设定的api密钥 32位*/ | |
'notify_url' => 'https://***', /*支付回调地址,确保可以访问*/ | |
); |
3.支付接口
public function AppPay($busid,$price,$code,$type,$attach){ | |
$businessInfo=$this->BusinessModel->find($busid); | |
Loader::import('wechatpay.Wechatpay', EXTEND_PATH,".php"); | |
$wechatpay = new \Wechatpay(); | |
$url='https://api.mch.weixin.qq.com/pay/unifiedorder'; | |
$parameters = array( | |
'appid' => $this->config["appid_app"], // 应用ID | |
'mch_id' => $this->config['mch_id'], // 商户号 | |
'nonce_str' => $wechatpay->getRandString(30), // 随机字符串 | |
'body' => '购买商品测试', // 商品描述 | |
'out_trade_no' => $code, // 商户订单号 | |
'total_fee' => $price * 100, // 总金额,单位为分 | |
'spbill_create_ip' => $this->get_client_ip(), // 终端IP | |
'notify_url' => $this->config["notify_url"], // 通知地址 | |
'trade_type' => "APP", // 交易类型改为APP | |
'sign_type' => "MD5", | |
'attach' => $attach | |
); | |
//参数名ASCII码从小到大排序 | |
ksort($parameters); | |
//统一下单签名 | |
$String = $wechatpay->ToUrlParams($parameters); | |
//签名步骤二:在string后加入KEY | |
$String = $String."&key=".$this->config['api_key']; // key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 | |
$String2=$String; | |
//签名步骤三:MD5加密 (一次签名) | |
$time=time(); | |
$parameters['sign']=strtoupper(md5($String)); | |
$xmlData=$wechatpay->arrayToXml($parameters); | |
$return=$wechatpay->xmlToArray($wechatpay->curl_https($url,$xmlData)); | |
if($return["return_code"]=="SUCCESS" && $return["result_code"]=="SUCCESS"){ | |
// 从预支付接口返回的参数中取得 prepay_id | |
$prepay_id = $return['prepay_id']; | |
// 构造二次签名的参数 | |
$signParams = array( | |
'appid' => $this->config["appid_app"], | |
'partnerid' => $this->config['mch_id'], // 商户号 | |
'prepayid' => $return['prepay_id'], // 从预支付接口返回的参数中取得prepay_id | |
'package' => 'Sign=WXPay', // Sign=WXPay | |
'noncestr' => $wechatpay->getRandString(30), // 随机字符串 | |
'timestamp' => (string)$time, // 时间戳需与统一下单时的时间戳保持一致 | |
); | |
// 对签名参数进行签名 | |
ksort($signParams); | |
$signString = $wechatpay->ToUrlParams($signParams); | |
$signString = $signString . "&key=" . $this->config['api_key']; | |
$sign = strtoupper(md5($signString)); | |
// 将签名加入返回给APP的参数中 | |
$signParams['sign'] = $sign; | |
// 返回参数给APP | |
$this->result($signParams, '1', '二次签名成功!', 'json'); | |
}else{ | |
echo json_encode(array("status"=>false,"msg"=>$return)); | |
$this->result($return, '0', '签名失败!', 'json'); | |
} | |
} | |
/* | |
获取当前服务器的IP | |
*/ | |
public function get_client_ip(){ | |
if ($_SERVER['REMOTE_ADDR']) { | |
$cip = $_SERVER['REMOTE_ADDR']; | |
} elseif (getenv("REMOTE_ADDR")) { | |
$cip = getenv("REMOTE_ADDR"); | |
} elseif (getenv("HTTP_CLIENT_IP")) { | |
$cip = getenv("HTTP_CLIENT_IP"); | |
} else { | |
$cip = "unknown"; | |
} | |
return $cip; | |
} |
- 支付回调
//微信支付回调接口 | |
public function wxpaynotify(){ | |
$xml = file_get_contents('php://input'); | |
Loader::import('wechatpay.Wechatpay', EXTEND_PATH,".php"); | |
$wechatpay = new \Wechatpay(); | |
//将服务器返回的XML数据转化为数组 | |
$data = $wechatpay->xmlToArray($xml); | |
// 保存微信服务器返回的签名sign | |
$data_sign = $data['sign']; | |
// sign不参与签名算法 | |
unset($data['sign']); | |
$sign = $wechatpay->ToUrlParams($data); | |
$payData=$sign; | |
$sign=strtoupper(md5($sign."&key=".$this->config["api_key"])); | |
// 判断签名是否正确 判断支付状态 | |
if ( ($sign===$data_sign) && ($data['result_code']=='SUCCESS') ) { | |
// 更新消费订单状态等操作 | |
$result = true; | |
}else{ | |
file_put_contents('payResult.tex','验签失败!'); | |
$result = false; | |
} | |
// 返回状态给微信服务器 | |
if ($result) { | |
$str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>'; | |
}else{ | |
$str='<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名失败]]></return_msg></xml>'; | |
} | |
echo $str; | |
} |
流程实现(前端)(Vue)(APP)
uni.getProvider({ | |
service: 'payment', | |
success: function(ress) { | |
//判断是否有支付权限 | |
if (~ress.provider.indexOf('wxpay')) { | |
let orderInfo={ | |
"appid": result.data.appid, // 微信开放平台 - 应用 - AppId,注意和微信小程序、公众号 AppId 可能不一致 | |
"noncestr": result.data.noncestr, // 随机字符串 | |
"package": "Sign=WXPay", // 固定值 | |
"partnerid": result.data.partnerid, // 微信支付商户号 | |
"prepayid": result.data.prepayid, // 统一下单订单号 | |
"timestamp": result.data.timestamp, // 时间戳(单位:秒) | |
"sign": result.data.sign // 签名,这里用的 MD5/RSA 签名 | |
} | |
// 调用 wx.requestPayment 方法发起支付请求 | |
uni.requestPayment({ | |
"provider": "wxpay", | |
"orderInfo": orderInfo, | |
success(res) { | |
// 支付成功的处理逻辑 | |
console.log('支付成功', res); | |
uni.showToast({ | |
title: '支付成功!', | |
icon: 'none', | |
duration: 1000 | |
}) | |
}, | |
fail(res) { | |
// 支付失败的处理逻辑 | |
console.log('支付失败', res); | |
uni.showToast({ | |
title: res.errMsg, | |
icon: 'none', | |
duration: 2000 | |
}) | |
} | |
}) | |
} | |
} | |
}); |