前一篇介绍了如何实现微信的统一下单,但在实际生产中,不建议直接使用。开发中的代码,须要可移植,低耦合。所以,特意重构了关于微信支付的代码,但愿为感兴趣的朋友能提供一些帮助。前端
1.新建实体类,封装微信公众平台的配置参数java
@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.新建属性文件保存配置参数的信息git
# 微信分配的公众帐号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.新建配置类,注入配置参数的beangithub
@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由抽象类改成接口web
interface WXPayConfig {
/** * 获取 App ID */
String getAppID();
// ...
/** * 获取商户证书内容 */
InputStream getCertStream();
/** * HTTP(S) 链接超时时间,单位毫秒 */
default int getHttpConnectTimeoutMs() {
return 6 * 1000;
}
// ...
}
复制代码
5.实现该接口redis
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.配置类中新增注入的beanjson
@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.新增统一下单的实体类api
@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储存,前端页面完善等,后面会持续更新,欢迎评论留下修改意见。
源代码已上传:github.com/chetwhy/wpp