概述
Native支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。详情见
开发文档
。
前期准备
- 注册微信支付商户号,获取商户号 mch_id、key;
- 申请微信认证的服务号、政府或媒体类订阅号、小程序、APP、企业微信其中之一,并与商户号绑定。获取 appid。
开发步骤
Native支付可分为两种模式,商户根据支付场景选择相应模式。本文仅介绍模式二。
业务流程时序图
业务流程说明:
(1)商户后台系统根据用户选购的商品生成订单。
(2)用户确认支付后调用微信支付【
统一下单API
】生成预支付交易;
(3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
(4)商户后台系统根据返回的code_url生成二维码。
(5)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
(6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
(7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
(8)微信支付系统根据用户授权完成支付交易。
(9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
(10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
(11)未收到支付通知的情况,商户后台系统调用【
查询订单API
】。
(12)商户确认订单已支付后给用户发货。
统一下单
SDK
API 详见
统一下单API
。
下载官方 SDK ,或通过 maven 获取依赖。推荐使用后者。
<!-- wxpay-sdk -->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
SDK 中集成了一个 WXPayConfig.java 的接口,我们需要写一个实现类,存放配置信息。
public class WXPayConfigImpl implements WXPayConfig {
// 设置证书,没有的话注释掉
// private byte[] certData;
// public WXPayConfigImpl() throws Exception {
// String certPath = "/path/to/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();
// }
// set appid
public static void setAppId(String appId) {
WXPayConfigImpl.appId = "";
}
// set mch_id
public static void setMchId(String mchId) {
WXPayConfigImpl.mchId = "";
}
// set key
public static void setKey(String key) {
WXPayConfigImpl.key = "";
}
public InputStream getCertStream() {
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
// 异步通知地址
public String getNotifyUrl() {
return "http://www.xxx.com/wxNotify";
}
public int getHttpConnectTimeoutMs() {
return 8000;
}
public int getHttpReadTimeoutMs() {
return 10000;
}
// IWXPayDomain getWXPayDomain() {
// return null;
// }
}
获取 code_url
核心代码如下:
public class WXPayService {
/**
* 微信扫码下单 native
* @param outTradeNo
* @param totalFee
* @param body
* @param productId
* @param attach 自定义参数,通知中原样返回。
* @return
* @throws Exception
*/
public static String wxUnifiedOrder(String outTradeNo,
String totalFee,
String body,
String productId,
String attach) throws Exception {
WXPayConfigImpl config = null;
WXPay wxpay = null;
config = new WXPayConfigImpl();
// 异步通知地址
String notifyUrl = config.getNotifyUrl();
// 使用沙箱环境
// wxpay = new WXPay(config, WXPayConstants.SignType.MD5, true);
// 默认使用MD5,不使用沙箱环境
wxpay = new WXPay(config);
Map<String, String> data = new HashMap<String, String>();
// 商品描述
data.put("body", body);
// 商户订单号
data.put("out_trade_no", outTradeNo);
// 标价金额
data.put("total_fee", totalFee);
// 产品id
data.put("product_id", productId);
// 终端IP:调用微信支付API的机器IP
data.put("spbill_create_ip", "221.12.4.52");
// 交易类型:此处指定为扫码支付
data.put("trade_type", "NATIVE");
// 异步通知 url
data.put("notify_url", notifyUrl);
// 自定义参数
data.put("attach", attach);
Map<String, String> resp = null;
try {
resp = wxpay.unifiedOrder(data);
} catch (Exception e) {
e.printStackTrace();
}
String codeUrl = resp.get("code_url");
System.out.println("============= 微信返回结果 =============");
System.out.println(resp);
return codeUrl;
}
}
将获取得到的 codeUrl 通过第三方库转为二维码,供用户扫码。
异步通知
异步通知
的请求路径在统一下单接口中设置。
@RequestMapping("wxNotify")
public class WXController {
public void notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 拿到微信回调信息
InputStream inputStream = request.getInputStream();
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
StringBuffer sb = new StringBuffer();
// 将微信回调信息转为字符串
String line;
while ((line = in.readLine()) != null) {
sb.append(line);
}
in.close();
inputStream.close();
String strXml = sb.toString();
WXPayConfigImpl config = new WXPayConfigImpl();
Map<String, String> map = WXPayUtil.xmlToMap(strXml);
System.out.println(strXml);
// 获取业务信息
String outTradeNo = map.get("out_trade_no");
String totalFee = map.get("total_fee");
String appId = map.get("appid");
String mchId = map.get("mch_id");
String transactionId = map.get("transaction_id");
String resultCode = map.get("result_code");
String attach = map.get("attach");
// 验签
boolean signatureValid = WXPayUtil.isSignatureValid(strXml, config.getKey());
// todo 这里写代码
/* 实际验证过程建议商户务必添加以下校验:
1、需要验证该通知数据中的 out_trade_no 是否为商户系统中创建的订单号
2、判断 total_fee 是否确实为该订单的实际金额(即商户订单创建时的金额)
*/
PrintWriter writer = response.getWriter();
// 判断签名是否正确
if (signatureValid) {
// 判断回调信息是否成功
if ("SUCCESS".equals(map.get("result_code"))) {
// todo 根据不同业务类型处理不同业务
// 通知微信订单处理成功
String noticeStr = setXML("SUCCESS", "");
writer.write(noticeStr);
writer.flush();
}
} else {
// 通知微信订单处理失败
String noticeStr = setXML("FAIL", "");
writer.write(noticeStr);
writer.flush();
}
}
private static String setXML(String return_code, String return_msg) {
return "<xml><return_code><![CDATA[" + return_code + "]]></return_code><return_msg><![CDATA[" + return_msg + "]]></return_msg></xml>";
}
}
沙箱测试
编写完代码,我们就要开始沙箱测试了。
微信的流程是,写完代码之后,必须使用沙箱测试,对测试结果进行验收,验收通过才可以使用正式的微信商户号,否则会返回“商户无此接口权限”。
沙箱验收过程可查看
支付验收指引
。
-
使用正式的 mch_id,商户key 来获取沙箱密钥 sandbox_signkey。
传送门
-
通过调用接口
https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey
,获取验签密钥。请求方式为 POST,发送 json 字符串。可以使用 Postman 调用此接口,获取验签密钥。
返回的结果如下
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<retmsg><![CDATA[ALSDFJASLDFKJASLDFKJASDLFJ]]></retmsg>
<retcode><![CDATA[0]]></retcode>
</xml>
- 使用沙箱环境
// 使用沙箱环境
wxpay = new WXPay(config, WXPayConstants.SignType.MD5, true);
-
测试结果
-
微信的沙箱测试
不会
发送异步通知,等待5s左右后,会在控制台打印出模拟异步通知的结果。若无通知,调用
查询订单
接口进行查询。 -
必须按照微信的
支付验收用例
来设置金额,否则会返回错误信息,“金额不合法”。 -
各项支付验收用例测试结束后,按照要求调用【
查询订单
】进行查询,查询结束后验收结束。 - 在官方微信号查询验收结果。验收通过后,即可使用正式账号。
-
微信的沙箱测试
查询订单
controller 层
public class WXController {
public void orderQuery(String key, String appid, String mchId, String outTradeNo, String transactionId) throws Exception {
WXPayConfigImpl.setKey(key);
WXPayConfigImpl.setAppId(appid);
WXPayConfigImpl.setMchId(mchId);
WXPayConfigImpl config = new WXPayConfigImpl();
Map<String, String> map = WXPayService.orderQuery(outTradeNo, transactionId);
String attach = map.get("attach");
String appId = map.get("appid");
String totalFee = map.get("total_fee");
String strXml = WXPayUtil.mapToXml(map);
String resultCode = map.get("result_code");
transactionId = map.get("transaction_id");
System.out.println("strXml: " + strXml);
}
}
service 层
/**
* 微信订单查询
*
* @param outTradeNo
* @param transactionId
* @return
* @throws Exception
*/
public static Map<String, String> orderQuery(String outTradeNo, String transactionId) throws Exception {
WXPayConfigImpl config = null;
WXPay wxpay = null;
config = new WXPayConfigImpl();
// wxpay = new WXPay(config, WXPayConstants.SignType.MD5, true); // 使用沙箱
wxpay = new WXPay(config);// 默认使用MD5,不使用沙箱环境
Map<String, String> data = new HashMap<String, String>();
if (!StringUtils.isEmpty(outTradeNo)) {
data.put("out_trade_no", outTradeNo);
}
if (!StringUtils.isEmpty(transactionId)) {
data.put("transaction_id", transactionId);
}
return wxpay.orderQuery(data);
}
遇到的问题
-
appid 与商户号绑定:
问题描述
:调用微信统一下单接口时,返回失败。错误信息为:appid 与 mch_id 不匹配。
原因分析
:appid 与 mch_id 不匹配,在 mch_id 没错的情况下,自然是 appid 出了问题。如果 appid 不合法,或者没有与 mch_id 绑定,就会报这个错。在微信商户账号界面绑定一个 appid 到 mch_id 下。
解决方法
:在微信商户账号界面绑定一个 appid 到 mch_id 下。
“申请微信认证的服务号、政府或媒体类订阅号、小程序、APP、企业微信其中之一,并与商户号绑定。”
-
商户订单号重复:
问题描述
:在微信支付页面,用户获取了二维码之后并没有付款,或是关闭了页面,或是暂时不想支付等原因,这很常见。此时再次对同一订单进行微信支付,统一下单接口可能会报“INVALID_REQUEST 商户订单号重复”的错误。
原因分析
:同一订单号调用微信的统一下单接口时,若其他参数与首次不同,则微信认为是两个订单。两个订单使用了同一个订单号,这是不被允许的。
解决方法
:订单重入时,要求参数值与原请求一致,请确认参数问题。包括 attach 这种商户自定义参数,都必须一致。
[end]