The previous article introduced how to realize the unified order of wechat, but in actual production, it is not recommended to use it directly. Code in development needs to be portable and low coupling. Therefore, the special reconstruction of the wechat payment code, hoping to provide some help to interested friends.

Refactoring steps

Configuring class injection

1. Create an entity class to encapsulate the configuration parameters of wechat public platform

@Data
public class WCPConfigParams {
    // Public id
    private String appId;
    / / app key
    private String appSecret;
    / / merchants
    private String muchId;
    / / API keys
    private String apiKey;
    // Public account register domain name
    private String registerDomain;
    // jsAPI payment directory
    private String jsapiPaymentAuthDir;
    // js secure domain name
    private String jsDomain;
    // Web page authorization domain name
    private String webAuthDomain;
    // Certificate directory
    private String apiclientCert;
    // Pay the callback address
    private String notifyUrl;
    // Refund callback address
    private String notifyUrlRefund;
}
Copy the code

2. Create an properties file to save the configuration information

APP_ID= # interface key wcp.APPSECRET= # wechat Pay merchant number wcp.MCH_ID= # API key wcp.API_KEY= # registered domain name Wcp.register_domain = # JSAPI Payment authorization directory wcp.jsapi_payment_auth_dir = # JS interface security domain name wcp.js_domain = # Web authorization domain name wcp.web_auth_domain = # Certificate path wcp.APICLIENT_CERT= # Callback address for receiving notification of wechat pay result asynchronously. Notification URL must be accessible to the external network and cannot carry parameter Wcp. NOTIFY_URL= # callback address for receiving notification of wechat Pay refund result asynchronously. Note The notification URL must be accessible to the Internet, and no parameters are allowed. If notifY_URL is passed, the callback address configured on the merchant platform will not take effect. wcp.NOTIFY_URL_REFUND=Copy the code

3. Create a configuration class and inject the bean of the configuration parameters

@Configuration
@PropertySource("classpath:config/wechat-pay.properties")
public class WCPConfig {

    @Autowired
    private Environment env;

    @Bean
    public WCPConfigParams wcpConfigParams(a) {
        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"));
        returnparams; }}Copy the code

4. WXPayConfig in SDK is changed from abstract class to interface

interface WXPayConfig {
    /** * Get App ID */
    String getAppID(a);
	// ...
    /** * Obtain merchant certificate content */
    InputStream getCertStream(a);
    /** * HTTP(S) connection timeout in milliseconds */
    default int getHttpConnectTimeoutMs(a) {
        return 6 * 1000;
    }
	// ...
}
Copy the code

5. Implement the interface

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(a) {}

    public void setWcpConfigParams(WCPConfigParams wcpConfigParams) {
        this.wcpConfigParams = wcpConfigParams;
    }

    @Override
    public String getAppID(a) {
        return wcpConfigParams.getAppId();
    }

	// ...
}
Copy the code

6. Configure the newly injected bean in the class

@Bean
@DependsOn(value = "wcpConfigParams")
public WXPayConfigImpl wxPayConfigImpl(a) {
	WXPayConfigImpl wxPayConfigImpl = new WXPayConfigImpl();
	wxPayConfigImpl.setWcpConfigParams(wcpConfigParams());
	return wxPayConfigImpl;
}

@Bean(name = "wxPayDefault")
@DependsOn(value = "wxPayConfigImpl")
public WXPay wxPayDefault(a) throws Exception {
	WXPay wxPay = new WXPay(wxPayConfigImpl());
	return wxPay;
}
Copy the code

Although Spring is executed from top to bottom, it is recommended to add bean dependencies

Wechat payment function development

Unified order

1. Added the entity class of unified order

@Data
public class UnifiedOrderRequestEntity {
    /** * public ID */
    private String appid;
    /** * Merchant number */
    @JSONField(name = "mch_id")
    private String mchId;
    /**
     * 设备号
     */
    @JSONField(name = "device_info")
    private String deviceInfo;

