前一篇介紹瞭如何實現微信的統一下單,但在實際生產中,不建議直接使用。開發中的代碼,須要可移植,低耦合。所以,特意重構了關於微信支付的代碼,但願爲感興趣的朋友能提供一些幫助。前端
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