maven project integrates official wechat payment v3.0.9 (spring boot sample version)

Keywords: Programming SDK xml github Maven

Former by: recently made wechat payment in java, checked the maven warehouse of wechat payment sdk, and found that the latest update was on March 16, 2017, and the version number stayed at 0.0.3:

<!-- https://mvnrepository.com/artifact/com.github.wxpay/wxpay-sdk -->
<dependency>
    <groupId>com.github.wxpay</groupId>
    <artifactId>wxpay-sdk</artifactId>
    <version>0.0.3</version>
</dependency>

At the same time, the official SDK (download address: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1) The content version is as follows:

<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>3.0.9</version>

The official SDK version 3.0.9 is also built based on maven, but it is not uploaded to the public warehouse of maven, so we can do it ourselves to build jar packages to import local Maven projects.

Packaging steps:

  1. Modify the abstract properties of the configuration file WXPayConfig. The original default Abstract attribute can only be seen under the same package. The default attribute under different packages does not have access rights. Cross package must be changed to the public attribute.
public abstract class WXPayConfig {

    /**
     * Get App ID
     *
     * @return App ID
     */
    public abstract String getAppID();

    /**
     * Get Mch ID
     *
     * @return Mch ID
     */
    public abstract String getMchID();
   
   /**
     * Get API key
     *
     * @return API secret key
     */
    public abstract String getKey();
   
   /**
     * Obtain merchant certificate content
     *
     * @return Contents of merchant certificate
     */
    public abstract InputStream getCertStream();
   
   /**
     * HTTP(S) Connection timeout in milliseconds
     *
     * @return
     */
    public int getHttpConnectTimeoutMs() {
        return 6*1000;
    }
   
   /**
     * HTTP(S) Read data timeout in milliseconds
     *
     * @return
     */
    public int getHttpReadTimeoutMs() {
        return 8*1000;
    }
   
   /**
     * Get WXPayDomain for automatic disaster recovery switching of multiple domain names
     * @return
     */
    public abstract IWXPayDomain getWXPayDomain();
   
   /**
     * Whether to submit automatically.
     * To turn off auto escalation, the implementation of this function in the subclass returns false.
     *
     * @return
     */
   public boolean shouldAutoReport() {
        return true;
    }
	
    /**
     * Number of threads for health escalation
     *
     * @return
     */
    public int getReportWorkerNum() {
        return 6;
    }
    
	/**
     * Maximum number of health escalation cached messages. There will be threads to report independently
     * Rough calculation: add a message of 200B, 10000 messages occupy 2000 KB, about 2MB, acceptable
     *
     * @return
     */
    public int getReportQueueMaxSize() {
        return 10000;
    }
    
	/**
     * Batch submit, up to multiple data at a time
     *
     * @return
     */
    public int getReportBatchSize() {
        return 10;
    }
	
}

2. After self packaging, the following commands are required to install the local jar package to the local warehouse (Note: do not wrap lines):

mvn install:install-file -Dfile=E:\wxpay-sdk-3.0.9.jar -DgroupId=com.github.wxpay -DartifactId=wxpay-sdk -Dversion=3.0.9 -Dpackaging=jar

3. Mapen's pom.xml introduces wxpay SDK dependency.

	<dependency>
            <groupId>com.github.wxpay</groupId>
            <artifactId>wxpay-sdk</artifactId>
            <version>3.0.9</version>
        </dependency>

At the same time, it should be noted that the package wxpay-sdk-3.0.9.jar does not include the package that wxpay depends on. We also need to introduce the package that wxpay SDK depends on:

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.12</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.30</version>
        </dependency>

There is a conflict between the slf4j that wxpay SDK depends on and the spring boot starter logging that spring boot starter web depends on. Please note that the dependency exclusion is the one that I exclude.

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions><!-- Remove springboot Default configured log -->
               <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

SDK use

1. Wechat payment yml configuration file

# Wechat app payment
pay:
  wxpay:
    app:
      appID: "xxxxx"
      mchID: "xxxx"
      key: "xxxxxxxxxxxxx"
      #The path to store the security certificate downloaded from the wechat merchant platform is under resources. Remember to check whether the class file in the target directory is packaged with the apiclient_cert.p12 file
      certPath: static/cert/wxpay/apiclient_cert.p12
      #Asynchronous notification interface for wechat payment success
      #payNotifyUrl: ${server.service-domain}/wxPay/notify
      payNotifyUrl: http://xxx.com/wxPay/notify

