[] Docking actual micro-channel pay points (distal applet, a rear end java) [two]

Connect text, This part describes the payment to pay points, callback pay, refunds and other processes, and the pit in the process need to pay attention.

Step four: the end of an order to pay points

End user services, business calls, "according to the actual end points paid orders " interfaces, micro-channel according to " the end of an order to pay points " charged amount passed interface debited.

This step is nothing more difficult, POST interface calling micro letter, I am here to say a few points to note.

1.url to be passed in the merchant side order number, order number is created when the orders. (A foreshadowing buried here first)

2. The period of time in service, the service end of the time required, and there is a mandatory requirement, it is recommended to pass according to business needs.

Call:

                //生成签名
                String completeUrl = "https://api.mch.weixin.qq.com/v3/payscore/serviceorder/"+ oi.getRefrenceId() +"/complete";
                HttpUrl completeHttpUrl = HttpUrl.parse(completeUrl);
                String completeToken = WxAPIV3SignUtils.getToken("POST",completeHttpUrl,JSON.toJSONString(completePayScoreIn),payScoreConfig.getMchId(),payScoreConfig.getPrivateKey(),payScoreConfig.getSerialNo());


                RequestBody completeRequestBody = RequestBody.create(MediaType_JSON,JSON.toJSONString(completePayScoreIn));
                Request completeRequest = new Request.Builder()
                        .url(completeUrl)
                        .addHeader("Content-Type","application/json")
                        .addHeader("Accept","application/json")
                        .addHeader("Authorization",completeToken)
                        .post(completeRequestBody)
                        .build();

                Response completeResponse = okHttpClient.newCall(completeRequest).execute();
                logger.debug("【完结支付分订单API】接口返回:{},code={},message={},body={}",JSON.toJSONString(completeResponse),completeResponse.code(),completeResponse.message(),completeResponse.body().string());
                if (completeResponse.isSuccessful()){//调用成功

                }else {//调用失败

                }

 

Receiving micro-channel pay success callback notification

Receiving a callback notification interface, there are several points to note.

1. If the micro-channel notification mechanism [micro businesses received a letter of response does not meet specifications or overtime, believes micro-channel notification fails, micro-channel will pass a certain strategy periodically re-launch notification], so in the realization of the logic, pay attention to the anti-repeat process.

2. The notification must be url url directly accessible and can not carry parameters.

Take a look at the actual processing logic:

    public ResponseEntity payScoreCallBack(HttpServletRequest request) throws Exception
    {
        boolean isOk = true;
        String message = null;
        String callBackIn = "";//json字符串
        String callBackOut = "";
        PayOrderIn orderIn = null;
        String shopId = (String) request.getAttribute("shopId");
        String mchKey = "";//商户APIv3密钥
        try {
            Date now = DateTime.Now();
            ServletInputStream servletInputStream = request.getInputStream();
            int contentLength = request.getContentLength();
            byte[] callBackInBytes = new byte[contentLength];
            servletInputStream.read(callBackInBytes, 0, contentLength);
            callBackIn = new String(callBackInBytes, "UTF-8");
            logger.debug("【微信支付分免密支付回调】:" + callBackIn);


            WxPayScoreNotifyIn notifyIn = JSON.parseObject(callBackIn,WxPayScoreNotifyIn.class);
            if(notifyIn == null){
                throw new Exception("参数不正确,反序列化失败");
            }

            if(!"PAYSCORE.USER_PAID".equals(notifyIn.getEvent_type())) {
                logger.debug("通知类型非支付成功");
                throw new Exception(message);
            }

            //解密回调信息
            byte[] key = mchKey.getBytes();
            WxAPIV3AesUtil aesUtil = new WxAPIV3AesUtil(key);
            String decryptToString = aesUtil.decryptToString(notifyIn.getResource().getAssociated_data().getBytes(),notifyIn.getResource().getNonce().getBytes(),notifyIn.getResource().getCiphertext());
            logger.debug("【支付分支付回调解密结果:】" + decryptToString);

            WxPayScoreNotify_Detail wxPayScoreNotify_detail  = JSON.parseObject(decryptToString,WxPayScoreNotify_Detail.class);

            //todo:处理业务逻辑

           
        }catch (Exception e){
            isOk = false;
            message = e.getMessage();
            LoggerUtils.logDebug(logger, "微信支付回调处理异常,"+e.toString());
        }finally {
            if(isOk){
            

            }
            try{

                return new ResponseEntity(HttpStatus.OK);
            } catch (Exception e){
                //记录日志
                LoggerUtils.logDebug(logger, "微信支付回调响应异常,"+e.toString());
                return new ResponseEntity(HttpStatus.EXPECTATION_FAILED);
            }
        }
    }

