本以为没有机会接触鼎鼎大名的支付宝和微信接口(公司本身是做第三方支付的),最近由于一个售货机项目需要对接银联,支付宝和微信接口,因为我自身已经对接了银联,之后根据安排,由我对接微信的相关接口。话不多说,让我们开启踩坑之旅。
对接微信支付接口准备工作:
1.微信支付文档地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
2.微信支付签名验证地址:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=20_1
3.下载微信支付官方demo(根据自身需求):https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1
4.申请相应的账号,包括:微信支付商户号,公众号ID:appid,appsecret,key,以及业务需要有可能用到的证书文件
进入微信支付文档首页,根据我们使用微信支付的不同场景选择不同的文档来进行阅读,否则,你就有可能因为文档阅读错误导致demo跑不通而到处找错。
仔细阅读方框中的文档说明后,我们就可以进入到API列表中的统一下单接口中,准备对接此接口
请务必注意传递字段的描述,微信支付文档中有很多地方在传递参数中,如果某个必传参数触发某个条件,那就需要多传递另外的参数,务必看仔细后再用demo去进行调用。
此处为什么说要修改官方demo,因为官方demo只提供了必要的结构和代码,相关我们自己的参数还需要自己去实现,打比方说:微信demo中有一个类:WXPayConfig,是一个抽象类,你就必须先实现里面的方法,然后自己写一个测试用例进行调用和调试。
和官方demo相比,我只增加了这两个类来分别实现相应的接口,统一下单接口中的测试和修改基本都在WXPay中完成,下面我贴上这两个类的代码,仅供参考。
WxPayConfigImpl类代码:
package com.github.wxpay.sdk;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class WxPayConfigImpl extends WXPayConfig{
private byte[] certData;
private static WxPayConfigImpl INSTANCE;
//公众号AppID
private String appId = "";
//商户号
private String mchId = "";
//key
private String key = "";
private WxPayConfigImpl() throws Exception{
// String certPath = "D://CERT/common/apiclient_cert.p12";
// File file = new File(certPath);
// InputStream certStream = new FileInputStream(file);
// this.certData = new byte[(int) file.length()];
// certStream.read(this.certData);
// certStream.close();
}
public static WxPayConfigImpl getInstance() throws Exception{
if (INSTANCE == null) {
synchronized (WxPayConfigImpl.class) {
if (INSTANCE == null) {
INSTANCE = new WxPayConfigImpl();
}
}
}
return INSTANCE;
}
public String getAppID() {
return this.appId;
}
public String getMchID() {
return this.mchId;
}
public String getKey() {
return this.key;
}
public InputStream getCertStream() {
ByteArrayInputStream certBis;
certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
public int getHttpConnectTimeoutMs() {
return 2000;
}
public int getHttpReadTimeoutMs() {
return 10000;
}
IWXPayDomain getWXPayDomain() {
return WXPayDomainSimpleImpl.instance();
}
public String getPrimaryDomain() {
return "api.mch.weixin.qq.com";
}
public String getAlternateDomain() {
return "api2.mch.weixin.qq.com";
}
@Override
public int getReportWorkerNum() {
return 1;
}
@Override
public int getReportBatchSize() {
return 2;
}
}
WXPayDomainSimpleImp类代码:
package com.github.wxpay.sdk;
import org.apache.http.conn.ConnectTimeoutException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
/**
* Created by blaketang on 2017/6/16.
*/
public class WXPayDomainSimpleImpl implements IWXPayDomain {
private WXPayDomainSimpleImpl(){}
private static class WxpayDomainHolder{
private static IWXPayDomain holder = new WXPayDomainSimpleImpl();
}
public static IWXPayDomain instance(){
return WxpayDomainHolder.holder;
}
public synchronized void report(final String domain, long elapsedTimeMillis, final Exception ex) {
DomainStatics info = domainData.get(domain);
if(info == null){
info = new DomainStatics(domain);
domainData.put(domain, info);
}
if(ex == null){ //success
if(info.succCount >= 2){ //continue succ, clear error count
info.connectTimeoutCount = info.dnsErrorCount = info.otherErrorCount = 0;
}else{
++info.succCount;
}
}else if(ex instanceof ConnectTimeoutException){
info.succCount = info.dnsErrorCount = 0;
++info.connectTimeoutCount;
}else if(ex instanceof UnknownHostException){
info.succCount = 0;
++info.dnsErrorCount;
}else{
info.succCount = 0;
++info.otherErrorCount;
}
}
public synchronized DomainInfo getDomain(final WXPayConfig config) {
DomainStatics primaryDomain = domainData.get(WXPayConstants.DOMAIN_API);
if(primaryDomain == null ||
primaryDomain.isGood()) {
return new DomainInfo(WXPayConstants.DOMAIN_API, true);
}
long now = System.currentTimeMillis();
if(switchToAlternateDomainTime == 0){ //first switch
switchToAlternateDomainTime = now;
return new DomainInfo(WXPayConstants.DOMAIN_API2, false);
}else if(now - switchToAlternateDomainTime < MIN_SWITCH_PRIMARY_MSEC){
DomainStatics alternateDomain = domainData.get(WXPayConstants.DOMAIN_API2);
if(alternateDomain == null ||
alternateDomain.isGood() ||
alternateDomain.badCount() < primaryDomain.badCount()){
return new DomainInfo(WXPayConstants.DOMAIN_API2, false);
}else{
return new DomainInfo(WXPayConstants.DOMAIN_API, true);
}
}else{ //force switch back
switchToAlternateDomainTime = 0;
primaryDomain.resetCount();
DomainStatics alternateDomain = domainData.get(WXPayConstants.DOMAIN_API2);
if(alternateDomain != null)
alternateDomain.resetCount();
return new DomainInfo(WXPayConstants.DOMAIN_API, true);
}
}
static class DomainStatics {
final String domain;
int succCount = 0;
int connectTimeoutCount = 0;
int dnsErrorCount =0;
int otherErrorCount = 0;
DomainStatics(String domain) {
this.domain = domain;
}
void resetCount(){
succCount = connectTimeoutCount = dnsErrorCount = otherErrorCount = 0;
}
boolean isGood(){ return connectTimeoutCount <= 2 && dnsErrorCount <= 2; }
int badCount(){
return connectTimeoutCount + dnsErrorCount * 5 + otherErrorCount / 4;
}
}
private final int MIN_SWITCH_PRIMARY_MSEC = 3 * 60 * 1000; //3 minutes
private long switchToAlternateDomainTime = 0;
private Map domainData = new HashMap();
}
记下来就是展现你有没有仔细看文档的时候了,我们可以写个测试类,也可以直接写个main方法,我这里因为后续要集成到代码中,所以我直接用main方法直接运行
8SwC0RG5WpHXPZrykLxEq7chaYFrKCbd
L0000000000000000026
10
359
NATIVE
福优售货机
http://222.186.36.87:8086/paytest/weixin/payNotify
222.186.36.87
MD5
此处为使用NATIVE支付时传递的参数(此处传递的是必传参数,根据业务需要大家可以根据文档来灵活使用),这里需要给大家说一下微信的签名验证工具
一般情况下,只要正确调用了相关的接口,是不会出现签名不过的情况,但就是有一些拿到官方文档后大改特改的人,所以这个时候需要用到微信的签名验证工具,地址在博客开头。
具体用法:拿到上面我展现的xml文件(debug找到输出地方打印即可),放到下图的文本框中进行验证
最终进行签名的校验即可。
1.切记,必须拿到最新的商户参数和密钥,并且保证正确可用
2.拿到demo后全局浏览后,补充必要文件和参数
3.根据文档传递参数,注意文档的备注
4.参数签名为MD5,注意追踪demo默认签名类型
统一下单接口支付成功后,会返回你一串以weixin.开头的url地址,用二维码生成工具生成后,即可用微信扫一扫功能扫码付款了,注意,传递金额是以分为单位,测试时不要金额传递过多哟。
public void payNotify() {
try {
InputStream inputStream ;
StringBuffer sb = new StringBuffer();
inputStream = request.getInputStream();
String s ;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while ((s = in.readLine()) != null){
sb.append(s);
}
in.close();
inputStream.close();
//解析xml成map
Map m = new HashMap();
m = XMLUtil.doXMLParse(sb.toString());
System.out.println("微信支付,支付通知数据:"+m.toString());
//过滤空 设置 TreeMap
SortedMap
因为此处的回调代码已经集成到公司项目里面了,这里就只贴出相应的代码点到为止,相应的工具类大家可以到支付回调工具类下载,配套使用更佳哦。
总结:实际上,微信支付能对接全国的开发者是有其道理的,我遇到问题的时候在百度的时候碰到很多这样的语句,都说微信支付文档是大坑,确实,很多时候有很多问题都是自己不仔细,然后微信接口提示你签名错误,其实,只要认真看了微信官方文档,绝大多数问题都可以迎刃而解,很多时候都是自己不细心导致的。所以,有些时候,细心最重要。
下一波我将介绍微信退款和退款结果通知的相关内容,剧透一下下:退款结果通知又是一个小坑。