	// ...
}
Copy the code

2. Added the wechat Payment backend tool class

@Component
public class WCPBackendUtil {

    @Autowired
    @Qualifier("wxPayDefault")
    private WXPay wxPayDefault;

    @Autowired
    private WCPConfigParams wcpConfigParams;

    @Autowired
    private WPPSignatureUtil wppSignatureUtil;

    @Autowired
    private WPPBackendUtil wppBackendUtil;

    /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **@paramOpenid user's unique id * in the public account@paramTradeType tradeType *@paramPrice price *@paramProductDesc Product description *@paramTerminalIP terminalIP *@paramRequestUrl Specifies the url of the request source *@returnReturns the map */ of the JS validation parameter
    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());

            Wxpay.fillrequestdata (data);
            Map<String, String> respMap = wxPayDefault.unifiedOrder(beanToMap(requestEntity));

            // The unified single interface was successfully invoked
            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));
                }
                returnmap; }}catch (Exception e) {
            // log ...
        }
        return null;	// Returns a map with an error message
    }

    /** * Universal wechat payment call method, flexible parameters *@paramRequestEntity UnifiedOrderRequestEntity unified order entity class *@paramRequestUrl Specifies the url of the request source *@return* /
    public Map<String, Object> unifiedorder(UnifiedOrderRequestEntity requestEntity, String requestUrl) {
        try {
            String nonceStr = requestEntity.getNonceStr();
            Map<String, String> respMap = wxPayDefault.unifiedOrder(beanToMap(requestEntity));

            // The unified single interface was successfully invoked
            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));
                }
                returnmap; }}catch (Exception e) {
            // log ...
        }
        return null; }}Copy the code

3. Method test

@SpringBootTest
class WppApplicationTests {

    @Autowired
    private WCPConfigParams wcpConfigParams;

    @Autowired
    private WCPBackendUtil wcpBackendUtil;

    @Test
    void testUnifiedOrder(a) {
        String openid = "o4036jqo2PN9isV6N2FHGRsGRVqg";		// OpenID under ** public id
        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); }}Copy the code

4. Return the result, as shown in the figure

emmm… There is something wrong with the public account. A normal return would look something like this:

{
    "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"}}Copy the code

Similarly, we will implement a query order function

Query order

1. Create an entity class for querying orders

@Data
public class OrderQueryRequestEntity {
    /** * public ID */
    private String appid;
    /** * Merchant number */
    @JSONField(name = "mch_id")
    private String mchId;
    /** * wechat order number */
    @JSONField(name = "transaction_id")
    private String transactionId;
    // ...
}
Copy the code

2. Create a method for querying orders

   /** **@paramOutTradeNo Merchant Order Number *@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;
    }

    /** **@paramRequestEntity OrderQueryRequestEntity requestEntity for order query *@return* /
    public Map<String, String> orderquery(OrderQueryRequestEntity requestEntity) {
        try {
            return wxPayDefault.orderQuery(beanToMap(requestEntity));
        } catch (Exception e) {
            // log ...
        }
        return null;
    }
Copy the code

3. Test the query order

@Test
void testQuery(a) {
    Map<String, String> result1 = wcpBackendUtil.orderquery("201907051128063699");     // This order can be the merchant order number generated when placing a unified order
    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);
}
Copy the code

4. Query result

The end of the

In this way, other methods of wechat payment can be similarly expanded, such as closing closeOrder, refundrefund (certificate required), refundquery refundQuery, download statement downloadbill, Pull order evaluation data batchqueryComment and so on are similar call processes.


At present, the refactored code has been exposed to coupling as much as possible to improve reusability, and has been tested. However, there are still many areas that need to be improved, such as wechat payment business joining merchant transaction business, token storage in Redis, front-end page improvement, etc., which will be updated continuously in the future, welcome to comment and leave modification suggestions.

The source code has been uploaded: github.com/chetwhy/wpp