2. Read configuration information


@Component
@ConfigurationProperties(prefix = "pay.wxpay.app")
public class WxPayAppConfig extends WXPayConfig {
    /**
     * appID
     */
    private String appID;

    /**
     * Merchant number
     */
    private String mchID;

    /**
     * API secret key
     */
    private String key;

    /**
     * API Absolute path of certificate (this project is placed in resources/cert/wxpay/apiclient_cert.p12 ")
     */
    private String certPath;

    /**
     * HTTP(S) Connection timeout in milliseconds
     */
    private int httpConnectTimeoutMs = 8000;

    /**
     * HTTP(S) Read data timeout in milliseconds
     */
    private int httpReadTimeoutMs = 10000;

    /**
     * Wechat payment asynchronous notification address
     */
    private String payNotifyUrl;

    /**
     * Wechat refund asynchronous notification address
     */
    private String refundNotifyUrl;

    /**
     * Obtain the content of merchant Certificate (the certificate here needs to be downloaded to wechat merchant platform)
     *
     * @return Contents of merchant certificate
     */
    @Override
    public InputStream getCertStream() {
        InputStream certStream  =getClass().getClassLoader().getResourceAsStream(certPath);
        return certStream;
    }

    public String getAppID() {
        return appID;
    }

    public void setAppID(String appID) {
        this.appID = appID;
    }

    public String getMchID() {
        return mchID;
    }

    public void setMchID(String mchID) {
        this.mchID = mchID;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getCertPath() {
        return certPath;
    }

    public void setCertPath(String certPath) {
        this.certPath = certPath;
    }

    public int getHttpConnectTimeoutMs() {
        return httpConnectTimeoutMs;
    }

    public void setHttpConnectTimeoutMs(int httpConnectTimeoutMs) {
        this.httpConnectTimeoutMs = httpConnectTimeoutMs;
    }

    public int getHttpReadTimeoutMs() {
        return httpReadTimeoutMs;
    }

    public void setHttpReadTimeoutMs(int httpReadTimeoutMs) {
        this.httpReadTimeoutMs = httpReadTimeoutMs;
    }

    public String getPayNotifyUrl() {
        return payNotifyUrl;
    }

    public void setPayNotifyUrl(String payNotifyUrl) {
        this.payNotifyUrl = payNotifyUrl;
    }

    public String getRefundNotifyUrl() {
        return refundNotifyUrl;
    }

    public void setRefundNotifyUrl(String refundNotifyUrl) {
        this.refundNotifyUrl = refundNotifyUrl;
    }


    public IWXPayDomain getWXPayDomain() {
        IWXPayDomain iwxPayDomain = new IWXPayDomain() {
            public void report(String domain, long elapsedTimeMillis, Exception ex) {
            }

            public DomainInfo getDomain(WXPayConfig config) {
                return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API, true);
            }
        };
        return iwxPayDomain;
    }
	
}

3. General return result set

/**
 * @Description General return result set
 * @Author
 */
public class ResultMap extends HashMap<String, Object> {
    public ResultMap() {
        put("state", true);
        put("code", 0);
        put("msg", "success");
    }

    public static ResultMap error(int code, String msg) {
        ResultMap r = new ResultMap();
        r.put("state", false);
        r.put("code", code);
        r.put("msg", msg);
        return r;
    }

    public static ResultMap error(String msg) {
        return error(HttpStatus.METHOD_NOT_ALLOWED.value(), msg);
    }

    public static ResultMap error() {
        return error(HttpStatus.METHOD_NOT_ALLOWED.value(), "Unknown exception, please contact administrator");
    }

    public static ResultMap ok(String msg) {
        ResultMap r = new ResultMap();
        r.put("msg", msg);
        return r;
    }

    public static ResultMap ok(Map<String, Object> par) {
        ResultMap r = new ResultMap();
        r.putAll(par);
        return r;
    }

    public static ResultMap ok() {
        return new ResultMap();
    }

    public ResultMap put(String key, Object value) {
        super.put(key, value);
        return this;
    }

}

4. Wechat payment service interface

/**
 * Wechat payment service interface
 */
public interface WxPayService {

    /**
     * @Description: Unified order of wechat payment
     * @param orderNo: Order number
     * @param amount: Actual payment amount
     * @param body: Order description
     * @Author:
     * @Date: 2019/8/1
     * @return
     */
    ResultMap unifiedOrder(String orderNo, double amount, String body) ;

