前一篇介绍了如何实现微信的统一下单,但在实际生产中,不建议直接使用。开发中的代码,需要可移植,低耦合。因此,特地重构了关于微信支付的代码,希望为感兴趣的朋友能提供一些帮助。
1.新建实体类,封装微信公众平台的配置参数
@Data
public class WCPConfigParams {
// 公众号id
private String appId;
// app密钥
private String appSecret;
// 商户号
private String muchId;
// api密钥
private String apiKey;
// 公众号注册域名
private String registerDomain;
// jsapi支付目录
private String jsapiPaymentAuthDir;
// js安全域名
private String jsDomain;
// 网页授权域名
private String webAuthDomain;
// 证书目录
private String apiclientCert;
// 支付回调地址
private String notifyUrl;
// 退款回调地址
private String notifyUrlRefund;
}
2.新建属性文件保存配置参数的信息
# 微信分配的公众账号ID(企业号corpid即为此appId)
wcp.APP_ID=
# 接口密钥
wcp.APPSECRET=
# 微信支付分配的商户号
wcp.MCH_ID=
# API密钥
wcp.API_KEY=
# 注册域名
wcp.REGISTER_DOMAIN=
# JSAPI支付授权目录
WCP.JSAPI_PAYMENT_AUTH_DIR=
# JS接口安全域名
wcp.JS_DOMAIN=
# 网页授权域名
wcp.WEB_AUTH_DOMAIN=
# 证书路径
wcp.APICLIENT_CERT=
# 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数
wcp.NOTIFY_URL=
# 异步接收微信支付退款结果通知的回调地址,通知URL必须为外网可访问的url,不允许带参数,如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效。
wcp.NOTIFY_URL_REFUND=
3.新建配置类,注入配置参数的bean
@Configuration
@PropertySource("classpath:config/wechat-pay.properties")
public class WCPConfig {
@Autowired
private Environment env;
@Bean
public WCPConfigParams wcpConfigParams() {
WCPConfigParams params = new WCPConfigParams();
params.setAppId(env.getProperty("wcp.APP_ID"));
params.setAppSecret(env.getProperty("wcp.APPSECRET"));
params.setMuchId(env.getProperty("wcp.MCH_ID"));
params.setApiKey(env.getProperty("wcp.API_KEY"));
params.setRegisterDomain(env.getProperty("wcp.REGISTER_DOMAIN"));
params.setJsapiPaymentAuthDir(env.getProperty("wcp.JSAPI_PAYMENT_AUTH_DIR"));
params.setJsDomain(env.getProperty("wcp.JS_DOMAIN"));
params.setWebAuthDomain(env.getProperty("wcp.webAuthDomain"));
params.setApiclientCert(env.getProperty("wcp.APICLIENT_CERT"));
params.setNotifyUrl(env.getProperty("wcp.NOTIFY_URL"));
params.setNotifyUrlRefund(env.getProperty("wcp.NOTIFY_URL_REFUND"));
return params;
}
}
4.sdk中的WXPayConfig由抽象类改为接口
interface WXPayConfig {
/**
* 获取 App ID
*/
String getAppID();
// ...
/**
* 获取商户证书内容
*/
InputStream getCertStream();
/**
* HTTP(S) 连接超时时间,单位毫秒
*/
default int getHttpConnectTimeoutMs() {
return 6 * 1000;
}
// ...
}
5.实现该接口
public class WXPayConfigImpl implements WXPayConfig {
private WCPConfigParams wcpConfigParams;
private byte[] certData;
public WXPayConfigImpl(WCPConfigParams wcpConfigParams) throws IOException {
this.wcpConfigParams = wcpConfigParams;
String certPath = wcpConfigParams.getApiclientCert();
File file = new File(certPath);
InputStream certStream = new FileInputStream(file);
this.certData = new byte[(int)file.length()];
certStream.read(this.certData);
certStream.close();
}
public WXPayConfigImpl() {}
public void setWcpConfigParams(WCPConfigParams wcpConfigParams) {
this.wcpConfigParams = wcpConfigParams;
}
@Override
public String getAppID() {
return wcpConfigParams.getAppId();
}
// ...
}
6.配置类中新增注入的bean
@Bean
@DependsOn(value = "wcpConfigParams")
public WXPayConfigImpl wxPayConfigImpl() {
WXPayConfigImpl wxPayConfigImpl = new WXPayConfigImpl();
wxPayConfigImpl.setWcpConfigParams(wcpConfigParams());
return wxPayConfigImpl;
}
@Bean(name = "wxPayDefault")
@DependsOn(value = "wxPayConfigImpl")
public WXPay wxPayDefault() throws Exception {
WXPay wxPay = new WXPay(wxPayConfigImpl());
return wxPay;
}
虽然Spring是从上到下的执行顺序,建议加上bean的依赖关系
1.新增统一下单的实体类
@Data
public class UnifiedOrderRequestEntity {
/**
* 公众账号ID
*/
private String appid;
/**
* 商户号
*/
@JSONField(name = "mch_id")
private String mchId;
/**
* 设备号
*/
@JSONField(name = "device_info")
private String deviceInfo;
// ...
}
2.新增微信支付后端工具类
@Component
public class WCPBackendUtil {
@Autowired
@Qualifier("wxPayDefault")
private WXPay wxPayDefault;
@Autowired
private WCPConfigParams wcpConfigParams;
@Autowired
private WPPSignatureUtil wppSignatureUtil;
@Autowired
private WPPBackendUtil wppBackendUtil;
/**
* 统一下单接口,输入指定参数,只关心必要参数
* @param openid 用户在公众号的唯一识别号
* @param tradeType 交易类型
* @param price 价格
* @param productDesc 商品描述
* @param terminalIP 终端ip
* @param requestUrl 请求来源的url
* @return 返回js校验参数的的map
*/
public Map<String, Object> unifiedorder(String openid, String tradeType, String price, String productDesc,
String terminalIP, String requestUrl) {
try {
UnifiedOrderRequestEntity requestEntity = new UnifiedOrderRequestEntity();
requestEntity.setBody(productDesc);
requestEntity.setOutTradeNo(generateRandomOrderNo());
requestEntity.setTotalFee(Utility.Yuan2Fen(Double.parseDouble(price)).toString());
requestEntity.setSpbillCreateIp(terminalIP);
requestEntity.setOpenid(openid);
requestEntity.setTradeType(tradeType);
String nonceStr = WXPayUtil.generateNonceStr();
requestEntity.setNonceStr(nonceStr);
requestEntity.setNotifyUrl(wcpConfigParams.getNotifyUrl());
// 利用sdk统一下单,已自动调用wxpay.fillRequestData(data);
Map<String, String> respMap = wxPayDefault.unifiedOrder(beanToMap(requestEntity));
// 统一下单接口调用成功
if (respMap.get("return_code").equals(WXPayConstants.SUCCESS)
&& respMap.get("result_code").equals((WXPayConstants.SUCCESS))) {
String prepayId = respMap.get("prepay_id");
return wppSignatureUtil.permissionValidate(wcpConfigParams.getAppId(), nonceStr, requestUrl, prepayId,
wcpConfigParams.getApiKey(),wppBackendUtil.getJsApiTicket(wppBackendUtil.getAccessToken()));
} else if (!respMap.get("return_code").equals(WXPayConstants.SUCCESS)) {
Map<String, Object> map = new HashMap<>();
for (String key : respMap.keySet()) {
map.put(key, respMap.get(key));
}
return map;
}
} catch (Exception e) {
// log ...
}
return null; // 返回包含错误提示的map
}
/**
* 通用微信支付的调用方法,参数灵活
* @param requestEntity UnifiedOrderRequestEntity统一下单的实体类
* @param requestUrl 请求来源的url
* @return
*/
public Map<String, Object> unifiedorder(UnifiedOrderRequestEntity requestEntity, String requestUrl) {
try {
String nonceStr = requestEntity.getNonceStr();
Map<String, String> respMap = wxPayDefault.unifiedOrder(beanToMap(requestEntity));
// 统一下单接口调用成功
if (respMap.get("return_code").equals(WXPayConstants.SUCCESS)
&& respMap.get("result_code").equals((WXPayConstants.SUCCESS))) {
String prepayId = respMap.get("prepay_id");
return wppSignatureUtil.permissionValidate(wcpConfigParams.getAppId(), nonceStr, requestUrl, prepayId,
wcpConfigParams.getApiKey(),wppBackendUtil.getJsApiTicket(wppBackendUtil.getAccessToken()));
} else if (!respMap.get("return_code").equals(WXPayConstants.SUCCESS)) {
Map<String, Object> map = new HashMap<>();
for (String key : respMap.keySet()) {
map.put(key, respMap.get(key));
}
return map;
}
} catch (Exception e) {
// log ...
}
return null;
}
}
3.方法测试
@SpringBootTest
class WppApplicationTests {
@Autowired
private WCPConfigParams wcpConfigParams;
@Autowired
private WCPBackendUtil wcpBackendUtil;
@Test
void testUnifiedOrder() {
String openid = "o4036jqo2PN9isV6N2FHGRsGRVqg"; // 在**公众号下的openid
String ipAddr = "127.0.0.1";
String url = "http://chety.mynatapp.cc";
Map<String, Object> result1 = wcpBackendUtil.unifiedorder(openid, WCPBackendConst.TradeType.JSAPI.toString(), "1", "Test", ipAddr, url);
UnifiedOrderRequestEntity requestEntity = new UnifiedOrderRequestEntity();
requestEntity.setOutTradeNo(wcpBackendUtil.generateRandomOrderNo());
requestEntity.setBody("Test");
requestEntity.setOpenid("o4036jqo2PN9isV6N2FHGRsGRVqg");
requestEntity.setSpbillCreateIp(ipAddr);
requestEntity.setTradeType(WCPBackendConst.TradeType.JSAPI.toString());
requestEntity.setTotalFee("1");
requestEntity.setNotifyUrl("1");
requestEntity.setNonceStr(WXPayUtil.generateNonceStr());
requestEntity.setNotifyUrl(wcpConfigParams.getNotifyUrl());
Map<String, Object> result2 = wcpBackendUtil.unifiedorder(requestEntity,url);
System.out.println(result1);
System.out.println(result2);
}
}
4.返回结果,如图
emmm… 这个公众号出了点问题,正常的返回结果应该类似这样:
{
"configMap": {
"appId": "wxa02348cd5ec17d28",
"nonceStr": "K2pJsNrRIlhQbeCAVDrPLFTrRo0q1zNS",
"signature": "f62e3c2a0e89973e548b046e8dd2d45f787d8b09",
"timestamp": "1568187468"
},
"payMap": {
"timeStamp": "1568187468",
"package": "prepay_id=wx11153748107050b2c5f8d4df1082400300",
"packageStr": "prepay_id=wx11153748107050b2c5f8d4df1082400300",
"paySign": "C7F135081A4476F434C67686403D741D",
"appId": "wxa02348cd5ec17d28",
"signType": "MD5",
"nonceStr": "K2pJsNrRIlhQbeCAVDrPLFTrRo0q1zNS"
}
}
同理,我们再实现一个查询订单的功能
1.新建查询订单的实体类
@Data
public class OrderQueryRequestEntity {
/**
* 公众账号ID
*/
private String appid;
/**
* 商户号
*/
@JSONField(name = "mch_id")
private String mchId;
/**
* 微信订单号
*/
@JSONField(name = "transaction_id")
private String transactionId;
// ...
}
2.新建查询订单的方法
/**
* 查询订单
* @param outTradeNo 商户订单号
* @return
*/
public Map<String, String> orderquery(String outTradeNo) {
try {
OrderQueryRequestEntity requestEntity = new OrderQueryRequestEntity();
requestEntity.setOutTradeNo(outTradeNo);
requestEntity.setNonceStr(WXPayUtil.generateNonceStr());
Map<String, String> map = orderquery(requestEntity);
return map;
} catch (Exception e) {
// log ...
}
return null;
}
/**
* 查询订单
* @param requestEntity OrderQueryRequestEntity订单查询的请求实体
* @return
*/
public Map<String, String> orderquery(OrderQueryRequestEntity requestEntity) {
try {
return wxPayDefault.orderQuery(beanToMap(requestEntity));
} catch (Exception e) {
// log ...
}
return null;
}
3.测试查询订单
@Test
void testQuery() {
Map<String, String> result1 = wcpBackendUtil.orderquery("201907051128063699"); // 该订单可以是统一下单时生成的商户订单号
OrderQueryRequestEntity requestEntity = new OrderQueryRequestEntity();
requestEntity.setOutTradeNo("201907051128063699");
requestEntity.setNonceStr(WXPayUtil.generateNonceStr());
Map<String, String> result2 = wcpBackendUtil.orderquery(requestEntity);
System.out.println(result1);
System.out.println(result2);
}
4.查询结果
从而,微信支付的其他方法可以类似的展开,
如关闭订单closeorder,瑞款refund(需要证书),退款查询refundquery,下载对账单downloadbill,拉取订单评价数据batchquerycomment等都是相似的调用过程。
目前重构的代码,已经尽量接触耦合,提高复用性,且都已测试。
但是仍然又很多需要改进的地方,如微信支付业务加入商家交易业务,token等在redis储存,前端页面完善等,后面会持续更新,欢迎评论留下修改意见。
源代码已上传:https://github.com/chetwhy/wpp