0
点赞
收藏
分享

微信扫一扫

微信支付-java实现微信支付-后端篇


微信支付系列文章

  1. 微信支付-java后端实现
  2. 微信支付-vue 前端实现

java demo: 下载地址文章底部

技术栈

  • Spring boot
  • java
  • XML (微信在http协议中数据传输方案)
  • MD5 签名

微信支付术语

  • openid (OpenID是公众号一对一对应用户身份的标识)
  • app_id (公众号id,登录微信公众号–开发–基本配置中获得;)
  • key (收款商户后台进行配置,登录微信商户平台–账户中心–API安全-设置秘钥,设置32位key值;)
  • mch_id (收款商家商户号;)
  • certPath (API证书, 登录微信商户平台–账户中心-API安全-下载证书)

后端流程

  1. 统一下单
  2. 前端调起微信支付必要参数 (需加密)
  3. 订单结果主动通知 (回调接口)
  4. 查询订单结果
  5. 结束订单支付接口(关闭订单,支付订单关闭)

代码

微信总共支持多种语言的sdk, 在官网可以下载例子, java程序也可以引入微信支付的sdk包, 但是github上的sdk已经很久没有更新了, 最好的选择, 也是我的选择, 在官网上下载sdk项目, 将其中所有java类copy到自己的项目中.

根据微信sdk生成配置类 WXPayConfig

创建IWxPayConfig.class, 继承sdk WXPayConfig.class, 实现sdk中部分抽象方法, 读取本地证书, 加载到配置类中.
package core.com.chidori.wxpay;

import core.com.wxpay.IWXPayDomain;
import core.com.wxpay.WXPayConfig;
import core.com.wxpay.WXPayConstants;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

@Service
public class IWxPayConfig extends WXPayConfig { // 继承sdk WXPayConfig 实现sdk中部分抽象方法

private byte[] certData;

@Value("${vendor.wx.config.app_id}")
private String app_id;

@Value("${vendor.wx.pay.key}")
private String wx_pay_key;

@Value("${vendor.wx.pay.mch_id}")
private String wx_pay_mch_id;

public IWxPayConfig() throws Exception { // 构造方法读取证书, 通过getCertStream 可以使sdk获取到证书
String certPath = "/data/config/chidori/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 app_id;
}

@Override
public String getMchID() {
return wx_pay_mch_id;
}

@Override
public String getKey() {
return wx_pay_key;
}

@Override
public InputStream getCertStream() {
return new ByteArrayInputStream(this.certData);
}

@Override
public IWXPayDomain getWXPayDomain() { // 这个方法需要这样实现, 否则无法正常初始化WXPay
IWXPayDomain iwxPayDomain = new IWXPayDomain() {
@Override
public void report(String domain, long elapsedTimeMillis, Exception ex) {

}
@Override
public DomainInfo getDomain(WXPayConfig config) {
return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API, true);
}
};
return iwxPayDomain;
}
}

发起统一下单 AND 前端调起微信支付必要参数

// 发起微信支付
WXPay wxpay = null;
Map<String, String> result = new HashMap<>();
try {
// ******************************************
//
// 统一下单
//
// ******************************************
wxpay = new WXPay(iWxPayConfig); // *** 注入自己实现的微信配置类, 创建WXPay核心类, WXPay 包括统一下单接口

Map<String, String> data = new HashMap<String, String>();
data.put("body", "订单详情");
data.put("out_trade_no", transOrder.getGlobalOrderId()); // 订单唯一编号, 不允许重复
data.put("total_fee", String.valueOf(transOrder.getOrderAmount().multiply(new BigDecimal(100)).intValue())); // 订单金额, 单位分
data.put("spbill_create_ip", "192.168.31.166"); // 下单ip
data.put("openid", openId); // 微信公众号统一标示openid
data.put("notify_url", "http://wxlj.oopmind.com/payCallback"); // 订单结果通知, 微信主动回调此接口
data.put("trade_type", "JSAPI"); // 固定填写

logger.info("发起微信支付下单接口, request={}", data);
Map<String, String> response = wxpay.unifiedOrder(data); // 微信sdk集成方法, 统一下单接口unifiedOrder, 此处请求 MD5加密 加密方式
logger.info("微信支付下单成功, 返回值 response={}", response);
String returnCode = response.get("return_code");
if (!SUCCESS.equals(returnCode)) {
return null;
}
String resultCode = response.get("result_code");
if (!SUCCESS.equals(resultCode)) {
return null;
}
String prepay_id = response.get("prepay_id");
if (prepay_id == null) {
return null;
}

// ******************************************
//
// 前端调起微信支付必要参数
//
// ******************************************
String packages = "prepay_id=" + prepay_id;
Map<String, String> wxPayMap = new HashMap<String, String>();
wxPayMap.put("appId", iWxPayConfig.getAppID());
wxPayMap.put("timeStamp", String.valueOf(Utility.getCurrentTimeStamp()));
wxPayMap.put("nonceStr", Utility.generateUUID());
wxPayMap.put("package", packages);
wxPayMap.put("signType", "MD5");
// 加密串中包括 appId timeStamp nonceStr package signType 5个参数, 通过sdk WXPayUtil类加密, 注意, 此处使用 MD5加密 方式
String sign = WXPayUtil.generateSignature(wxPayMap, iWxPayConfig.getKey());

// ******************************************
//
// 返回给前端调起微信支付的必要参数
//
// ******************************************
result.put("prepay_id", prepay_id);
result.put("paySign", sign);
result.putAll(wxPayMap);
return result;
} catch (Exception e) {
}

