Diseño e Implementación de Sistema de Pago Agregado

Bienvenido a seguir la cuenta pública de WeChat del autor: Programación de coco

La finalidad de la existencia del centro de pago

El sistema del centro de pago proporciona pagos unificados, reembolsos y otros servicios para cada línea comercial internamente, y externamente se conecta con pagos de terceros o servicios bancarios para realizar el flujo de fondos. Como se muestra abajo:

La mayoría de las empresas cuentan básicamente con esta estructura, que tiene las siguientes ventajas:

  1. Forme un servicio de pago unificado para reducir los costos de acceso a la línea comercial y los costos repetitivos de I+D.
  2. Apoyo mejor y más rápido para los negocios innovadores, para proporcionar las condiciones para el rápido desarrollo de los negocios de la empresa.
  3. Es más propicio para construir un sistema de pago seguro, estable y escalable.
  4. Es propicio para la precipitación y la utilización unificada de datos de pago básicos.

proceso de pago

La figura anterior muestra el proceso principal de pago del usuario, que se divide en tres pasos:

  1. El usuario evoca la página del cajero en la página de confirmación del pedido comercial.
  2. El usuario selecciona el método de pago en la página del cajero, confirma el pago, muestra la página de pago de terceros, ingresa la contraseña y realiza el comportamiento de pago real.
  3. El sistema procesa el resultado del pago del usuario e informa al usuario y a varios sistemas relacionados.

Los tres pasos se detallan a continuación:

1. Despertar a los cajeros comerciales

  1. El usuario hace clic en el botón "Ir al pago" en la página de confirmación del pedido para llamar a la interfaz de pago del cajero.
  2. El cajero almacena en caché y almacena la información del pedido, y luego ensambla el ID del pedido en la URL del cajero y lo devuelve al sistema de pedidos.
  3. El sistema de pedidos recibe la dirección del cajero y salta a la página del cajero.

La imagen de arriba muestra la página del cajero evocada por dos líneas comerciales (línea comercial escénica, línea comercial hotelera), que se pueden dividir aproximadamente en tres áreas:

La parte superior de la página muestra el tiempo restante para el pago y el monto adeudado;

La parte central es la información del pedido, que es transmitida dinámicamente por la línea de negocio según el formato de datos definido por el cajero;

La parte restante muestra los canales de pago. Los canales de pago también son configurados por la línea de negocio en el sistema de gestión de antecedentes de pago según sus propias necesidades. Puede personalizar qué métodos de pago desea y su orden.

2. El usuario confirma el pago

  1. El usuario selecciona el método de pago (pago Alipay, pago WeChat, pago con tarjeta bancaria, etc.) en la página del cajero y hace clic en el botón Pagar ahora.
  2. Llame a la interfaz de creación de pedidos del centro de pago, el centro de pago llama a la interfaz de creación de pago de terceros y devuelve la información de pago sincrónicamente. El centro de pago procesa los parámetros devueltos y los devuelve al cajero.
  3. La caja registradora lleva los parámetros devueltos por el centro de pago, llama a la interfaz de tres partes y evoca la caja registradora de tres partes.
  4. El usuario ingresa la contraseña y paga inmediatamente.

3. Procesamiento de resultados de pago

  1. El sistema tripartito realiza el procesamiento de la deducción y devuelve el resultado al cajero (actualmente, el pago de WeChat está devolviendo el pago, Alipay devuelve el estado del pago final (pago exitoso o pago fallido)),

Los siguientes pasos se realizan de forma asíncrona, sin ningún orden en particular.

  1. 收银台拿到三方返回的结果,确认用户已经支付,则分配定时任务轮询查询(注意超时时间)后台支付结果,拿到终态之后跳转到相应页面,
  2. 三方系统支付成功后会通知支付中心结果,支付中心做好自身逻辑处理,异步通知订单系统,然后返回三方系统通知结果,
  3. 如果长时间未收到三方支付结果的通知,为了防止掉单,支付中心会发起主动查询来获取支付最终结果,以保证支付结果的及时更新。
  4. 当然业务线订单系统为了防止支付系统出现异步通知问题,也可以定时轮询支付中心的支付状态,防止掉单。(图中未画)

支付中心系统一些问题及解决方案

1. 支付订单超时关闭问题

如果用户长时间没有支付,一般都会有一个超时时间(如上图商户收银台的支付剩余时间),到达这个超时时间支付单会自动关闭。实现此需求有很多方式,比如:

1. 轮询 DB

定时轮询DB,取出达到超时时间且在支付中的数据,然后执行关闭逻辑。

缺点:1. 存在延迟,取决于定时任务的频率。2. 影响数据库性能。

2. JDK 延时队列(DelayQueue)和时间轮算法

这两种的算法的实现方式自行搜索。

共同的缺点是 1. 数据易丢失,由于数据存储在内存中,服务重启后数据全部消失。2. 有内存限制,如果数据量过大,会出现OOM异常。

3. RocketMQ 延时队列

RocketMQ 支持消息延时发送,社区版不支持任意等级的延迟,目前默认支持18个延时等级:

1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h 
复制代码

比如支付单30分钟过期,在支付单创建成功后发送延迟消息(延时等级为 16),消费者在30分钟后会拉取到该消息然后执行关闭逻辑。

RocketMQ 延时队列,无论在数据安全性和及时性都有明显的优势,但是目前社区版没有支持任意级别的延迟。

目前我们使用的是 RocketMQ 延时队列实现的订单关闭。

2. 保证支付结果实时性

