통합 결제 시스템의 설계 및 구현

저자의 WeChat 공개 계정을 팔로우하는 것을 환영합니다: 프로그래밍 코코넛

결제센터의 존재 목적

결제센터 시스템은 내부적으로는 각 사업부문에 대해 통합결제, 환불 및 기타 서비스를 제공하고, 외부적으로는 제3자 결제 또는 은행 서비스와 연결하여 자금의 흐름을 실현합니다. 아래 그림과 같이:

대부분의 회사는 기본적으로 다음과 같은 장점이 있는 이러한 구조를 가지고 있습니다.

  1. 통합 결제 서비스를 구성하여 업무 라인 접속 비용과 반복적인 R&D 비용을 절감합니다.
  2. 회사 비즈니스의 빠른 발전을 위한 조건을 제공하기 위해 혁신적인 비즈니스에 대한 더 빠르고 더 나은 지원.
  3. 안전하고 안정적이며 확장 가능한 지불 시스템을 구축하는 데 더 도움이 됩니다.
  4. 핵심 지불 데이터의 강수 및 통합 활용에 도움이 됩니다.

지불 과정

위의 그림은 사용자 결제의 주요 프로세스를 보여주며 3단계로 나뉩니다.

  1. 사용자는 비즈니스 주문 확인 페이지에서 계산원 페이지를 불러옵니다.
  2. 사용자는 캐셔 페이지에서 결제 수단을 선택하고, 결제를 확인하고, 제3자 결제 페이지를 표시하고, 비밀번호를 입력하고, 실제 결제 행동을 수행합니다.
  3. 시스템은 이용자의 결제 결과를 처리하여 이용자 및 각종 관련 시스템에 알립니다.

세 단계는 아래에 자세히 설명되어 있습니다.

1. 상인을 깨우다

  1. 사용자는 주문 확인 페이지에서 "결제로 이동" 버튼을 클릭하여 계산원의 결제 주문 인터페이스를 호출합니다.
  2. 계산원은 주문 정보를 캐싱 및 저장한 다음 주문 ID를 계산원 URL에 조합하여 주문 시스템에 반환합니다.
  3. 주문 시스템은 계산원의 주소를 수신하고 계산원 페이지로 이동합니다.

위의 그림은 두 개의 비즈니스 라인(경관 비즈니스 라인, 호텔 비즈니스 라인)에 의해 유발된 캐셔 페이지를 보여주며, 대략 세 영역으로 나눌 수 있습니다.

페이지 상단에는 남은 지불 시간과 지불해야 할 금액이 표시됩니다.

중간 부분은 계산원이 정의한 데이터 형식에 따라 비즈니스 라인에서 동적으로 전송되는 주문 정보입니다.

나머지 부분은 지불 채널을 보여줍니다. 지불 채널은 또한 지불 배경 관리 시스템의 비즈니스 라인에 의해 자신의 필요에 따라 구성됩니다. 원하는 지불 방법과 순서를 사용자 정의할 수 있습니다.

2. 사용자가 결제 확인

  1. 사용자는 캐셔 페이지에서 결제수단(Alipay 결제, WeChat 결제, 은행 카드 결제 등)을 선택하고 Pay Now 버튼을 클릭합니다.
  2. 지불 센터의 주문 생성 인터페이스를 호출하고, 지불 센터가 제3자 지불 생성 인터페이스를 호출하고, 동시에 지불 정보를 반환합니다. 지불 센터는 반환된 매개변수를 처리하고 계산원에게 반환합니다.
  3. 금전 등록기는 지불 센터에서 반환된 매개변수를 전달하고 타사 인터페이스를 호출하며 타사 금전 등록기를 불러옵니다.
  4. 사용자는 비밀번호를 입력하고 즉시 지불합니다.

3. 결제결과 처리

  1. 3자 시스템이 차감처리를 하여 계산대에 결과를 반환(현재 WeChat 결제는 결제반환, Alipay는 최종 결제상태(결제성공 또는 결제실패)를 반환),

다음 단계는 특정 순서 없이 비동기적으로 수행됩니다.

  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

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

추천

출처juejin.im/post/7119441713784946695