回调结果处理

核心是支付订单回调时, 需校验加密签名是否匹配, 防止出现模拟成功通知

@RequestMapping(value = "/payCallback", method = RequestMethod.POST)
public String payCallback(HttpServletRequest request, HttpServletResponse response) {
logger.info("进入微信支付异步通知");
String resXml="";
try{
//
InputStream is = request.getInputStream();
//将InputStream转换成String
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
try {
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
resXml=sb.toString();
logger.info("微信支付异步通知请求包: {}", resXml);
return wxTicketService.payBack(resXml);
}catch (Exception e){
logger.error("微信支付回调通知失败",e);
String result = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
return result;
}
}

@Override
public String payBack(String notifyData) {
logger.info("payBack() start, notifyData={}", notifyData);
String xmlBack="";
Map<String, String> notifyMap = null;
try {
WXPay wxpay = new WXPay(iWxPayConfig);

notifyMap = WXPayUtil.xmlToMap(notifyData); // 转换成map
if (wxpay.isPayResultNotifySignatureValid(notifyMap)) {
// 签名正确
// 进行处理。
// 注意特殊情况:订单已经退款,但收到了支付结果成功的通知,不应把商户侧订单状态从退款改成支付成功
String return_code = notifyMap.get("return_code");//状态
String out_trade_no = notifyMap.get("out_trade_no");//订单号

if (out_trade_no == null) {
logger.info("微信支付回调失败订单号: {}", notifyMap);
xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
return xmlBack;
}

// 业务逻辑处理 ****************************
logger.info("微信支付回调成功订单号: {}", notifyMap);
xmlBack = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[SUCCESS]]></return_msg>" + "</xml> ";
return xmlBack;
} else {
logger.error("微信支付回调通知签名错误");
xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
return xmlBack;
}
} catch (Exception e) {
logger.error("微信支付回调通知失败",e);
xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
}
return xmlBack;
}

注意点

  1. 统一下单的签名和后续前端拉取微信支付的签名需要统一, 也就是都采用MD5加密, 如果2者不同, 会导致前端拉取微信支付fail, 这是一个巨大的坑, 因为这个原因调试了好久, 微信在文档里没有明确标出统一下单的签名校验方式 需要和前端拉取微信支付的签名校验保持一致.
    微信sdk里的源码需要针对这个问题调整一下, 调整如下:
    WXPay类需要修改下加密判断,在WXPay构造方法中,调整如下

    public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport, final boolean useSandbox) throws Exception {
    this.config = config;
    this.notifyUrl = notifyUrl;
    this.autoReport = autoReport;
    this.useSandbox = useSandbox;
    if (useSandbox) {
    this.signType = SignType.MD5; // 沙箱环境
    }
    else {
    this.signType = SignType.MD5; // 将这里的加密方式修改为SignType.MD5, 保持跟前端吊起微信加密方式保持一致
    }
    this.wxPayRequest = new WXPayRequest(config);
    }

结束语

博客

请移步: https://www.oopmind.com

Demo下载地址

联系方式

微信公众号

预览图

举报

相关推荐

0 条评论