三方支付系统支付成功后99.9%的情况下都会回调通知我们,但也难免有意外,比如三方延迟回调或者三方系统宕机,为了保证支付结果的实时性,三方支付也要求我们不能完全依赖于回调接口,所以我们需要定时的调用主动查询接口来查询三方的支付结果。这里我们也是使用的 RocketMQ 延时队列实现的:

  1. 调用三方支付创单成功后,发送<支付主动查询>延时MQ消息。
  2. 消费消息,判断支付状态是否到达终态,如果到达终态,则返回处理成功,否则调用三方支付查询接口,如果支付成功则处理成功业务,返回处理成功。
  3. 如果客户未支付则判断是否达到最大的重试次数,如果达到最大重试次数则停止<支付主动查询>的重试,否则解析重试规则,发送下一轮的延时消息。

有三个重要参数,这些参数可以放到配置中心或者配置库中,

// 初始延迟级别,对应RocketMQ延时等级,比如3对应的延时时间就是10s
private Integer queryInitLevel = 3;

// 重试次数
private Integer queryCount = 6;

// 重试级别,对应RocketMQ延时等级,5s,10s,30s,1m,10m,20m
private String queryDelayLevels = 2,3,4,5,14,15;
复制代码

支付创单成功后发送延时消息:

public void payQueryTask(String orderNo) {
        PayQueryMessage payQueryMessage = new PayQueryMessage();
        payQueryMessage.setOrderNo(orderNo);

        RetryMessage<PayQueryMessage> retryMessage = new RetryMessage<>();
        retryMessage.setTotalCount(queryCount);
        retryMessage.setDelayLevels(queryDelayLevels);
        retryMessage.setTopic(TopicConst.PAY_QUERY_TOPIC);
        retryMessage.setEventType(RetryEventTypeEnum.PAY_QUERY);
        retryMessage.setEventDesc(RetryEventTypeEnum.PAY_QUERY.getDesc());
        retryMessage.setData(payQueryMessage);

        log.info("{} - 发送消息, retryMessage: {}", LOG_DESC, retryMessage);
        rocketMqProducer.asyncSend(retryMessage.getTopic(), JsonUtil.toJson(retryMessage),
                CodeEnum.codeOf(RocketMQDelayLevelEnum.class, queryInitLevel).orElse(RocketMQDelayLevelEnum.FiveSeconds), LOG_DESC);
}
复制代码

判断的是否继续执行任务:

public void sendDelayRetry(RetryMessage<?> retryMessage) {
        int currentCount;
        retryMessage.setCurrentCount(currentCount = retryMessage.getCurrentCount() + 1);
        // 重试达到最大次数
        if (currentCount > retryMessage.getTotalCount()) {
            log.warn("{} - 达到最大次数-{}, 停止重试! retryMessage: {}", retryMessage.getEventDesc(), retryMessage.getTotalCount(), JsonUtil.toJson(retryMessage));
            return;
        }
        log.info("{} - 发送重试消息-{}/{}, retryMessage: {}", retryMessage.getEventDesc(), retryMessage.getCurrentCount(), retryMessage.getTotalCount(), JsonUtil.toJson(retryMessage));
        int delayLevel = Integer.parseInt(retryMessage.getDelayLevels().split(",")[retryMessage.getCurrentCount() - 1]);
        rocketMqProducer.asyncSend(retryMessage.getTopic(), retryMessage,
                CodeEnum.codeOf(RocketMQDelayLevelEnum.class, delayLevel).orElse(RocketMQDelayLevelEnum.FiveSeconds), retryMessage.getEventDesc()+", 发送重试消息");
    }
复制代码

3. 支付结果通知上游容错

在回调通知上游系统支付结果时,可能会回调失败,比如网络异常或上游系统发生短时故障,如果发生这种情况我们单靠简单的重试是无法完全解决问题的。为了尽可能的通知成功,我们需要针对没有通知成功的数据,每隔一段时间通知一次,那这个需求和我们上一个问题差不多,所以可以复用我们的延时重试框架。

流程和保证支付结果实时的差不多,不再赘述。

支付中心系统中设计模式的应用

模板方法

模板方法模式思想:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

简单的理解就是定义一个模版方法,然后子类实现模版方法中的抽象方法实现个性化的需求。

就支付而言,无论何种支付产品,都是走的同一个支付流程,那我们就可以定义一个支付流程的模板,然后每种支付产品实现这个模板中特定步骤来实现自己的特定需求。

策略

策略模式主要思想:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。

在支付系统中,支付结果主动查询需要查询不同的渠道,比如支付宝,微信,银联等,每个渠道查询的方式和参数不尽相同,可以将每种渠道查询封装成不同的策略类,然后根据查询条件来调用不同的策略类。

查询策略有两个策略接口,callChannel功能是组装查询参数和查询三方,execute 是处理三方返回的结果统一为支付中心状态。(因callChannel有其他地方共用所以分开了两个方法)。

Spring 下使用策略模式,在项目启动时,将所有的策略类加载到Map中,然后使用时直接在Map中获取。

@Component
public class PayQueryStrategyContext {

    private final Map<String, PayQueryStrategy> payQueryStrategyMap = Maps.newConcurrentMap();

    public PayQueryStrategyContext(Map<String, PayQueryStrategy> payQueryStrategyMap) {
        this.payQueryStrategyMap.clear();
        payQueryStrategyMap.forEach(this.payQueryStrategyMap::put);
    }

    public PayQueryStrategy getPayQuery(@NotNull String channelCode) {
        return this.payQueryStrategyMap.get(OperationTypeConst.Pay_Query + channelCode);
    }
}
复制代码

扫码_搜索联合传播样式-白色版.png

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿

Supongo que te gusta

Origin juejin.im/post/7119441713784946695
Recomendado
Clasificación