第三方支付流程设计及微信支付实现

        商户平台对接支付系统使得平台具备了商业价值,支付系统包括了第三方支付和聚合平台,第三方支付是指具备一定实力和信誉保障的独立机构,采用与各大银行签约的方式,通过与银行支付结算系统接口对接而促成交易双方进行交易的网络支付模式,而聚合支付是相对第三方支付而言的,作为对第三方支付平台服务的拓展。

        第三方支付是介于银行和商户之间的,而聚合支付是介于第三方支付和商户之间。

设计实现多渠道支付

        为应对后续多渠道支付,使代码有更好的重用性、可读性、可靠性、可维护性,需从顶层进行设计,尽可能满足设计模式六大原则。

六大设计原则

        1)单一职责。一个类只负责一个功能领域中的相应职责,就一个类而言,应该只有一个引起它变化的原因 (高内聚、低耦合);

        2)开闭原则。对扩展开放,对修改关闭,在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果;

        3)里氏替换原则LSP。任何基类可以出现的地方,子类一定可以出现,在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象;

        4)依赖倒转原则。是开闭原则的基础,针对接口编程,依赖于抽象而不依赖于具体。高层模块不应该依赖低层模块,二者都应该依赖其抽象。

        5)接口隔离原则。客户端不应该依赖那些它不需要的接口,使用多个隔离的接口,比使用单个接口要好,降低类之间的耦合度;

        6)迪米特法则。最少知道原则,一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及。通过引入一个合理的第三者来降低现有对象之间的耦合度

工厂模式

        提供了一种创建对象的最佳方式,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。主要有 3 种不同的实现方式 :

        简单工厂模式:通过传入相关的类型来返回相应的类,这种方式比较单一 , 可扩展性相对较差;
        工厂方法模式:通过实现类实现相应的方法来决定相应的返回结果,这种方式的可扩展性比较强;
        抽象工厂模式:基于上述两种模式的拓展,且支持细化产品;

简单工厂模式

        静态工厂方法, 可以根据参数的不同返回不同类的实例,专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类,由于工厂方法是静态方法,可通过类名直接调用,而且只需要传入简单的参数即可。将对象的创建和对象本身业务处理分离可以降低系统的耦合度,使得两者修改起来都相对容易。

        缺点:工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,这一点与开闭原则是相违背。将会增加系统中类的个数,在一定程度上增加了系统的复杂度和理解难度,不利于系统的扩展和维护

代码实现

//支付策略
public interface PayStrategy {
    /**
     * 统一下单接口
     * @param payInfoVO
     * @return
     */
    String unifiedOrder(PayInfoVO payInfoVO);

    /**
     * 退款接口
     * @param payInfoVO
     * @return
     */
    default String refund(PayInfoVO payInfoVO ){
        return "";
    }

    /**
     * 查询订单
     * @param payInfoVO
     * @return
     */
    default String queryPayStatus(PayInfoVO payInfoVO){
        return "";
    }

    /**
     * 关闭订单
     * @param payInfoVO
     * @return
     */
    default String closeOrder(PayInfoVO payInfoVO){
        return "";
    }
}



/**
 * 微信支付
 */
@Service
@Slf4j
public class WechatPayStrategy implements PayStrategy {
    @Autowired
    private PayBeanConfig payBeanConfig;
    @Autowired
    private WechatPayConfig payConfig;

