微信刷卡支付实现(普通商户)
文章包含查询订单、撤销订单、申请退款、查询退款
官方参考文档
一、微信公众号配置
- 微信公众号申请
地址
- 申请流程
- 记下公众号开发者ID(appid)
- 申请商户
- 在微信公众平台完成商户申请。
- 设置API安全
1、下载API证书。
2、设置并记下API秘钥(很重要)。
二、功能的具体实现
- 下载官方Demo
- 创建MyConfig类并继承WXPayConfig抽象类
- 查看demo中的README.md文件。
- 将appid、mchid、key以及API证书路径添加到方法中。
具体代码:
public class MyConfig extends WXPayConfig {
private byte[] certData;
public MyConfig() throws Exception {
String certPath = "C:/Users/syf/Desktop/cert/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();
}
@Override
Public String getAppID() {
return "w"; //添加appid
}
@Override
Public String getMchID() {
return "; //添加mchid
}
@Override
Public String getKey() {
return "ig5jed04hrmo"; //添加key
}
@Override
InputStream getCertStream() {
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
@Override
IWXPayDomain getWXPayDomain() {
IWXPayDomain iwxPayDomain = new IWXPayDomain() {
public DomainInfo getDomain(WXPayConfig config) {
return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API, true);
}
public void report(String domain, long elapsedTimeMillis, Exception ex) {
}
};
return iwxPayDomain;
}
}
- 将整个demo中复制到项目中。
- 刷卡支付
应用场景:
收银员使用扫码设备读取微信用户刷卡授权码以后,二维码或条码信息传送至商户收银台,由商户收银台或者商户后台调用该接口发起支付。
1.1、创建controller类和刷卡支付方法
1.2、创建MyConfig对象和WXPay对象方法。正式环境时:new WXPay(config),若使用沙箱环境:WXPay wxpay = new WXPay(config, true, true)。
1.3、 创建map对象,将必填字段(除appid,mch_id,nonce_str,sign,sign_type。microPay方法中已经字段将这些字段添加到参数中)添加到map中(注意查看参数要求!)。
1.4、调用microPay方法并传入map参数。
1.5、microPay方法返回Map类型参数。仔细阅读返回参数字段以及条件。根据返回结果决定下一步操作。(注意return_code和result_code返回不同状态时返回的参数)
提醒1:提交支付请求后微信会同步返回支付结果。当返回结果为“系统错误”时,商户系统等待5秒后调用【查询订单API】,查询支付实际交易结果;当返回结果为“USERPAYING”时,商户系统可设置间隔时间(建议10秒)重新查询支付结果,直到支付成功或超时(建议30秒);
提醒2:在调用查询接口返回后,如果交易状况不明晰,请调用【撤销订单API】,此时如果交易失败则关闭订单,该单不能再支付成功;如果交易成功,则将扣款退回到用户账户。当撤销无返回或错误时,请再次调用。注意:请勿扣款后立即调用【撤销订单API】,建议至少15秒后再调用。撤销订单API需要双向证书。
具体代码实现:
public MyJsonResult microPay(HttpServletRequest request,HttpServletResponse response) throws Exception{
MyConfig config = new MyConfig();
WXPay wxpay = new WXPay(config);//上线环境用
//WXPay wxpay = new WXPay(config, true, true);//使用沙箱环境
String total_fee=request.getParameter("total_fee");
String auth_code=request.getParameter("auth_code");
//请求刷卡支付用参数map
Map<String, String> data = new HashMap<String, String>();
//接收返回参数
Map<String, String> resp = new HashMap<String, String>();
data.put("auth_code", auth_code);
data.put("device_info", "qiang1");
data.put("body", "停车收费!");
data.put("out_trade_no", WXPayUtil.generateNonceStr());
data.put("total_fee", total_fee);
data.put("spbill_create_ip","218.28.14.143");
try {
resp = wxpay.microPay(data);
} catch (Exception e) {
e.printStackTrace();
}
//请求成功
if("SUCCESS".equals(resp.get("return_code"))){
//不需要输如密码时
if("SUCCESS".equals(resp.get("result_code"))){
//验证签名
if(wxpay.isPayResultNotifySignatureValid(resp)) {
return new MyJsonResult(ConstantCode.SUCCESS,"支付成功! 已验证签名!");
}
//String transaction_id=resp.get("transaction_id");
//return new MyJsonResult(ConstantCode.SUCCESS,"支付成功!"+transaction_id);
//验证签名失败,做出相应的处理
return new MyJsonResult(ConstantCode.ERROR,"签名验证失败!");
} //需要用户输入密码
if("FAIL".equals(resp.get("result_code"))){
if("USERPAYING".equals(resp.get("err_code"))){
//查询订单
Map<String,String> qRequestMap = new HashMap<String, String>();
qRequestMap.put("out_trade_no", data.get("out_trade_no"));
//
Map<String,String> resultMap=new HashMap<String,String>();
//等待5秒,查询订单
try {
Thread.sleep(5000);
} catch (InterruptedException e1) {
//捕获异常
e1.printStackTrace();
}
try {
resultMap=checkOrder(wxpay,qRequestMap);
} catch (Exception e2) {
// TODO Auto-generated catch block
e2.printStackTrace();
}
if("SUCCESS".equals(resultMap.get("trade_state"))){
String transaction_id=resultMap.get("transaction_id");
return new MyJsonResult(ConstantCode.SUCCESS,"支付成功!");
}
//每个10秒调用订单查询接口,查看支付结果
for(int i=0;i<3;i++){
try {
Thread.sleep(10000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
resultMap=checkOrder(wxpay,qRequestMap);
} catch (Exception e2) {
// TODO Auto-generated catch block
e2.printStackTrace();
}
if("SUCCESS".equals(resultMap.get("trade_state"))){
String transaction_id=resultMap.get("transaction_id");
return new MyJsonResult(ConstantCode.SUCCESS,"支付成功!");
}
System.out.println("查询支付中。。。。");
}
//撤销订单
Map<String,String> reversResp =new HashMap<String,String>();
try {
reversResp = wxpay.reverse(qRequestMap);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(reversResp!=null){
if("SUCCESS".equals(reversResp.get("return_code")) &&
"SUCCESS".equals(reversResp.get("result_code"))){
return new MyJsonResult(ConstantCode.SUCCESS, "订单已撤销,请重新支付");
}
return new MyJsonResult(ConstantCode.ERROR, reversResp.get("err_code_des"));
}
}else{
return new MyJsonResult(ConstantCode.ERROR, resp.get("err_code_des"));
}
}
}
//请求失败
return new MyJsonResult(ConstantCode.ERROR, resp.get("err_code_des"));
}
- 使用仿真测试系统:
仿真系统的API协议与正式API完全相同(API接口文档)。只需将正式API的调用URL增加一层sandboxnew路径,即可对接到仿真系统。仿真系统与生产环境完全独立,包括存储层。商户在仿真系统所做的所有交易(如下单、支付、查询)均为无资金流的假数据。
接入仿真系统的交互流程示例:
(1)、商户发起刷卡支付请求,使用POST方式调用 https://api.mch.weixin.qq.com/sandboxnew/pay/micropay
(2)、带sandboxnew 的https请求会被nginx路由到仿真系统。仿真系统根据支付金额(total_fee字段)返回预期报文给商户。同时,落地该笔请求数据;
(3)、商户发起查单,调用 https://api.mch.weixin.qq.com/sandboxnew/pay/orderquery,带上微信订单号(transaction_id)或商户内部单号(out_trade_no);
(4)、仿真系统收到查单请求后,根据单号及金额返回预期的查单结果给商户;
(5)、商户下载对账单,调用 https://api.mch.weixin.qq.com/sandboxnew/pay/downloadbill,仿真系统返回固定的账单格式给商户。
注:账单内容不一定与商户在仿真系统产生的交易完全相同。
注:验收仿真测试系统的API验签密钥需从API获取
- 获取沙箱key查看
- 获取API验签秘钥方法参考
public String retrieveSandboxSignKey(WXPayConfig config, WXPay wxPay,Map<String,String> params) {
try {
String strXML = wxPay.requestWithoutCert("/sandboxnew/pay/getsignkey",
params, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs());
if (StringUtils.isBlank(strXML)) {
return null;
}
Map<String, String> result = WXPayUtil.xmlToMap(strXML);
//log.info("retrieveSandboxSignKey:" + result);
System.out.println(result.get("return_code")+"\n"+result.get("return_msg"));
if ("SUCCESS".equals(result.get("return_code"))) {
return result.get("sandbox_signkey");
}
if("FAIL".equals(result.get("return_code"))){
return result.get("return_code");
}
return null;
} catch (Exception e) {
//log.error("获取sandbox_signkey异常", e);
return null;
}
}
- 查询订单(不需要证书)
1、创建map对象,添加商户订单号(out_trade_no)请求参数。(appid,mch_id,nonce_str,sign,sign_type不用添加,orderQuery方法会自动将这些字段添加到参数中)添加到map中(注意查看参数要求!)。
2、调用查询订单方法orderQuery方法,根据返回参数判断订单支付情况。(注意查看返回参数!)。
具体代码实现
MyConfig config = new MyConfig();
WXPay wxpay = new WXPay(config);
Map<String,String> qRequestMap = new HashMap<String, String>();
qRequestMap.put("out_trade_no", out_trade_no));
//接收返回参数
Map<String,String> resultMap=new HashMap<String,String>();
try {
queryResp= wxpay.orderQuery(map);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if("SUCCESS".equals(queryResp.get("return_code")) && "SUCCESS".equals(queryResp.get("result_code"))
&& "SUCCESS".equals(queryResp.get("trade_state"))){
System.out.println("支付成功!");
returnQuery.put("trade_state", queryResp.get("trade_state"));
returnQuery.put("transaction_id", queryResp.get("transaction_id"));
return returnQuery;
}
- 撤销订单(需要证书)
1、创建map对象,添加商户订单号(out_trade_no)或者微信订单号(transaction_id)请求参数。(appid,mch_id,nonce_str,sign,sign_type不用添加,reverse方法会自动将这些字段添加到参数中)添加到map中(注意查看参数要求!)。
2、调用查询订单方法reverse方法,根据返回参数判断订单撤销状态。(注意查看返回参数!)。
具体代码实现
MyConfig config = new MyConfig();
WXPay wxpay = new WXPay(config);
Map<String,String> reversResp =new HashMap<String,String>();
try {
reversResp = wxpay.reverse(qRequestMap);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(reversResp!=null){
if("SUCCESS".equals(reversResp.get("return_code")) &&
"SUCCESS".equals(reversResp.get("result_code"))){
return new MyJsonResult(ConstantCode.SUCCESS, "订单已撤销,请重新支付");
}
return new MyJsonResult(ConstantCode.ERROR, reversResp.get("err_code_des"));
}else{
return new MyJsonResult(ConstantCode.ERROR, resp.get("err_code_des"));
}
- 申请退款(需要证书)
1、创建map对象,添加请求参数。(appid,mch_id,nonce_str,sign,sign_type不用添加,reverse方法会自动将这些字段添加到参数中)添加到map中(注意查看参数要求!)。
2、调用查询订单方法refund方法,根据返回参数判断退款请求。(注意查看返回参数!)。
具体代码实现
public MyJsonResult refundPay(HttpServletRequest request,HttpServletResponse response) throws Exception {
MyConfig config = new MyConfig();
WXPay wxpay = new WXPay(config);
String total_fee=request.getParameter("total_fee");
String out_trade_no=request.getParameter("out_trade_no");
String refund_fee=request.getParameter("refund_fee");
System.out.println(total_fee +"\n"+out_trade_no+"\n"+refund_fee);
Map<String, String> data = new HashMap<String, String>();
//接收返回参数
Map<String, String> resp = null;
data.put("total_fee", total_fee);
data.put("out_trade_no", out_trade_no);
data.put("refund_fee", refund_fee);
//商户退款订单号
data.put("out_refund_no",WXPayUtil.generateNonceStr().substring(0,11));
try {
resp = wxpay.refund(data);
} catch (Exception e) {
e.printStackTrace();
}
if(resp!=null){
//请求成功
if("SUCCESS".equals(resp.get("return_code")) ){
if("SUCCESS".equals(resp.get("result_code"))){
return new MyJsonResult(ConstantCode.SUCCESS,"退款申请提交成功!");
}
return new MyJsonResult(ConstantCode.ERROR,resp.get("err_code_des"));
}
return new MyJsonResult(ConstantCode.ERROR,resp.get("return_msg"));
}
return new MyJsonResult(ConstantCode.ERROR,"位未知错误!");
}
- 退款查询(不需要证书)
1、创建map对象,添加请求参数。(appid,mch_id,nonce_str,sign,sign_type不用添加,refundQuery方法会自动将这些字段添加到参数中)(注意查看参数要求!)。
2、调用查询订单方法refundQuery方法,根据返回参数判断退款状态。(注意查看返回参数!)。
参考代码
public MyJsonResult refundQuery(HttpServletRequest request,HttpServletResponse response) throws Exception {
MyConfig config = new MyConfig();
WXPay wxpay = new WXPay(config);
String out_trade_no=request.getParameter("out_trade_no");
//请求参数
Map<String, String> data = new HashMap<String, String>();
//接收返回参数
Map<String, String> resp = new HashMap<String, String>();
data.put("out_trade_no", out_trade_no);
try {
resp = wxpay.refundQuery(data);
} catch (Exception e) {
e.printStackTrace();
}
if(resp!=null){
if("SUCCESS".equals(resp.get("return_code")) ){
if ("SUCCESS".equals(resp.get("result_code"))) {
return new MyJsonResult(ConstantCode.SUCCESS,resp.get("refund_status_0"));
}
return new MyJsonResult(ConstantCode.ERROR, resp.get("err_code_des"));
}
return new MyJsonResult(ConstantCode.ERROR, resp.get("return_msg"));
}
return new MyJsonResult(ConstantCode.ERROR,"位未知错误!");
}