    /**
     * @Description: Order payment asynchronous notification
     * @param notifyStr: Wechat asynchronous notification message string
     * @Author:
     * @Date: 2019/8/1
     * @return
     */
    String notify(String notifyStr) throws Exception;

    /**
     * @Description: refund
     * @param orderNo: Order number
     * @param amount: Actual payment amount
     * @param refundReason: Refund reason
     * @Author:
     * @Date: 2019/8/6
     * @return
     */
    ResultMap refund(String orderNo, double amount, String refundReason) throws Exception;

}

5. Service interface implementation class

@Service
public class WxPayServiceImpl implements WxPayService {

    private final Logger logger = LoggerFactory.getLogger(WxPayServiceImpl.class);

    //@Reference
    //private IOrderPaymentService orderPaymentService;
    @Autowired
    private WxPayAppConfig wxPayAppConfig;

    @Override
    public ResultMap unifiedOrder(String orderNo, double amount, String body) {
        Map<String, String> returnMap = new HashMap<>();
        Map<String, String> responseMap = new HashMap<>();
        Map<String, String> requestMap = new HashMap<>();
        try {
            WXPay wxpay = new WXPay(wxPayAppConfig);
            requestMap.put("body", body);                                     // Commodity Description
            requestMap.put("out_trade_no", orderNo);                          // Merchant order number
            requestMap.put("total_fee", String.valueOf((int)(amount*100)));   // Total sum
            //Requestmap. Put ("spbill? Create? IP", httpcontextutils. Getipaddr()); / / terminal IP
            requestMap.put("trade_type", "APP");                              // App payment type
            requestMap.put("notify_url", wxPayAppConfig.getPayNotifyUrl());   // Receive wechat payment asynchronous notification callback address
            Map<String, String> resultMap = wxpay.unifiedOrder(requestMap);
            for (String resultKey : resultMap.keySet()) {
                logger.info("Order key:{}", resultMap.get(resultKey));
            }

            //Get return code
            String returnCode = resultMap.get("return_code");
            String returnMsg = resultMap.get("return_msg");
            //If the return code is SUCCESS, a result code will be returned and the result code will be judged
            if ("SUCCESS".equals(returnCode)) {
                String resultCode = resultMap.get("result_code");
                String errCodeDes = resultMap.get("err_code_des");
                if ("SUCCESS".equals(resultCode)) {
                    responseMap = resultMap;
                }
            }
            if (responseMap == null || responseMap.isEmpty()) {
                return ResultMap.error("Failed to get pre payment transaction session ID");
            }
            // 3. Signature generation algorithm
            Long time = System.currentTimeMillis() / 1000;
            String timestamp = time.toString();
            returnMap.put("appid", wxPayAppConfig.getAppID());
            returnMap.put("partnerid", wxPayAppConfig.getMchID());
            returnMap.put("prepayid", responseMap.get("prepay_id"));
            returnMap.put("noncestr", responseMap.get("nonce_str"));
            returnMap.put("timestamp", timestamp);
            returnMap.put("package", "Sign=WXPay");
            returnMap.put("sign", WXPayUtil.generateSignature(returnMap, wxPayAppConfig.getKey()));//Wechat payment signature
            return ResultMap.ok().put("data", returnMap);
        } catch (Exception e) {
            logger.error("Order number:{},Error message:{}", orderNo, e.getMessage());
            return ResultMap.error("Wechat payment unified order failed");
        }
    }

