微信JSAPI支付(二)代碼重構之統一下單,查詢,下載等功能實現

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

重構步驟

配置類注入

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

相關文章
相關標籤/搜索