微信支付,支持的支付方式比较多:有扫码支付,刷卡支付,APP支付和公众号支付。其中,APP和网站上最经常使用的就是APP支付和公众号支付。前者集成在APP中,后者主要是为微信用户提供了另外一种支付方式(须要在微信的内置浏览器中打开页面,再调起微信支付)。php
微信支付,支持的支付方式比较多:有扫码支付,刷卡支付,APP支付和公众号支付。其中,APP和网站上最经常使用的就是APP支付和公众号支付。前者集成在APP中,后者主要是为微信用户提供了另外一种支付方式(须要在微信的内置浏览器中打开页面,再调起微信支付)。前端
一样的,微信的APP支付和支付宝的APP支付也是很简单:web
商户系统和微信支付系统主要交互说明:json
步骤1:用户在商户APP中选择商品,提交订单,选择微信支付。api
步骤2:商户后台收到用户支付单,调用微信支付统一下单接口。参见【统一下单API】。浏览器
步骤3:统一下单接口返回正常的prepay_id,再按签名规范从新生成签名后,将数据传输给APP。参与签名的字段名为
appId
,partnerId
,prepayId
,nonceStr
,timeStamp
,package
。注意:package的值格式为Sign=WXPay安全步骤4:商户APP调起微信支付。服务器
步骤5:商户后台接收支付通知。微信
步骤6:商户后台查询支付结果。app
这里主要的仍是后台干活(获取 prepay_id
,生成随机字符串 nonceStr
和时间戳 timeStamp
,appId
和 partnerId
均能在后台管理中查看。)
后台的步骤也很简洁,就是上述中的步骤1,2。
获取 prepayId
:
prepayId
所需参数。此处须要调用微信的统一下单接口。这个过程,官方文档已经写得十分之详细了,包括调用的接口API地址,须要传递的参数(必要和非必要的参数),还有返回结果也写得很清楚。
如下是我在实际项目开发中传入的参数。
签名都差很少,都是先将全部的带签名的参数进行字典排序。
ksort($data); |
而后将参数以 {key}={value}
的组合形式,用 &
链接。
$a = array(); foreach ($data as $k => $v) { if ((string) $v === '') { continue; } $a[] = "{$k}={$v}"; } $a = implode('&', $a); |
最后拼上 &key={Your apiKey}
,而后对整串字符串进行MD5加密便可。
$sign = strtoupper(md5($a)); |
将拼好的数据,以 XML
的格式发送给微信,请求 prepayId
没错,就是要转成 XML
格式再发送。
可是,这个XML格式很简单,只须要进行简单的拼接便可:
public function arrayToXml(array $data) { $xml = "<xml>"; foreach ($data as $k => $v) { if (is_numeric($v)) { $xml .= "<{$k}>{$v}</{$k}>"; } else { $xml .= "<{$k}><![CDATA[{$v}]]></{$k}>"; } } $xml .= "</xml>"; return $xml; } |
参数值用XML转义便可,CDATA标签用于说明数据不被XML解析器解析。。
而后请求统一下单API便可(url = https://api.mch.weixin.qq.com/pay/unifiedorder
)
$ch = curl_init(); curl_setopt($ch, CURLOPT_TIMEOUT, 30); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); curl_setopt($ch, CURLOPT_HEADER, FALSE); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($ch, CURLOPT_POST, TRUE); curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); $response = curl_exec($ch); if (!$response) { throw new Exception('CURL Error: ' . curl_errno($ch)); } curl_close($ch); |
请求回来的数据也为XML格式,只须要简单作下处理,转换成array便可:
public function xmlToArray($xml){ return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); } |
若是返回值中的 return_code
和 result_code
都为 SUCCESS
的时候会返回 交易类型 trade_type
和 预支付交易会话标识 prepayId
。到这里,咱们就能够获取到 prepayId
。
将获取的 prepayId
与其余参数拼接,返回给APP便可。
$params = array(); // 商户号 $params['appid'] = $this->config->appId; // 时间戳 $params['timestamp'] = '' . time(); // 随机字符串 $params['noncestr'] = md5(uniqid(mt_rand(), true)); // 固定为 'Sign=WXPay' $params['package'] = 'Sign=WXPay'; // 步骤3获取的预支付交易会话标识 $params['prepayid'] = $prepayId; // 合做伙伴id $params['partnerid'] = $this->config->partnerId; // 步骤2生成的签名。 $params['sign'] = $this->sign($params); |
微信APP支付,后台须要干的活到这里就暂时结束了(由于还有支付成功后的异步通知商户后面再讲)
下面就是web版的微信支付(公司项目是在微信浏览器内,选择微信支付后,在微信中调起的微信支付)
web版微信支付的步骤和APP的大同小异,也是现获取 prepayId
,再在页面中,调用jsapi进行支付。
可是,此处有2个坑
appid and openid not match
的报错缘由很是的简单,就是支付时所获取的 openid
在并不属于支付的商户。
这个 openid
为微信用户在商户对应appid下的惟一标识。也就是说,必须根据支付的商户的 appid 去获取用户的 openid
。
由于业务逻辑须要,项目中用于微信登陆用的公众号A与用于支付的公众号B(其实还和开放平台用于APP支付的 appId
也是不同的)是不同的,虽然所获取unionid是一致,可是 openid
是不!一!样!的!因此,在获取 openid
时,须要使用当前支付时所用到的 appid 去请求用户的 openid
,同时,请求 openid 后的回调也必须是 支付商户 后台所设置好的回调地址,要否则就会报 redirect_uri 参数错误
的错误。
↑ APP支付的参数
↑ web支付的参数
仔细看看划横线的地方。没错,app中的参数的key全是小写,web支付中的key则为驼峰命名方式。并且,签名方式 signType
是必填的, 签名的字段也变成了 paySign
,其中 package
的值也是不同,APP支付是固定的值,web支付则为 prepayId
,这也要注意。固然,官方文档也是很详细的说明,可是须要细心观察(因此说嘛,仍是直接拷贝必填项最保险了2333)。
拿到全部参数后,就能够在页面中发起微信支付的请求了。
代码能够直接使用官方提供的js代码
function onBridgeReady(){ WeixinJSBridge.invoke( 'getBrandWCPayRequest', YOUR_PARAMS, function(res){ if(res.err_msg == "get_brand_wcpay_request:ok" ) { // success_callback } // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。 } ); } if (typeof WeixinJSBridge == "undefined"){ if( document.addEventListener ){ document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false); }else if (document.attachEvent){ document.attachEvent('WeixinJSBridgeReady', onBridgeReady); document.attachEvent('onWeixinJSBridgeReady', onBridgeReady); } }else{ onBridgeReady(); } |
其中 YOUR_PARAMS 是参数转换成json格式直接渲染至页面便可。
若是参数没错的话,那么就能够顺利的调起支付窗口了。
APP支付是在开放平台中申请下来的,appId和apiKey都是不同的。而jsapi支付实质就是公众号支付,是在公众平台中申请获得的。因此,在这里,须要注意一下。
重要的来了,能体现后台的重要性的地方终于来了 —
官方文档写得也很详细了(不得不说,微信的开发文档真的很清晰。很容易找到。就是没有详细的步骤区分)。
首先须要申明的是:异步通知的URL是必须能在公网访问的,并且,必须不能携带参数。
也就是说,http://domain.com/payment/wxpay/notify.php 是没问题的,可是 http://domain.com/payment/notify.php?payment_code=wxpay 这样的URL是不行的。若是要想达到这种效果,要不服务器(Nginx , Apache)进行rewrite,要不在notify.php 中,手动修改 $_GET
中的参数。
返回的数据,都是一致的:
这时候,商户后台拿到这些异步通知的数据进行简单的校验便可,而后修改商户中相应订单的支付状态。
校验返回码是否成功
$d = $this->xmlToArray(file_get_contents('php://input')); if (empty($d)) { throw new Exception(__METHOD__); } if ($d['return_code'] != 'SUCCESS') { throw new Exception($d['return_msg']); } if ($d['result_code'] != 'SUCCESS') { throw new Exception("[{$d['err_code']}]{$d['err_code_des']}"); } |
对返回数据进行校验
和请求 prepayId
时处理数据的方式差很少,先取出签名 sign
,而后除去签名后,进行字典排序,以 {key}={value}
的方式进行组合,并在最后加上 &key={apiKey}
获得待校验字符串,最后,将待校验字符串进行MD5加密,和签名进行比较,若一致则校验成功,而且支付成功,而后后台作相应操做。
if (!$this->verify($d)) { throw new Exception("Invalid signature"); } // 验证函数 if (empty($d['sign'])) { return false; } $sign = $d['sign']; unset($d['sign']); return $sign == $this->sign($d); |
有支付确定就会有退款。微信的退款操做也是很简单,并且退款速度很是快,测试时基本都是秒退。
可是退款是有注意事项的:
【证书获取方式:】
微信支付接口中,涉及资金回滚的接口会使用到商户证书,包括退款、撤销接口。商家在申请微信支付成功后,收到的相应邮件后,能够按照指引下载API证书,也能够按照如下路径下载:微信商户平台(pay.weixin.qq.com)–>帐户中心–>帐户设置–>API安全–>证书下载。
微信退款程序流程:
设置退款时得参数。
请求的参数有:
签名仍是老规矩(默认是MD5方式),先将全部参数进行字典排序,而后以$key=$value
的形式用&
字符拼接成字符串,最后将拼上 &key=YOUR_APIKEY
的待签名字符串进行MD5加密便可。
将参数列表转换成XML格式。
发送退款请求。
退款请求须要携带微信上下载的证书,请保证证书存放路径外网不能直接访问。
解析请求结果。
当返回的结果中, return_code
和 result_code
均为 SUCCESS
,即为退款申请成功。更多返回结果,请移步至 官网
微信支付的官方开发文档其实算是很详细了,传递的参数,返回结果,若是判断是否成功,都写的很好。只是,开发中的逻辑过程须要本身慢慢摸索,理清思路后,开发起来其实都是很迅速的。
可是,开发微信支付时,须要留个心,须要将全部涉及到的微信后台提供的数据当心保存(好比AppSecret,一当忘记只能重置。)