3. micro-channel returned decrypt the ciphertext and decryption algorithms, space reasons, refer to my other article:

https://blog.csdn.net/w1170384758/article/details/105414860

4. micro letter to us here left a pit, returned content is encrypted, use the decryption key businesses. If a single business, no problem, because it is a key. If SAAS platform, multi-merchant settled in it, how to decrypt? I asked about the corresponding Tencent technology, the answer is: poll decryption key one by one to take the test, know success so far.

See the results, I would huh up.

! ! ! I offer here an idea, what is the point buried in front of foreshadowing:

We can adopt this line of thinking, the payment callback url, the stitching on the information we need, I'm here to put our merchant id, after receiving the notice, the interception url, this re-routing forward as an argument.

One-time notification settings look under the address:

    String notifyUrl = "https://url/{shopId}/payScoreCallBack";
            notifyUrl = notifyUrl.replaceAll("\\{shopId}",oi.getShopId());
            wxApplyPayScoreIn.setNotify_url(notifyUrl);

Routing forwarding logic (spring frame, custom interceptor):

    /**
     * 动态拦截请求的所有参数
     * <p>
     *     通过"op"参数自动路由到具体的控制方法
     * </p>
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
    {
        

                String uri = request.getRequestURI();
            if (uri.contains("/payScoreCallBack")){//微信支付分回调通知
                String process = (String) request.getAttribute("process");
                if (!org.springframework.util.StringUtils.isEmpty(process)) return true;//已转发的消息透传
                String[] params = uri.split("/");
                String shopId = params[params.length - 2];//shopId存储在倒数第二节
                //重新拼接url,并将mchId放在参数里
                request.setAttribute("shopId",shopId);
                request.setAttribute("process", "true");
                uri = uri.replaceFirst("/" + shopId,"");
                String vPath = request.getContextPath();
                if(!org.springframework.util.StringUtils.isEmpty(vPath))
                    uri = uri.replaceFirst(vPath, "");
                request.getRequestDispatcher(uri).forward(request, response);
                return false;
            }

            return true;
        
        
    }

 

Thus, not against micro-channel interface specification and satisfy our need to pass parameters.

 

Refund Request pay points

After reading official documents found, refund payments and refunds minute callback and other payment method (H5 payment, payment applets) use the same API interface, I thought it would be very easy, but measured down to find the Oh!

First on the code:

    //微信申请退款
    private boolean wxApplyRefund(ApplyRefundIn refundIn){
        Date wxReqDate = new Date();
        boolean result = false;
        String wxApplyRefundInXml="",wxApplyRefundOutXml="";
        try{
            //获取商家支付信息
            GetPayInfoOut payInfoOut = customerFacade.getPayInfo(new GetPayInfoIn(refundIn.getShopMchId(), refundIn.getPayType()));
 
            //创建请求对象
            BigDecimal handred = new BigDecimal(100);
            WxApplyRefundIn wxApplyRefundIn = new WxApplyRefundIn(payInfoOut.getAppId(),payInfoOut.getMchId()
            , SerialnoUtils.buildUUID(), "MD5", refundIn.getPayOrderId()/*, refundIn.getOrderId()*/, refundIn.getRefundId()
            , refundIn.getOrderAmount().multiply(handred).setScale(2, RoundingMode.HALF_EVEN).intValue()
            , refundIn.getRefundAmount().multiply(handred).setScale(2, RoundingMode.HALF_EVEN).intValue()
            , "CNY", "order error", SystemConst.WEIXIN_NOTIFY_URL + "/refund");//refundIn.getRefundDesc()
            wxApplyRefundIn.setSign(MapUtils.getObjectMD5Sign(wxApplyRefundIn, payInfoOut.getMchKey()));
            //调用接口
            wxApplyRefundInXml = XmlUtils.beanToXml(wxApplyRefundIn);
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            FileInputStream instream = new FileInputStream(new File(SystemConst.WX_Cert_URL+payInfoOut.getCertFile()));
            try {
                keyStore.load(instream,payInfoOut.getMchId().toCharArray());
            }finally {
                instream.close();
            }
            CloseableHttpClient httpclient = null;
            CloseableHttpResponse response = null;
            try{
                // Trust own CA and all self-signed certs
                SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore
                        , payInfoOut.getMchId().toCharArray()).build();
                // Allow TLSv1 protocol only
                SSLConnectionSocketFactory sslcsf = new SSLConnectionSocketFactory(
                        sslcontext, new String[] { "TLSv1" }, null,
                        SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
                httpclient = HttpClients.custom().setSSLSocketFactory(sslcsf).build();
                HttpPost httpPost = new HttpPost(SystemConst.WX_Refund_URL);
                httpPost.setEntity(new StringEntity(wxApplyRefundInXml));
                response = httpclient.execute(httpPost);
                int contentLength = (int) response.getEntity().getContentLength();
                byte[] wxApplyPayOutBytes = new byte[contentLength];
                InputStream outStream = response.getEntity().getContent();
                outStream.read(wxApplyPayOutBytes, 0, contentLength);
                wxApplyRefundOutXml = new String(wxApplyPayOutBytes, "UTF-8");
            }finally {
                response.close();
                httpclient.close();
            }
            WxApplyRefundOut wxApplyRefundOut = XmlUtils.xmlContentToBean(wxApplyRefundOutXml, WxApplyRefundOut.class);
            if (wxApplyRefundOut == null) {
                throw new Exception("微信支付响应异常");
            }
            if ("FAIL".equals(wxApplyRefundOut.getReturn_code())) {
                throw new Exception(wxApplyRefundOut.getReturn_msg());
            }
            //验证签名
            /*String sign = MapUtils.getObjectMD5Sign(wxApplyRefundOut, payInfoOut.getMchKey());
            if (!sign.equals(wxApplyRefundOut.getSign())) {
                throw new BusinessException("验证签名失败");
            }*/
            if ("FAIL".equals(wxApplyRefundOut.getResult_code())) {
                throw new Exception("err_code:" + wxApplyRefundOut.getErr_code() + ",err_code_des:" + wxApplyRefundOut.getErr_code_des());
            }
            result = true;
        }catch (Exception e){
            refundIn.setErrorMsg(e.getMessage());
            logger.error("微信退款申请异常", e);
        }finally {
            //记录退款请求日志
            logger.debug("微信退款申请,请求("+DateUtils.formatDateTime(wxReqDate)+"):"+wxApplyRefundInXml
                    +"响应("+DateUtils.formatDateTime(new Date())+"):"+wxApplyRefundOutXml);
        }
        return result;
    }