    @Override
    public String notify(String notifyStr) {
        String xmlBack = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[The message is empty.]]></return_msg></xml> ";
        try {
            // Convert to map
            Map<String, String> resultMap = WXPayUtil.xmlToMap(notifyStr);
            WXPay wxpayApp = new WXPay(wxPayAppConfig);
            if (wxpayApp.isPayResultNotifySignatureValid(resultMap)) {
                String returnCode = resultMap.get("return_code");  //state
                String outTradeNo = resultMap.get("out_trade_no");//Merchant order number
                String transactionId = resultMap.get("transaction_id");
                if (returnCode.equals("SUCCESS")) {
                    if (! StringUtils.isEmpty(outTradeNo)) {
                        /**
                         * be careful!!!
                         * Please modify the payment status of database order and the corresponding status of other data according to the business process
                         *
                         */
                        logger.info("Wechat mobile payment callback succeeded,Order number:{}", outTradeNo);
                        xmlBack = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return xmlBack;
    }

    @Override
    public ResultMap refund(String orderNo, double amount, String refundReason) throws Exception {

        if(StringUtils.isEmpty(orderNo)){
            return ResultMap.error("Order No. cannot be empty");
        }
        if(amount <= 0){
            return ResultMap.error("Refund amount must be greater than 0");
        }

        Map<String, String> responseMap = new HashMap<>();
        Map<String, String> requestMap = new HashMap<>();
        WXPay wxpay = new WXPay(wxPayAppConfig);
        System.out.println(wxPayAppConfig.toString());
        requestMap.put("out_trade_no", orderNo);
        requestMap.put("out_refund_no", "xxxx Self generated manually");//The refund No. in the merchant system is unique in the merchant system. It can only be a number, upper and lower case letters Β * @, and only one refund is requested for the same refund No. multiple times.
        requestMap.put("total_fee", "Total order amount");
        requestMap.put("refund_fee", String.valueOf((int)(amount*100)));//Refund amount required
        requestMap.put("refund_desc", refundReason);
        try {
            responseMap = wxpay.refund(requestMap);
        } catch (Exception e) {
            e.printStackTrace();
        }
        for (String responseKey : responseMap.keySet()) {
            logger.info("Order key:{}", responseMap.get(responseKey));
        }
        String return_code = responseMap.get("return_code");   //Return status code
        String return_msg = responseMap.get("return_msg");     //Return information
        if ("SUCCESS".equals(return_code)) {
            String result_code = responseMap.get("result_code");       //Business results
            String err_code_des = responseMap.get("err_code_des");     //Error code description
            if ("SUCCESS".equals(result_code)) {
                //Indicates that the refund application has been accepted successfully. The result is queried through the refund query interface
                //Modify the status of user's order to be in refund application or refunded. Refund asynchronous notification based on demand, optional
                return ResultMap.ok("Refund application successful");
            } else {
                logger.info("Order number:{}error message:{}", orderNo, err_code_des);
                return ResultMap.error(err_code_des);
            }
        } else {
            logger.info("Order number:{}error message:{}", orderNo, return_msg);
            return ResultMap.error(return_msg);
        }
    }

}

6. Wechat payment external REST interface (using Swagger2)

@Api(tags = "Wechat payment interface management")
@RestController
@RequestMapping("/wxPay")
public class WxPayController{

    @Autowired
    private WxPayService wxPayService;
    private final Logger logger = LoggerFactory.getLogger(WxPayController.class);
    /**
     * Unified single interface
     */
    @ApiOperation(value = "Unified order", notes = "Unified order")
    @GetMapping("/unifiedOrder")
    public ResultMap unifiedOrder(
            @ApiParam(value = "Order number") @RequestParam String orderNo,
            @ApiParam(value = "Order amount") @RequestParam double amount,
            @ApiParam(value = "Trade name") @RequestParam String body,
            HttpServletRequest request) {
        try {
            // 1. Verify order exists

            // 2, Start WeChat payment unified order
            ResultMap resultMap = wxPayService.unifiedOrder(orderNo, amount, body);
            return resultMap;//See the end of the article for the common return result set of the system
        } catch (Exception e) {
            logger.error(e.getMessage());
            return ResultMap.error("Abnormal operation, please contact the administrator");
        }
    }

    /**
     * Wechat payment asynchronous notification
     */
    @RequestMapping(value = "/notify")
    public String payNotify(HttpServletRequest request) {
        InputStream is = null;
        String xmlBack = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[The message is empty.]]></return_msg></xml> ";
        try {
            is = request.getInputStream();
            // Convert InputStream to String
            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
            StringBuilder sb = new StringBuilder();
            String line = null;
            while ((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
            xmlBack = wxPayService.notify(sb.toString());
        } catch (Exception e) {
            logger.error("Wechat mobile payment callback notification failed:", e);
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return xmlBack;
    }

    @ApiOperation(value = "refund", notes = "refund")
    @PostMapping("/refund")
    public ResultMap refund(@ApiParam(value = "Order number") @RequestParam String orderNo,
                            @ApiParam(value = "refund amount") @RequestParam double amount,
                            @ApiParam(value = "Refund reason") @RequestParam(required = false) String refundReason) throws Exception {

        return wxPayService.refund(orderNo, amount, refundReason);
    }

}


Posted by edwinlcy on Tue, 31 Mar 2020 20:09:14 -0700