    @Autowired
    private CloseableHttpClient wechatPayClient;
    @Override
    public String unifiedOrder(PayInfoVO payInfoVO) {

        //过期时间  RFC 3339格式
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
        //支付订单过期时间
        String timeExpire = sdf.format(new Date(System.currentTimeMillis() + payInfoVO.getOrderPayTimeoutMills()));

        JSONObject amountObj = new JSONObject();
        //数据库存储是double比如,100.99元,微信支付需要以分为单位
        int amount = payInfoVO.getPayFee().multiply(BigDecimal.valueOf(100)).intValue();
        amountObj.put("total", amount);
        amountObj.put("currency", "CNY");

        JSONObject payObj = new JSONObject();
        payObj.put("mchid", payConfig.getMchId());
        payObj.put("out_trade_no", payInfoVO.getOutTradeNo());
        payObj.put("appid", payConfig.getWxPayAppid());
        payObj.put("description", payInfoVO.getTitle());
        payObj.put("notify_url", payConfig.getCallbackUrl());

        payObj.put("time_expire", timeExpire);
        payObj.put("amount", amountObj);
        //回调携带
        payObj.put("attach", "{\"accountNo\":" + payInfoVO.getAccountNo() + "}");


        // 处理请求body参数
        String body = payObj.toJSONString();

        log.debug("请求参数:{}", body);

        StringEntity entity = new StringEntity(body, "utf-8");
        entity.setContentType("application/json");

        HttpPost httpPost = new HttpPost(WechatPayApi.NATIVE_ORDER);
        httpPost.setHeader("Accept", "application/json");
        httpPost.setEntity(entity);

        String result = "";

        try (CloseableHttpResponse response = wechatPayClient.execute(httpPost)) {

            //响应码
            int statusCode = response.getStatusLine().getStatusCode();
            //响应体
            String responseStr = EntityUtils.toString(response.getEntity());

            log.debug("微信支付响应:resp code={},return body={}", statusCode, responseStr);
            //处理成功
            if (statusCode == HttpStatus.OK.value()) {
                JSONObject jsonObject = JSONObject.parseObject(responseStr);
                if (jsonObject.containsKey("code_url")) {
                    result = jsonObject.getString("code_url");
                }
            } else {
                log.error("微信支付响应失败:resp code={},return body={}", statusCode, responseStr);
            }

        } catch (Exception e) {
            log.error("微信支付响应异常信息:{}", e);

        }
        return result;
    }

    @Override
    public String refund(PayInfoVO payInfoVO) {
        return PayStrategy.super.refund(payInfoVO);
    }

