Table of Contents
1. Preparation
2. Idea configuration file preparation
3. Back-end code writing
Interface 1: Payment order
Interface 2: Query order
Interface 3: Order refund
Interface 4: Query refund results
Interface 5: Get the total bill
Interface 6: Cancel order
Interface 7: Callback interface
Scheduled tasks: Actively query orders
4. Front-end code writing
5.Verification
Pay order
checking order
Order refund
Inquiry for refund
cancel order
1.Preparation
Enter the sandbox console to obtain your buyer, seller ID, gateway and other configuration information
Quick Access – Alipay Document Center (alipay.com)
Download the sandbox tool locally to facilitate payment and debugging, Alipay sandbox version
2.idea configuration file preparation
properties sets your own sandbox information: 8 values
#alipay sandbox environment #My own appid inside the sandbox application alipay.app-id=xxx #Merchant pid inside the sandbox account alipay.seller-id=xxx #AlipayPublicKey Public key mode in sandbox application-》View alipay.alipay-public-key=xxx #Application private key inside the sandbox application alipay.merchant-private-key=xxx #Alipay gateway address inside the sandbox application alipay.gateway-url=xxx #Interface content encryption method In the sandbox application-》Interface content encryption method alipay.content-key=xxx #Payment callback return address If you have a page, write it, if not, return to Baidu alipay.return-url = https://www.baidu.com #Payment callback public network address + interface This needs to be penetrated by ngrok to map your local project to the public network. I won’t explain much here. alipay.notify-url = xxx
3. Back-end code writing
Controller layer: The interface has a total of 7 interfaces, one of which is the Alipay callback interface. After you complete the payment, Alipay calls the interface you provided to send you the callback message, so you need this interface to be accessible on the public network, so For local environments, intranet penetration is required.
Interface 1: Payment Order
According to the product id/number selected by the front end, after passing it to the backend, call AlipayTradePagePayModel to set parameters. Finally, through AlipayTradePagePayRequest, set 1 the callback address after successful payment. I wrote Baidu 2. Which interface exposes the callback to the public network interface, and then executes pageExecute completes signature execution request
The implementation code is as follows:
impl layer code
public String createPay(Long orderId) { //1.Request AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); //2.Set data AlipayTradePagePayModel bizModel = new AlipayTradePagePayModel(); // Product Number bizModel.setOutTradeNo(orderId.toString()); //The unit is yuan bizModel.setTotalAmount(String.valueOf(0.01)); // Order title bizModel.setSubject("Test product"); //default bizModel.setProductCode("FAST_INSTANT_TRADE_PAY"); //3.Binding request.setBizModel(bizModel); //Where to return after successful payment request.setReturnUrl(returnUrl); //Result callback address request.setNotifyUrl(notifyUrl); //After the user pays, Alipay will request the returnUrl with the GET method and carry parameters such as out_trade_no, trade_no, total_amount, etc. AlipayTradePagePayResponse response = null; try { //Complete the signature and execute the request response = alipayClient.pageExecute(request); if (response.isSuccess()) { log.debug("The call was successful, the parameters are ===>{}",JSON.toJSONString(response.getBody())); return response.getBody(); } else { log.error("Call failed"); log.error(response.getMsg()); return null; } } catch (AlipayApiException e) { log.error("Call exception"); return null; } }
Payment callback interface
/** * Alipay callback * After initiating payment, the merchant performs operations such as verification and record saving * This interface address is configured according to the configuration file * @param params returned by Alipay * @return There are only two states returned to Alipay: success failure */ @PostMapping("/tradeNotify") public String tradeNotify(@RequestParam Map<String, String> params) { log.info("Payment callback is being executed"); log.info("Alipay callback parameter is ===>{}", JSON.toJSONString(params)); //Signature verification boolean signVerified = false; String result = "failure"; try { //Input parameters signVerified = AlipaySignature.rsaCheckV1(params, aliPayPublicKey, AlipayConstants.CHARSET_UTF8, AlipayConstants.SIGN_TYPE_RSA2); //Signature verification successful if (signVerified) { log.info("Signature verification successful"); // 1. The merchant needs to verify whether out_trade_no in the notification data is the order number created in the merchant system. Does the database query whether this record exists? Create orders when generating orders String out_trade_no = params.get("out_trade_no"); // Query this record based on the order number and return the order. If it does not exist, return result(failure) // 2. The merchant needs to verify whether the total_amount in the notification data is the actual amount of the order (that is, the amount when the merchant's order is created) String totalAmount = params.get("total_amount"); int totalAmountInt = new BigDecimal(totalAmount).multiply(new BigDecimal("100")).intValue(); // Take out the price in the order and compare it with this one. If different, return result(failure) // 3. Verify whether the seller_id (or sell_email) in the notification is out_trade_no. The corresponding operation method of this document needs to be consistent with the merchant pid. String sellerId = params.get("seller_id"); if (!sellerId.equals(aliPaySellerId)){ log.error("Merchant pid verification failed"); return result; } // 4. Verify whether app_id is the merchant itself String appId = params.get("app_id"); if (!appId.equals(aliPayAppId)){ log.error("appId verification failed"); return result; } // 5. Verify whether the transaction notification is TRADE_SUCCESS. Only if it is TRADE_SUCCESS will Alipay consider the buyer's payment successful. String tradeStatus = params.get("trade_status"); if (!"TRADE_SUCCESS".equals(tradeStatus)){ log.error("Payment not successful"); return result; } // Process order follow-up log.info("5 major verifications passed, start processing the callback function"); aliPayService.processOrder(params); result = "success"; return result; } else { log.error("Signature verification failed"); return result; } } catch (AlipayApiException e) { log.error("Signature verification exception"); return result; } }
impl
@Override public void processOrder(Map<String, String> params) { String out_trade_no = params.get("out_trade_no"); //Payment number in Alipay payment String trade_no = params.get("trade_no"); //Transaction type (scan code, log in, etc.) String trade_status = params.get("trade_status"); //Storage all data (json) in case of emergency String s = JSON.toJSONString(params); // Check the order status. If it has not been paid, continue to perform the following operations. Only perform the following operations if it has not been paid. //Return if payment has been made if (lock.tryLock()) { try { log.info("Idempotence verification starts"); // if (!"Not paid".equals("Calling database query")) { // return; // } //Update order status //Record payment log log.info("Idempotence verification ended"); log.info("The payment log of order {} has been added, the status has been changed to paid, the Alipay record number is {}", out_trade_no, trade_no); //The payment record of order 789454 has been added successfully, and the payment record id is 2023110122001436370501040721. } catch (Exception e) { } finally { lock.unlock(); } } }
Interface 2: Query Order
The front-end passes in the product id/number, the back-end encapsulates the data using AlipayTradeQueryModel, and uses AlipayTradeQueryRequest to initiate a request.
The implementation code is as follows:
@Override public String queryPay(String orderNo) { //ask AlipayTradeQueryRequest request=new AlipayTradeQueryRequest(); //data AlipayTradeQueryModel bizModel=new AlipayTradeQueryModel(); bizModel.setOutTradeNo(orderNo); request.setBizModel(bizModel); try{ //Complete the signature and execute the request AlipayTradeQueryResponse response=alipayClient.execute(request); if(response.isSuccess()){ log.info("query order {} successfully",orderNo); return response.getBody(); } else{ log.error("Failed to query order {}, the response data is {}.", orderNo, response.getBody()); return null; } } catch(AlipayApiException e){ log.error("query order{} exception",orderNo); return null; } }
Interface 3: Order Refund
Pass in two parameters, product id/number and refund reason
What needs to be noted is that if the refund order number (set by yourself) is passed in when encapsulating the data, then this refund order number also needs to be passed in when querying the order refund information later. If it is not set here, the refund order number will be passed in later. The number is the product id/number
The implementation code is as follows:
@Override public void refund(String orderNo, String reason) { //ask AlipayTradeRefundRequest request=new AlipayTradeRefundRequest(); //data AlipayTradeRefundModel bizModel=new AlipayTradeRefundModel(); //order number bizModel.setOutTradeNo(orderNo); //Refund amount bizModel.setRefundAmount("0.01"); //reason for return bizModel.setRefundReason(reason); request.setBizModel(bizModel); log.info("Signature input parameter===>{}",JSON.toJSONString(request)); try{ //Complete the signature and execute the request AlipayTradeRefundResponse response=alipayClient.execute(request); //Success means the refund is successful. if(response.isSuccess()){ log.info("Order {} refunded successfully", orderNo); } else{ log.error("Failed to refund order {}, error reason ===>{}", orderNo, response.getSubMsg()); throw new RuntimeException("Order refund failed"); } } catch(AlipayApiException e){ log.error("Order {} refund exception", orderNo); throw new RuntimeException("Order refund exception"); } }
Interface 4: Query refund results
The front-end inputs the order number, and the back-end performs a query. The point to note is the refund order number just mentioned. If the refund order number is not sent when making a refund, the order number will be sent here.
The implementation code is as follows:
@Override public String queryRefund(String orderNo) { AlipayTradeFastpayRefundQueryRequest request=new AlipayTradeFastpayRefundQueryRequest(); AlipayTradeFastpayRefundQueryModel bizModel=new AlipayTradeFastpayRefundQueryModel(); //order number bizModel.setOutTradeNo(orderNo); // Refund order number. If the refund order number is not sent when making a refund, then the order number will be sent when querying. bizModel.setOutRequestNo(orderNo); //The additional data you want to return (that is, the response optional data in the document) ArrayList<String> extraResponseDatas=new ArrayList<>(); extraResponseDatas.add("refund_status"); bizModel.setQueryOptions(extraResponseDatas); request.setBizModel(bizModel); try{ //Complete the signature and execute the request AlipayTradeFastpayRefundQueryResponse response=alipayClient.execute(request); if(response.isSuccess()){ log.info("Refund {} query successful", orderNo); return JSON.toJSONString(response.getBody()); } else{ log.debug("Refund{} query failed because ==>{}",orderNo,response.getSubMsg()); return null; } } catch(AlipayApiException e){ log.debug("Refund{}query exception",orderNo); return null; } }
Interface 5: Get the total bill
Get the bill url address based on the bill type and date. The parameter here is the url. Put it in the browser and download it directly.
The implementation code is as follows:
@Override public String queryBill(String billDate, String type) { //ask AlipayDataDataserviceBillDownloadurlQueryRequest request=new AlipayDataDataserviceBillDownloadurlQueryRequest(); //data AlipayDataDataserviceBillDownloadurlQueryModel bizModel=new AlipayDataDataserviceBillDownloadurlQueryModel(); bizModel.setBillType(type); bizModel.setBillDate(billDate); request.setBizModel(bizModel); try{ //Complete the signature and execute the request AlipayDataDataserviceBillDownloadurlQueryResponse response=alipayClient.execute(request); if(response.isSuccess()){ log.info("Get the bill download url successfully"); return response.getBillDownloadUrl(); } else{ log.error("Failed to obtain the bill download url, the reason is ===>{}", response.getSubMsg()); return null; } } catch(AlipayApiException e){ log.error("Exception in getting bill download url"); return null; } }
Interface 6: Cancel Order
Pass in the product id/number, encapsulate the data in the backend, and cancel after completing the signature.
The implementation code is as follows:
private void closePay(String orderNo) { log.info("Order number of the order interface, order number ===>{}",JSON.toJSONString(orderNo)); //ask AlipayTradeCloseRequest request=new AlipayTradeCloseRequest(); //data AlipayTradeCloseModel bizModel=new AlipayTradeCloseModel(); bizModel.setOutTradeNo(orderNo); request.setBizModel(bizModel); try{ //Complete the signature and execute the request AlipayTradeCloseResponse response=alipayClient.execute(request); if(response.isSuccess()){ log.info("Order {} canceled successfully", orderNo); } else{ log.info("Cancellation of order {} failed, reason ==>{}", orderNo, response.getSubMsg()); throw new RuntimeException("Failed to call the single interface"); } } catch(AlipayApiException e){ log.error("Order {} cancellation exception", orderNo); throw new RuntimeException("Close single interface exception"); } }
Interface 7: Callback Interface
When payment is initiated, Alipay will call this interface to return data, which requires verification of the signature and 5 parameters.
@PostMapping("/tradeNotify") public String tradeNotify(@RequestParam Map<String, String> params) { log.info("Payment callback is being executed"); log.info("Alipay callback parameter is ===>{}", JSON.toJSONString(params)); //Signature verification boolean signVerified = false; String result = "failure"; try { //Input parameters signVerified = AlipaySignature.rsaCheckV1(params, aliPayPublicKey, AlipayConstants.CHARSET_UTF8, AlipayConstants.SIGN_TYPE_RSA2); //Signature verification successful if (signVerified) { log.info("Signature verification successful"); // 1. The merchant needs to verify whether out_trade_no in the notification data is the order number created in the merchant system. Does the database query whether this record exists? Create orders when generating orders String out_trade_no = params.get("out_trade_no"); // Query this record based on the order number and return the order. If it does not exist, return result(failure) // 2. The merchant needs to verify whether the total_amount in the notification data is the actual amount of the order (that is, the amount when the merchant's order is created) String totalAmount = params.get("total_amount"); int totalAmountInt = new BigDecimal(totalAmount).multiply(new BigDecimal("100")).intValue(); // Take out the price in the order and compare it with this one. If different, return result(failure) // 3. Verify whether the seller_id (or sell_email) in the notification is out_trade_no. The corresponding operation method of this document needs to be consistent with the merchant pid. String sellerId = params.get("seller_id"); if (!sellerId.equals(aliPaySellerId)){ log.error("Merchant pid verification failed"); return result; } // 4. Verify whether app_id is the merchant itself String appId = params.get("app_id"); if (!appId.equals(aliPayAppId)){ log.error("appId verification failed"); return result; } // 5. Verify whether the transaction notification is TRADE_SUCCESS. Only if it is TRADE_SUCCESS will Alipay consider the buyer's payment successful. String tradeStatus = params.get("trade_status"); if (!"TRADE_SUCCESS".equals(tradeStatus)){ log.error("Payment not successful"); return result; } // Process order follow-up log.info("5 major verifications passed, start processing the callback function"); // Carry out your own business operations aliPayService.processOrder(params); result = "success"; return result; } else { log.error("Signature verification failed"); return result; } } catch (AlipayApiException e) { log.error("Signature verification exception"); return result; } }
Scheduled task: proactively query orders
In addition to the passive query of callback, we can also use scheduled tasks to regularly query the unpaid orders in the table and the status in Alipay to ensure consistency.
Regularly take out the unpaid orders in the table and query them
/** * Executed every 30s starting from the 0th second, the query creates orders that are more than 5 minutes old and have not been paid. */ @Scheduled(cron = "0/30 * * * * ?") public void orderConfirm(){ log.info("orderConfirm is executed..."); // 1. Query local unpaid records, this is written to death String orderNo = "9956851223"; // 2. Verify the order status and call the Alipay order checking interface aliPayService.checkPayStatus(orderNo); }
/** * Check Alipay order status * @param orderNo */ @Override public void checkPayStatus(String orderNo) { log.info("Verify order status based on order number======>{}",JSON.toJSONString(orderNo)); // Check the status of this Alipay order String result = this.queryPay(orderNo); // 1. If not created if (result == null){ log.info("Order not created===>{}",JSON.toJSONString(orderNo)); //Update order status return; } System.out.println("result" + result); // Get order status String alipayTradeQueryResponse1 = JSON.parseObject(result).get("alipay_trade_query_response").toString(); String tradeStatus = JSON.parseObject(alipayTradeQueryResponse1).get("trade_status").toString(); // 2. If the order status is unpaid if (AliPayTradeState.NOTYPE.getType().equals(tradeStatus)){ log.info("Order not paid ===>{}",JSON.toJSONString(orderNo)); // Call the custom order interface this.closePay(orderNo); //Update order status } // 3. If the order status is paid if (AliPayTradeState.SUCCESS.getType().equals(tradeStatus)){ log.info("Order paid===>{}",JSON.toJSONString(orderNo)); //Update merchant order status //Record payment log } // 4. If the order status is closed if (AliPayTradeState.CLOSED.getType().equals(tradeStatus)){ log.info("Order has been closed===>{}",JSON.toJSONString(orderNo)); //Update merchant order status //Record payment log } }
public enum AliPayTradeState { /** * payment successful */ SUCCESS("TRADE_SUCCESS"), /** * unpaid */ NOTYPE("WAIT_BUYER_PAY"), /** * closed */ CLOSED("TRADE_CLOSED"); private String type; }
4. Front-end code writing
<!doctype html> <html> <head> <meta charset='utf-8'> <title>Login</title> <script src="js/jquery-1.8.3.min.js"></script> </head> <script> function zhifu(){ $.ajax({ url:"http://localhost:8080/pay/createPay/995685122334", type:"post", success: function(data) { // console.log(data) document.write(data.body) } }) } </script> <script> function guanbi(){ $.ajax({ url:"http://localhost:8080/pay/cancelPay/995685122334", type:"post", success: function(data) { alert(data.msg) } }) } </script> <script> function tuikuan(){ $.ajax({ url:"http://localhost:8080/pay/refunds/995685122334/I don’t want it anymore", type:"post", success: function(data) { alert(data.msg) } }) } </script> <script> function chaxuntuikuan(){ $.ajax({ url:"http://localhost:8080/pay/queryRefund/995685122334", type:"post", success: function(data) { console.log(data) } }) } </script> <script> function chaxun(){ $.ajax({ url:"http://localhost:8080/pay/query/995685122334", type:"get", success: function(data) { console.log(data) } }) } </script> <script> function duizhangliushui(){ $.ajax({ url:"http://localhost:8080/pay/downloadurl/query/2023-10-31/trade", type:"get", success: function(data) { console.log(data.body) // alert(data.body) } }) } </script> <body> <div style="margin: 0 auto; width: 600px; height: 600px; text-align: center; margin-top: 300px; display: flex;"> <div style="width:50px; height:50px;left: auto;"> <button onclick="zhifu()">Pay</button> </div> <div style="width:100px; height:50px;left: auto;"> <button onclick="chaxun()">Query order</button> </div> <div style="width:50px; height: 50px;left: auto;"> <button onclick="tuikuan()">Refund</button> </div> <div style="width:100px; height: 50px;left: auto;"> <button onclick="guanbi()">Cancel order</button> </div> <div style="width:100px; height: 50px;left: auto;"> <button onclick="chaxuntuikuan()">Inquiry for refund</button> </div> <div style="width:100px; height: 50px;left: auto;"> <button onclick="duizhangliushui()">Reconciliation flow</button> </div> </div> </body> </html>
5.Verification
Payment Order
Click the pay button
Mobile sandbox version of Alipay scan QR code to pay.
Then the front-end page jumps to Baidu, because the callback is set to jump to Baidu
query order
Order Refund
Enquiry for refund
Cancel order
Place an order again, but do not pay after scanning the QR code