Technology stack: java+springboot+rabbitmq
In this article, let's discuss the design scheme of merchant transaction result notification. Students who do payment know that some transaction types are asynchronous transactions, that is, when the transaction message is sent to the bank or a third-party payment channel, the other party does not return the final status (transaction success / failure) response, but returns that the transaction has been accepted. How do you get the final status of the transaction, Did the deal succeed or fail? How can we inform our merchant system of the transaction results? This is the topic we will discuss.
There are generally two ways to obtain the final status: the first is to actively query the transaction results, and the second is to wait for the channel to actively notify after the transaction is completed. The former requires us to connect with the transaction result query interface of the payment channel to regularly obtain the transaction results in the processing order, and the latter requires us to develop corresponding interfaces according to the format and content of the notification message of the channel party, We discuss the latter in this article.
When a transaction occurs, the payment channel returns to the transaction has been accepted, and we have returned the transaction to the merchant. It has been accepted. The transaction process is over, and we are waiting to receive the notification of the result of the transaction. When the channel completes the transaction, we call the result of the delivery of our transaction and inform us of the result of the transaction. Obtain the relevant transaction parameter data, change the status of the corresponding transaction in our database to the final status, and then notify the merchant of the transaction result. The merchant gets the payment result and displays the page of successful payment in the user's mobile phone.
The process is roughly as above. What we need to solve is if we fail to notify the merchant, what should we do? Is it a channel dependent re notification? (in this scheme, if the payment platform fails to notify the merchant, we will respond to the notification result of the channel as failure, the channel receives the notification failure message, and then sends the notification again according to its own strategy) or do we retry the failure by our own system? (the channel notifies the payment platform, and the platform responds successfully to the payment channel after the order is processed. It does not depend on the result of notifying the merchant.) the former relies on the payment channel to make a failure retry notification, which has a great risk. If it doesn't work well, it will bring down the payment system. This is not a problem we discuss. How should the latter make a repeated notification after the failure of its own system? After the notification fails, retry again at a specified time interval, such as 1s, 3s, 6s... Until the merchant system responds to the notification successfully, or terminates the notification when the maximum number of notifications is reached. The overall design logic is as follows:
About the next process, this design scheme is implemented by RabbitMQ, mainly using its dead letter queue to achieve the desired business effect. After receiving the channel transaction result notification, modify the order transaction status of the payment platform, and then send the notification task to the MQ queue to respond to the success of the payment channel notification.
Before talking about the above figure, we should first understand the basic details, what is dead letter, how to become dead letter, dead letter queue, dead letter exchanger, message level timeout, queue level timeout, queue binding, etc.. These are basic things. In order not to want the article to be too long, students Baidu by themselves. If you don't take good care of it, please forgive me.
As shown in the figure, Queue.xxx Queue represents that the message becomes dead letter after XXX milliseconds. Application1, as the producer, sends the notification message to the MQ Queue, and Application2, as the consumer, obtains the notification message task, assembles the message to request the merchant system. If the Merchant fails to call, re send the task to the MQ Queue. Note that the original Queue cannot be put here, On the one hand, putting into the original Queue can not achieve the desired delay effect, on the other hand, there will be interesting accidents. We do not expand here. We need to initialize multiple queues according to the delay interval policy and bind them to the corresponding dead letter exchanger. At the same time, when the notification fails and is re sent to MQ, the number of notifications will be stored in the message header information to limit the number of notifications, and determine which Queue.xxx should be sent each time according to the number of notifications. The message is sent to the specified retry Queue. Since there is no consumer in the retry Queue.xxx, the message in it will become dead- message after the set time. It will be routed to the Queue for re consumption through the bound dlx.exchange and RoutingKey configuration. In this way, we can achieve the desired effect.
The core code fragment and other logic are not difficult. It is a small application of MQ. The source code is temporarily disclosed
/** * Create exponential retry queue */ public void generateRetryQueues(){ LoggerUtil.info(logger, "Start creating exponential retry queue"); for (int i = 0; i < MAX_RETRY_COUNT; i++) { String queueName = null; try { Map<String, Object> args = new HashMap<String, Object>(); args.put("x-dead-letter-exchange", MqConstant.APPLICATION1_TOPIC_EXCHANGE); args.put("x-dead-letter-routing-key", MqConstant.APPLICATION1_APPLICATION2_NOTIFY_KEY); String expiration = String.valueOf(Double.valueOf(Math.pow(2, i)).intValue()*MqConstant.APPLICATION2_QUEUE_TRANS_MESSAGE_DELAY_MILLISECONDS); queueName = MqConstant.APPLICATION2_RETRY_QUEUE + "." + expiration; Queue queue = QueueBuilder.durable(queueName).withArguments(args).build(); rabbitAdmin.declareQueue(queue); LoggerUtil.info(logger, "The exponential retry queue was created successfully[queueName:{}]", queueName); exponentialRetryQueues.add(queue); } catch (Throwable e){ LoggerUtil.error(logger, "Failed to create exponential retry queue[i:{}, queueName:{}, e.message: {}],abnormal:", i, queueName, e.getMessage(), e); } } LoggerUtil.info(logger, "End of creating "exponential" retry queue"); }