    /**
     * 微信支付查询订单状态
     * @param payInfoVO
     * @return
     */
    @Override
    public String queryPayStatus(PayInfoVO payInfoVO) {
        String outTradeNo = payInfoVO.getOutTradeNo();

        String url = String.format(WechatPayApi.NATIVE_QUERY, outTradeNo, payConfig.getMchId());
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");
        String result = "";

        try (CloseableHttpResponse response = wechatPayClient.execute(httpGet)) {
            //响应码
            int statusCode = response.getStatusLine().getStatusCode();
            //响应体
            String responseStr = EntityUtils.toString(response.getEntity());

            log.debug("查询订单响应码:{},响应体:{}", statusCode, responseStr);
            if(statusCode==HttpStatus.OK.value()){
                JSONObject jsonObject = JSONObject.parseObject(responseStr);
                if(jsonObject.containsKey("trade_state")){
                    result=jsonObject.getString("code_url");
                }
            } else {
                log.error("查询订单状态响应失败:resp code={},return body={}", statusCode, responseStr);
            }

        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return result;
    }

    @Override
    public String closeOrder(PayInfoVO payInfoVO) {
        String outTradeNo = payInfoVO.getOutTradeNo();

        String url = String.format(WechatPayApi.NATIVE_CLOSE_ORDER, outTradeNo);
        JSONObject closeOrderObj = new JSONObject();
        closeOrderObj.put("mchid", payConfig.getMchId());

        String body = closeOrderObj.toJSONString();
        log.debug("请求参数:{}", body);
        StringEntity entity = new StringEntity(body, "utf8");
        entity.setContentType("application/json");

        HttpPost httpPost = new HttpPost(url);
        httpPost.setHeader("Accept", "application/json");
        httpPost.setEntity(entity);
        String result = "";
        try (CloseableHttpResponse response = wechatPayClient.execute(httpPost)) {
            //响应码
            int statusCode = response.getStatusLine().getStatusCode();
            log.debug("关闭订单响应码:{},无响应体", statusCode);
            if(statusCode==HttpStatus.NO_CONTENT.value()){
                result="CLOSE_SUCCESS";
            }

        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return result;
    }
}



public class PayStrategyContext {

    private PayStrategy payStrategy;
    public  PayStrategyContext(PayStrategy payStrategy){
        this.payStrategy=payStrategy;
    }

    /**
     * 根据策略对象,执行不同的下单接口
     * @return
     */
    public String executeUnifiedOrder(PayInfoVO payInfoVO){
       return payStrategy.unifiedOrder(payInfoVO);
    }
    /**
     * 根据策略对象,执行不同的退款接口
     * @return
     */
    public String excuteRefund(PayInfoVO payInfoVO){
        return payStrategy.unifiedOrder(payInfoVO);
    }
    /**
     * 根据策略对象,执行不同的关闭接口
     * @return
     */
    public String executeCloseOrder(PayInfoVO payInfoVO){
        return payStrategy.unifiedOrder(payInfoVO);
    }
    /**
     * 根据策略对象,执行不同的查询订单接口
     * @return
     */
    public String executeQueryPayStatus(PayInfoVO payInfoVO){
        return payStrategy.unifiedOrder(payInfoVO);
    }

}


@Component
@Slf4j
public class PayFactory {
    @Autowired
    private AliPayStrategy aliPayStrategy;
    @Autowired
    private WechatPayStrategy wechatPayStrategy;

    /**
     * 创建支付,简单工程模式
     * @param payInfoVO
     * @return
     */
    public String pay(PayInfoVO payInfoVO){
        String payType = payInfoVO.getPayType();
        if(ProductOrderPayTypeEnum.ALI_PAY.name().equals(payType)){
            PayStrategyContext payStrategyContext=new PayStrategyContext(aliPayStrategy);
            return payStrategyContext.executeUnifiedOrder(payInfoVO);
        } else if (ProductOrderPayTypeEnum.WECHAT_PAY.name().equals(payType)) {
            PayStrategyContext payStrategyContext=new PayStrategyContext(wechatPayStrategy);
            return payStrategyContext.executeUnifiedOrder(payInfoVO);
        }
        return "";
    }
    /**
     * 关闭订单,简单工程模式
     * @param payInfoVO
     * @return
     */
    public String closeOrder(PayInfoVO payInfoVO){
        String payType = payInfoVO.getPayType();
        if(ProductOrderPayTypeEnum.ALI_PAY.name().equals(payType)){
            PayStrategyContext payStrategyContext=new PayStrategyContext(aliPayStrategy);
            return payStrategyContext.executeCloseOrder(payInfoVO);
        } else if (ProductOrderPayTypeEnum.WECHAT_PAY.name().equals(payType)) {
            PayStrategyContext payStrategyContext=new PayStrategyContext(wechatPayStrategy);
            return payStrategyContext.executeCloseOrder(payInfoVO);
        }
        return "";
    }
    /**
     * 查询支付状态,简单工程模式
     * @param payInfoVO
     * @return
     */
    public String queryPayStatus(PayInfoVO payInfoVO){
        String payType = payInfoVO.getPayType();
        if(ProductOrderPayTypeEnum.ALI_PAY.name().equals(payType)){
            PayStrategyContext payStrategyContext=new PayStrategyContext(aliPayStrategy);
            return payStrategyContext.executeQueryPayStatus(payInfoVO);
        } else if (ProductOrderPayTypeEnum.WECHAT_PAY.name().equals(payType)) {
            PayStrategyContext payStrategyContext=new PayStrategyContext(wechatPayStrategy);
            return payStrategyContext.executeQueryPayStatus(payInfoVO);
        }
        return "";
    }
    /**
     * 退款,简单工程模式
     * @param payInfoVO
     * @return
     */
    public String refund(PayInfoVO payInfoVO){
        String payType = payInfoVO.getPayType();
        if(ProductOrderPayTypeEnum.ALI_PAY.name().equals(payType)){
            PayStrategyContext payStrategyContext=new PayStrategyContext(aliPayStrategy);
            return payStrategyContext.excuteRefund(payInfoVO);
        } else if (ProductOrderPayTypeEnum.WECHAT_PAY.name().equals(payType)) {
            PayStrategyContext payStrategyContext=new PayStrategyContext(wechatPayStrategy);
            return payStrategyContext.excuteRefund(payInfoVO);
        }
        return "";
    }
}



下单

        以微信支付为例,订单支付链接可以生成二维码供用户扫码支付。建议在客户端生成支付二维码,而不是通过服务端生成。因为通过服务端生成支付二维码会占用服务端的CPU内存和资源,并消耗传输带宽,导致性能较差。

        客户端生成支付二维码的方式相对更为高效,因为它将生成过程移到了用户设备上,减轻了服务端的负担。用户可以直接在自己的设备上生成二维码,并通过微信扫描完成支付,这样不仅提升了支付过程的效率,还能减少服务端的压力,保障系统的稳定性和性能表现。

微信支付-开发者文档


    @Override
    public String unifiedOrder(PayInfoVO payInfoVO) {

        //过期时间  RFC 3339格式
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
        //支付订单过期时间
        String timeExpire = sdf.format(new Date(System.currentTimeMillis() + payInfoVO.getOrderPayTimeoutMills()));

        JSONObject amountObj = new JSONObject();
        //数据库存储是double比如,100.99元,微信支付需要以分为单位
        int amount = payInfoVO.getPayFee().multiply(BigDecimal.valueOf(100)).intValue();
        amountObj.put("total", amount);
        amountObj.put("currency", "CNY");

        JSONObject payObj = new JSONObject();
        payObj.put("mchid", payConfig.getMchId());
        payObj.put("out_trade_no", payInfoVO.getOutTradeNo());
        payObj.put("appid", payConfig.getWxPayAppid());
        payObj.put("description", payInfoVO.getTitle());
        payObj.put("notify_url", payConfig.getCallbackUrl());

        payObj.put("time_expire", timeExpire);
        payObj.put("amount", amountObj);
        //回调携带
        payObj.put("attach", "{\"accountNo\":" + payInfoVO.getAccountNo() + "}");


        // 处理请求body参数
        String body = payObj.toJSONString();

        log.info("请求参数:{}", body);

        StringEntity entity = new StringEntity(body, "utf-8");
        entity.setContentType("application/json");

        HttpPost httpPost = new HttpPost(WechatPayApi.NATIVE_ORDER);
        httpPost.setHeader("Accept", "application/json");
        httpPost.setEntity(entity);

        String result = "";

        try (CloseableHttpResponse response = wechatPayClient.execute(httpPost)) {

            //响应码
            int statusCode = response.getStatusLine().getStatusCode();
            //响应体
            String responseStr = EntityUtils.toString(response.getEntity());

            log.info("微信支付响应:resp code={},return body={}", statusCode, responseStr);
            //处理成功
            if (statusCode == HttpStatus.OK.value()) {
                JSONObject jsonObject = JSONObject.parseObject(responseStr);
                if (jsonObject.containsKey("code_url")) {
                    result = jsonObject.getString("code_url");
                }
            } else {
                log.error("微信支付响应失败:resp code={},return body={}", statusCode, responseStr);
            }

        } catch (Exception e) {
            log.error("微信支付响应异常信息:{}", e);

        }
        return result;
    }

查询和关闭订单

查询订单

@Override
    public String queryPayStatus(PayInfoVO payInfoVO) {
        String outTradeNo = payInfoVO.getOutTradeNo();

        String url = String.format(WechatPayApi.NATIVE_QUERY,outTradeNo,payConfig.getMchId());
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept","application/json");

        String result = "";
        try(CloseableHttpResponse response = wechatPayClient.execute(httpGet)){

            //响应码
            int statusCode = response.getStatusLine().getStatusCode();
            //响应体
            String responseStr = EntityUtils.toString(response.getEntity());

            log.debug("查询响应码:{},响应体:{}",statusCode,responseStr);

            if(statusCode == HttpStatus.OK.value()){
                JSONObject jsonObject = JSONObject.parseObject(responseStr);
                if(jsonObject.containsKey("trade_state")){
                    result = jsonObject.getString("trade_state");
                }
            }else {
                log.error("查询支付状态响应失败:{},响应体:{}",statusCode,responseStr);
            }


        }catch (Exception e){
            log.error("微信支付响应异常:{}",e);
        }

        return result;
    }

 关闭订单

 @Override
    public String closeOrder(PayInfoVO payInfoVO) {

        String outTradeNo = payInfoVO.getOutTradeNo();

        JSONObject payObj = new JSONObject();
        payObj.put("mchid",payConfig.getMchId());

        String body = payObj.toJSONString();

        log.debug("请求参数:{}",body);
        //将请求参数设置到请求对象中
        StringEntity entity = new StringEntity(body,"utf-8");
        entity.setContentType("application/json");

        String url = String.format(WechatPayApi.NATIVE_CLOSE_ORDER,outTradeNo);
        HttpPost httpPost = new HttpPost(url);
        httpPost.setHeader("Accept","application/json");
        httpPost.setEntity(entity);

        String result = "";
        try(CloseableHttpResponse response = wechatPayClient.execute(httpPost)){

            //响应码
            int statusCode = response.getStatusLine().getStatusCode();
            log.debug("关闭订单响应码:{},无响应体",statusCode);
            if(statusCode == HttpStatus.NO_CONTENT.value()){
                result = "CLOSE_SUCCESS";
            }

        }catch (Exception e){
            log.error("微信支付响应异常:{}",e);

        }

        return result;
    }

回调验签

        商户系统在接收到通知后,可能会出现多次重复发送通知的情况。为了保证系统的稳定性和数据的完整性,商户系统必须能够正确处理这些重复的通知。推荐的做法是,在商户系统接收到通知并进行处理时,首先应该检查相应业务数据的状态,并判断该通知是否已经被处理过。如果该通知尚未被处理,则执行相应的处理逻辑;如果已经处理过,则直接返回处理结果,标记为成功。在进行业务数据状态检查和处理之前,务必采用数据锁进行并发控制,以防止函数重入带来的数据混乱问题。

        若在一定的通知频率后商户系统仍未收到微信侧的回调通知,商户应该主动调用查询订单接口来确认订单状态。此举可以确保订单状态的准确性和及时性,避免因回调通知未到达而导致的业务延误。

        另外,需要特别注意的是,回调URL必须是外部可正常访问的,并且不能携带任何后缀参数。这样可以确保微信支付系统能够顺利地发送回调通知,并且商户系统能够正确地接收和处理这些通知,从而保证支付流程的顺畅进行。

内网穿透工具

        在确保支付成功后,需要配置回调通知应用服务器订单支付成功的过程中,必须设置相应的域名配置。然而,在本地电脑进行开发时,由于微信和支付宝无法直接回调到本地,因此需要进行地址映射的配置。这意味着我们需要配置一个外部服务器地址,以便通过该地址访问当前开发电脑的服务。

        针对微信登录、授权、支付等功能,都需要借助域名映射工具来配合完成。这些工具可以让开发人员在本地开发环境中模拟域名回调,使得开发和调试过程更加顺畅。通过配置合适的地址映射工具,我们能够在本地开发环境中完整地模拟出线上的支付流程,从而更加高效地进行开发和测试工作。

        ngrock https://ngrok.com/

        花生壳 https://hsk.oray.com/

        小米球 http://ngrok.ciqiuwl.cn/

        natapp(采用) https://natapp.cn/

./natapp -authtoken=b1824af621e40514
@Controller
@RequestMapping("/api/callback/order/v1")
@Slf4j
public class PayCallbackController {

    @Autowired
    private WechatPayConfig wechatPayConfig;

    @Autowired
    private ProductOrderService productOrderService;

    @Autowired
    private ScheduledUpdateCertificatesVerifier verifier;

    /**
     * 获取报文
     * 验证签名(确保是微信传输过来的)
     * 解密(AES对称解密出原始数据)
     * 处理业务逻辑
     * 响应请求
     *
     * @param request
     * @param response
     * @return
     */
    @PostMapping("wechat")
    @ResponseBody
    public Map<String, String> wechatPayCallback(HttpServletRequest request, HttpServletResponse response) {

//        获取header信息

//        证书序列号,微信平台
        String serialNo = request.getHeader("Wechatpay-Serial");
//        签名
        String signature = request.getHeader("Wechatpay-Signature");
//        时间戳
        String timestamp = request.getHeader("Wechatpay-Timestamp");
//        随机串
        String nonceStr = request.getHeader("Wechatpay-Nonce");
//        获取报文
        String body = getRequestBody(request);

//        构造签名串:
//        应答时间戳\n
//        应答随机串\n
//        应答报文主体\n
        String signStr = Stream.of(timestamp, nonceStr, body).collect(Collectors.joining("\n", "", "\n"));

        Map<String, String> map = Maps.newHashMapWithExpectedSize(2);
        try {
            //验证签名是否通过
            boolean result = verifiedSign(serialNo, signStr, signature);
            if(result){
                //解密数据
                String plainBody = decryptBody(body);
                log.debug("解密后的明文:{}",plainBody);

                Map<String, String> paramMap = convertWechatPayMsgToMap(plainBody);
                //处理业务逻辑
                productOrderService.proceesOrderCallbackMsg(ProductOrderPayTypeEnum.WECHAT_PAY,paramMap);
                //响应微信
                //            {
                //                "code": "FAIL",
                //                "message": "失败"
                //            }
                map.put("code", "SUCCESS");
                map.put("message", "成功");
            }


        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return map;

    }

    /**
     * 转换body为Map
     * @param payBody
     * @return
     */
    private Map<String,String> convertWechatPayMsgToMap(String payBody){
        HashMap<String, String> paramMap = Maps.newHashMap();
        JSONObject jsonObject = JSONObject.parseObject(payBody);
        //商户订单号
        paramMap.put("out_trade_no",jsonObject.getString("out_trade_no"));
        //交易状态
        paramMap.put("trade_state",jsonObject.getString("trade_state"));
        //自定义数据
        paramMap.put("account_no",jsonObject.getJSONObject("attach").getString("accountNo"));
        return paramMap;
    }
    /**
     *解密body
     * @param body
     * @return
     */
    private String decryptBody(String body) throws UnsupportedEncodingException, GeneralSecurityException {
//        解密body的密文;
        AesUtil aesUtil = new AesUtil(wechatPayConfig.getApiV3Key().getBytes("utf-8"));
        JSONObject jsonObject = JSONObject.parseObject(body);
        JSONObject resource = jsonObject.getJSONObject("resource");
//        "resource": {
//            "original_type": "transaction",
//                    "algorithm": "AEAD_AES_256_GCM",
//                    "ciphertext": "",
//                    "associated_data": "",
//                    "nonce": ""
//        }
        String ciphertext = resource.getString("ciphertext");
        String associatedData = resource.getString("associated_data");
        String nonce = resource.getString("nonce");
        String decryptStr = aesUtil.decryptToString(associatedData.getBytes("utf-8"), nonce.getBytes("utf-8"), ciphertext);
        return decryptStr;
    }

    /**
     * 验证前面
     *
     * @param serialNo  微信平台证书序列号
     * @param signStr   自己组装的签名串
     * @param signature 微信返回的签名
     * @return
     */
    private boolean verifiedSign(String serialNo, String signStr, String signature) throws UnsupportedEncodingException {
        boolean verify = verifier.verify(serialNo, signStr.getBytes("utf-8"), signature);
        return verify;
    }

    /**
     * 读取请求数据流
     *
     * @param request
     * @return
     */
    private String getRequestBody(HttpServletRequest request) {
        StringBuffer sb = new StringBuffer();
        try (ServletInputStream inputStream = request.getInputStream()) {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            log.error("读取数据流异常:{}", e);
        }
        return sb.toString();
    }
}

退款

@Override
    public String unifiedOrder(PayInfoVO payInfoVO) {

        //过期时间  RFC 3339格式
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
        //支付订单过期时间
        String timeExpire = sdf.format(new Date(System.currentTimeMillis() + payInfoVO.getOrderPayTimeoutMills()));

        JSONObject amountObj = new JSONObject();
        //数据库存储是double比如,100.99元,微信支付需要以分为单位
        int amount = payInfoVO.getPayFee().multiply(BigDecimal.valueOf(100)).intValue();
        amountObj.put("total", amount);
        amountObj.put("currency", "CNY");

        JSONObject payObj = new JSONObject();
        payObj.put("mchid", payConfig.getMchId());
        payObj.put("out_trade_no", payInfoVO.getOutTradeNo());
        payObj.put("appid", payConfig.getWxPayAppid());
        payObj.put("description", payInfoVO.getTitle());
        payObj.put("notify_url", payConfig.getCallbackUrl());

        payObj.put("time_expire", timeExpire);
        payObj.put("amount", amountObj);
        //回调携带
        payObj.put("attach", "{\"accountNo\":" + payInfoVO.getAccountNo() + "}");


        // 处理请求body参数
        String body = payObj.toJSONString();

        log.debug("请求参数:{}", body);

        StringEntity entity = new StringEntity(body, "utf-8");
        entity.setContentType("application/json");

        HttpPost httpPost = new HttpPost(WechatPayApi.NATIVE_ORDER);
        httpPost.setHeader("Accept", "application/json");
        httpPost.setEntity(entity);

        String result = "";

        try (CloseableHttpResponse response = wechatPayClient.execute(httpPost)) {

            //响应码
            int statusCode = response.getStatusLine().getStatusCode();
            //响应体
            String responseStr = EntityUtils.toString(response.getEntity());

            log.debug("微信支付响应:resp code={},return body={}", statusCode, responseStr);
            //处理成功
            if (statusCode == HttpStatus.OK.value()) {
                JSONObject jsonObject = JSONObject.parseObject(responseStr);
                if (jsonObject.containsKey("code_url")) {
                    result = jsonObject.getString("code_url");
                }
            } else {
                log.error("微信支付响应失败:resp code={},return body={}", statusCode, responseStr);
            }

        } catch (Exception e) {
            log.error("微信支付响应异常信息:{}", e);

        }
        return result;
    }

延迟队列

        当确认用户支付状态时,我们不能仅仅依赖于微信支付的回调机制。尽管微信支付提供了一种便捷的方式来通知商户系统支付状态的变化,但由于网络存在不确定性,可能会出现网络抖动等问题,导致支付回调无法及时到达商户系统。因此,为了确保支付状态的可靠性,我们需要设计一套兜底策略。

        一种有效的兜底策略是利用 RabbitMQ 的死信队列机制,结合延迟队列的特性。当用户发起支付后,我们将支付信息发送到 RabbitMQ 中,并设置相应的延迟时间。在延迟时间到达后,系统将检查订单的支付状态。如果订单在规定时间内完成支付,则订单状态正常更新;若超时未完成支付,则系统将自动执行关单操作,并相应地修改业务状态,以确保订单数据的一致性和完整性。

        通过引入兜底策略,我们能够有效地应对网络波动和其他不可预测因素对支付流程的影响,保障了系统的稳定性和可靠性。同时,这种设计也提高了系统对异常情况的容错能力,为用户提供更加可靠的支付体验。

@Component
@Slf4j
@RabbitListener(queuesToDeclare = {@Queue("order.close.queue"),@Queue("order.update.queue")})
public class ProductOrderMQListener {


    @Autowired
    private ProductOrderService productOrderService;

    @RabbitHandler
    public void productOrderHandler(EventMessage eventMessage, Message message, Channel channel){
        log.info("监听到消息ProductrOrderMQListener message:{}", eventMessage);

        try {
            productOrderService.handleProductOrderMessage(eventMessage);
        } catch (Exception e) {
            log.error("消费失败:{}", eventMessage);
            throw new BizException(BizCodeEnum.MQ_CONSUME_EXCEPTION);
        }
        log.info("消费成功");
    }

}


/**
     * 处理订单相关消息
     * @param eventMessage
     */
    @Override
    public void handleProductOrderMessage(EventMessage eventMessage) {

        String messageType = eventMessage.getEventMessageType();

        try {
            if(EventMessageTypeEnum.PRODUCT_ORDER_NEW.name().equalsIgnoreCase(messageType)){
                //关闭订单
                this.closeProductOrder(eventMessage);
            } else if (EventMessageTypeEnum.PRODUCT_ORDER_PAY.name().equalsIgnoreCase(messageType)) {
                //订单已经支付,更新订单状态
                String outTradeNo = eventMessage.getBizId();
                Long accountNo = eventMessage.getAccountNo();
                int rows = productOrderManager.updateOrderPayState(outTradeNo, accountNo, ProductOrderStateEnum.PAY.name(), ProductOrderStateEnum.NEW.name());
                log.info("订单更新成功:rows={},eventMessage={}",rows,eventMessage);
            }

        } catch (Exception e) {
            log.error("消费者消费失败:{}",eventMessage);
            throw new BizException(BizCodeEnum.MQ_CONSUME_EXCEPTION);
        }
       

    }
/**
     * //延迟消息的时间 需要比订单过期 时间长一点,这样就不存在查询的时候,用户还能支付成功
     * <p>
     * //查询订单是否存在,如果已经支付则正常结束
     * //如果订单未支付,主动调用第三方支付平台查询订单状态
     * //确认未支付,本地取消订单
     * //如果第三方平台已经支付,主动的把订单状态改成已支付,造成该原因的情况可能是支付通道回调有问题,然后触发支付后的动作,如何触发?RPC还是?
     *
     * @param eventMessage
     * @return
     */
    @Override
    public boolean closeProductOrder(EventMessage eventMessage) {
        String outTradeNo = eventMessage.getBizId();
        Long accountNo = eventMessage.getAccountNo();
        ProductOrderDO productOrderDO = productOrderManager.findByOutTradeNoAndAccountNo(outTradeNo, accountNo);
        if (productOrderDO == null) {
            //订单不存在
            log.warn("订单不存在");
            return true;

        }
        if (productOrderDO.getState().equalsIgnoreCase(ProductOrderStateEnum.PAY.name())) {
            //已经支付
            log.info("直接确认消息,订单已经支付:{}", eventMessage);
            return true;
        } else if (productOrderDO.getState().equalsIgnoreCase(ProductOrderStateEnum.NEW.name())) {
            //未支付,需要向第三方支付平台查询状态
            PayInfoVO payInfoVO = new PayInfoVO();
            payInfoVO.setPayType(productOrderDO.getPayType());
            payInfoVO.setOutTradeNo(outTradeNo);
            payInfoVO.setAccountNo(accountNo);

            //调用第三方支付平台查询状态
            String result = "";
            if (StringUtils.isBlank(result)) {
                //如果为空,则未支付成功,本地取消订单
                productOrderManager.updateOrderPayState(outTradeNo, accountNo, ProductOrderStateEnum.CANCEL.name(), ProductOrderStateEnum.NEW.name());
                log.info("未支付成功,本地取消订单:{}", eventMessage);
            } else {
                //支付成功,主动把订单状态更新成支付
                //触发支付成功后的逻辑
                productOrderManager.updateOrderPayState(outTradeNo, accountNo, ProductOrderStateEnum.PAY.name(), ProductOrderStateEnum.NEW.name());
                log.warn("支付成功,但是微信回调通知失败,需要排查问题:{}", eventMessage);
            }
        }
        return true;
    }
@Override
    public int updateOrderPayState(String outTradeNo, Long accountNo, String newState, String oldState) {
        return productOrderMapper.update(null, new UpdateWrapper<ProductOrderDO>()
                .eq("account_no", accountNo)
                .eq("out_trade_no", outTradeNo)
                .eq("state", oldState)
                .eq("del", 0)
                .set("state", newState));
    }

  • 39
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值