[Payment System] How to implement your own asynchronous callback notification

        In the payment system, when we receive an asynchronous notification from a third-party payment, how should we solve it if we also have business requirements for callback notifications?

        There are many solutions on the Internet, such as using delayed messages in message queues or using dynamic timers.

        Here we use the timer method, which is simpler and lighter.

        Anyone who has been in contact with payment may know that when the user pays successfully, the payment platform will send us multiple notifications to tell us that the payment is successful. After receiving the notification message, we can operate the order to complete the entire payment process. .But if we want to build our own payment platform, we also need to send notifications to users who use our platform. Usually this notification is sent multiple times at different time intervals to avoid network problems and users not being able to receive them. Here is how to use timing Send notifications to users in the form of a server. Of course, the premise is the same as other third-party payments. The user needs to pass me a callback address when placing an order. We need to send multiple notifications to this address.

        The dynamic timer method of hutool is used here. The code is as follows:

  1. Turn on the hutool timer, and then turn on the timer second-level task in the startup class
@SpringBootApplication
public class XsPlatformApplication {

	public static void main(String[] args) {
		SpringApplication.run(XsPlatformApplication.class, args);
		// 支持秒级别定时任务
		CronUtil.setMatchSecond(true);
		CronUtil.start();
	}


}

        2. Write a method to send asynchronous notifications (in the following code, we add @Async to the method of sending callback notifications so that it will not affect the main business and optimize the processing speed of the main business)

	/**
	 * 充值回调
	 *
	 * @author jy
	 * @since 2020/10/25 2:03
	 */
	@Async("taskExecutor")
	public void action(NotifyVo notifyVo, String url, LocalDateTime payTime, Long orderId) {
        // 获取用户信息及秘钥
		MerUser merUser = merUserService.getOneByAccount(notifyVo.getMerchantId());
		String secret = merUser.getSecret();
        // 对回调信息进行加密,避免回调信息被拦截篡改,加密方式因人而异,可以是rsa,md5等等
		String generateSign = notifyVo.generateSign(secret);
		notifyVo.setCode(generateSign);
        // 这里定义发送三次回调 
		for (int i = 1; i < 3; i++) {
            //计算发送回调的时间
			LocalDateTime offset = LocalDateTimeUtil.offset(payTime, 5 * i, ChronoUnit.SECONDS);
			int year = offset.getYear();
			int monthValue = offset.getMonthValue();
			int dayOfMonth = offset.getDayOfMonth();
			int hour = offset.getHour();
			int minute = offset.getMinute();
			int second = offset.getSecond();
			int finalI = i;
            //这里一定要生成定时器的id,方便发送之后销毁定时器
			String id = IdUtil.fastUUID();
			CronUtil.schedule(id, StrUtil.format("{} {} {} {} {} ? {}", second, minute, hour, dayOfMonth, monthValue, year), (Task) () -> {
				log.info("第{}次执行回调任务,订单id:{},发送时间:{}", finalI + 1, orderId, LocalDateTime.now());
                // 发送
				sendAction(url, JSONUtil.toJsonStr(notifyVo), orderId, id);
			});
		}
		log.info("第一次执行回调任务,订单id:{},发送时间:{}", orderId, LocalDateTime.now());
		// 此处表示立即发送第一次
        sendAction(url, JSONUtil.toJsonStr(notifyVo), orderId, null);
	}

	void sendAction(String url, String body, Long orderId, String cronId) {
		String post = null;
		// 无论是否发送成功 只要有发送动作 就记为已发送  并且不应影响之后的定时任务
		try {
			post = HttpUtil.post(url, body);
			log.info("发送支付回调请求,订单id:{},发送时间:{},响应结果:{},发送内容:{}", orderId, LocalDateTime.now(), post, body);
		} catch (Exception e) {
			log.error("发送支付回调请求失败,订单id:" + orderId + ",发送时间:" + LocalDateTime.now() + ",发送内容:" + body, e);
		} finally {
            // 这里注意,定时器不是执行之后就会自动销毁,这里必须手动销毁,不然运行久了会导致内存溢出等问题
			if (StrUtil.isNotBlank(cronId)) {
				log.info("移除支付回调定时任务,id:{}", cronId);
				CronUtil.remove(cronId);
			}
		}
        // 订单都会有个通知状态,未发送,已发送,已确认
        // 如果发送回调,用户给我们返回Success,表示他们已经正确接收
		LambdaUpdateWrapper<Order> updateWrapper = Wrappers.lambdaUpdate();
		updateWrapper.eq(Order::getId, orderId)
				.set(Order::getNotifyStatus, StrUtil.equalsIgnoreCase(post, "SUCCESS") ? OrderNotifyStatus.CONFIRMED : OrderNotifyStatus.SENDED)
				.and(e -> e.eq(Order::getNotifyStatus, OrderNotifyStatus.unsent).or().eq(Order::getNotifyStatus, OrderNotifyStatus.SENDED));
		orderService.update(updateWrapper);
	}

3. Encapsulate callback entity class and call

The content of the callback entity class has no fixed format and mainly includes payment amount, merchant ID, order number, and order status.

This method is mainly used to send callbacks to merchants on our own platform after receiving third-party payment callbacks, or for users to reissue order callback notifications.

	@Override
	public void sendNotify(Order order) {
		Order res = getById(order);
		NotifyVo notifyVo = new NotifyVo();
        // 商户id
		notifyVo.setMerchantId(res.getUserId());
		// 支付金额
        notifyVo.setAmount(NumberUtil.mul(res.getMoney(), 100).setScale(0, RoundingMode.DOWN).toString());
		// 商户下单传递的订单id
        notifyVo.setRequestId(res.getUserSetId());
        // 我们平台生成的订单id
		notifyVo.setOrderId(res.getId());
        // 订单支付状态
		notifyVo.setStatus("SUCCESS");
        // 商户下单传递的通知地址
		String notifyUrl = res.getNotifyUrl();
        // 调用发送通知
		sendNotify.action(notifyVo, notifyUrl, LocalDateTime.now(), order.getId());
		
	}

Guess you like

Origin blog.csdn.net/qq_39078783/article/details/131321101