Alipay payment using java (sandbox environment)

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