It said the pit below:

1. take a look at the success of micro-channel pay, ordinary users and micro-channel business background show is how this order.

Yes, you read that right, micro-channel pay-point, the merchant side order number, he changed all the letters, ha ha, look at the micro-channel official reply given.

It does not support, ha ha, killing me, micro-channel business background does not support micro-channel product, anyway, I feel really sick.

! ! ! When we pay attention to the development of the points came out

Points when paying the refund, you must use transaction_id, and can not be used out_trade_no, do not pass out_trade_no, as will complain that the order does not exist. Refund callback notification, is the same reason, the attention compatible processing. (Micro channel estimate update will follow)

Then look at the results of the processing logic refund callback notifications:

    /**
     * 微信退款回调
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    public void refund(HttpServletRequest request, HttpServletResponse response) throws Exception {
        boolean isOk = false;
        String message = null;
        String reqXml = "";String resXml = "";
        PayOrderIn orderIn = null;
        WxRefundNotifyIn reqParam = null;
        WxRefundNotifyReqInfo reqInfo = null;
        try {
            //解析报文
            ServletInputStream servletInputStream = request.getInputStream();
            int contentLength = request.getContentLength();
            byte[] callBackInBytes = new byte[contentLength];
            servletInputStream.read(callBackInBytes, 0, contentLength);
            reqXml = new String(callBackInBytes, "UTF-8");
            logger.info("【微信退款回调开始】请求报文:" + reqXml);

            //记录回调日志

            //xml转object
            reqParam = XmlUtils.xmlContentToBean(reqXml, WxRefundNotifyIn.class);
            if (reqParam == null) {
                logger.error("回调参数反序列化失败");
                return;
            }

            if (WXPayConstants.FAIL.equals(reqParam.getReturn_code())) {
                message = reqParam.getReturn_msg();
                logger.error(message);
                return;
            }

            PayConfig payConfig = null;
            if (StringUtils.isNotBlank(reqParam.getMch_id())) { //订单有收款信息,获取商户收款账户信息
                GetPayInfoIn payInfoIn = new GetPayInfoIn(reqParam.getMch_id(),PayTypeEnum.wx_payscore.getVal());
                GetPayInfoOut payInfoOut = customerFacade.getPayInfo(payInfoIn);


            }
            if (payConfig == null) {
                WxConfig config = SpringUtils.getBean(WxConfig.class);
                payConfig = new PayConfig(config.getAppID(), config.getMchID(),config.getKey());
            }

            
            //aes解密
            String reqInfoXml = AESUtils.decryptData(reqParam.getReq_info(), payConfig.getApiSecret());
            reqInfo = XmlUtils.xmlContentToBean(reqInfoXml, WxRefundNotifyReqInfo.class);
            if (reqInfo == null) {
                logger.error("回调参数“reqInfo”反序列化失败");
                return;
            }
            logger.debug("【微信退款回调,解密后参数:】{}",JSON.toJSONString(reqInfo));

            //boolean isValid = WXPayUtil.isSignatureValid(reqXml, payConfig.getApiSecret());//验证签名
            //if (!isValid) {
            //    logger.error("签名验证失败");
            //    return;
            //}

            if (!WXPayConstants.SUCCESS.equals(reqInfo.getRefund_status())) {
                logger.error("退款失败");
                return;
            }

            isOk = true;
            logger.debug("微信退款回调成功");
        } catch (Exception e) {
            isOk = false;
            message = e.getMessage();
            logger.info("微信退款回调处理异常:", e);
        } finally {
            try{
                //处理业务逻辑

            }catch (Exception e){
                logger.error("微信退款回调逻辑处理异常", e);
            }
            try {
                WxRefundNotifyOut resParam = new WxRefundNotifyOut();
                if (isOk) {
                    resParam.setReturn_code(WXPayConstants.SUCCESS);
                } else {
                    resParam.setReturn_code(WXPayConstants.FAIL);
                    resParam.setReturn_msg(message);
                }
                resXml = XmlUtils.beanToXml(resParam);
                byte[] resBytes = resXml.getBytes(Charset.forName("UTF-8"));
                ServletOutputStream servletOutputStream = response.getOutputStream();
                servletOutputStream.write(resBytes);
            } catch (Exception e) {
                logger.error("微信退款回调应答异常,", e);
            }
            logger.info("【微信退款回调结束】应答报文:" + resXml);
        }
    }

I believe this to take over for small micro-channel partners, nothing is difficult, there are two tools: xml turn Javabean and symmetrical AES decryption requires a small private partners can believe me.

Haha, so far, our micro-channel pay points docking is complete. Hope that we can support, problem areas also welcome the god of correction.

 

 

Released six original articles · won praise 14 · views 567

Guess you like

Origin blog.csdn.net/w1170384758/article/details/105412503