SpringBoot integrates Alipay sandbox payment

introduction

Since there is a payment function in the graduation project, I thought of integrating Alipay's sandbox payment, but I haven't touched related functions before.

After consulting relevant information on Baidu, the relevant integrated information is sorted out as follows

Process overview

This process is the integrated Alipay sandbox computer website payment process

image-20230217135026494

The specific process is as follows:

  1. The merchant system calls alipay.trade.page.pay (unified page interface for order receipt and payment) to initiate a payment request to Alipay, Alipay verifies the merchant request parameters, and then redirects to the user login page.
  2. After the user confirms the payment, Alipay requests returnUrl through get (merchant input parameters are passed in), and returns the synchronous return parameters.
  3. After the transaction is successful, Alipay requests notifyUrl through post (entered by the merchant), and returns the asynchronous notification parameters.
  4. If the merchant system does not receive the asynchronous notification due to network reasons, the merchant can call alipay.trade.query (unified offline transaction query interface for billing) to query the transaction and payment information (the merchant can also directly call the query interface, no need rely on asynchronous notifications).

Note :

  • Due to the unreliability of synchronous return, the payment result must be subject to the asynchronous notification or query interface return, and cannot rely on synchronous jump.
  • After the merchant system receives the asynchronous notification, it must verify the signature (verify the sign parameter in the notification) to ensure that the payment notification is sent by Alipay. For detailed signature verification rules, please refer to Asynchronous Notification Signature Verification .
  • After receiving the asynchronous notification and passing the signature verification, please be sure to check whether the app_id, out_trade_no, total_amount and other parameter values ​​in the notification are consistent with those in the request, and perform subsequent business processing according to trade_status.
  • On the Alipay side, partnerId and out_trade_no only correspond to one document, and the merchant side guarantees that out_trade_no cannot be repeated for different payments; if repeated, Alipay will link to the original document, and if the basic information is consistent, the original document will prevail for payment.

Preparation

Alipay Development Platform Center

Alipay Open Center: https://openhome.alipay.com/

The preliminary preparation is to create a sandbox application in the Alipay Open Center

  1. Visit the Alipay Open Center, log in to the account and click the console

    image-20230217141308803

  2. After entering the console, slide down the page and you will see沙箱

    image-20230217141629152

  3. After clicking into the sandbox, the following interface appears, and you can see the appid

    image-20230217141727133

  4. Then we need to set the interface signing method, it is recommended to select 自定义密钥the公钥模式

    When you click Settings and View, related document links will appear in the interface

    https://opendocs.alipay.com/common/02kipl

    image-20230217161929751

  5. Set application gateway address

    It is used to receive Alipay sandbox asynchronous notification messages (such as From ant messages, etc.), and it needs to pass in the address of a webpage accessible by the http(s) public network. Optional, if not set, the corresponding asynchronous notification message cannot be received.

    If you want to receive the information after the user's payment is completed, then this url must be set

    **Note:** The url address set must be accessible from the external network. If the test is performed on this machine, then the relevant port of the machine needs to be 内网穿透processed, otherwise Alipay cannot access the url

    For example, the asynchronous notification url after my local port 8888 is 内网穿透processed is as follows:

    image-20230217164018980

  6. Set authorization callback address

    The external accesses the gateway to distribute the request to the corresponding sandbox internal business system

    The authorization callback address is used for third-party login. If you don’t need to use the Alipay user login function, you don’t need to set it

  7. Swipe down to see the documentation for developing related products

    image-20230217162807413

  8. Then click on the sandbox account on the left, you can see the merchant account and seller account provided by Alipay sandbox

    image-20230217162918117

  9. Then we download it in the sandbox tool 支付宝客户端沙箱版(Android only)

    Then log in to the buyer's account in the Alipay sandbox version, and then test the website payment.

Intranet penetration

https://natapp.cn/

I use intranet penetration NATAPP, the main function is to allow the local port to be accessed by the external network through a proxy url

**The role here is: **Use the intranet penetration tool to proxy the local back-end port, and then Alipay's asynchronous notification can be sent to the corresponding interface, otherwise Alipay will not be able to access our local interface

image-20230219004957114

The following is the 8888 port of my computer for intranet penetration, so I can use it http://4get9t.natappfree.ccto replace the access of this port

image-20230217164340086

code example

This code example is a computer website payment example

If you have any questions, you can refer to the official document: https://opendocs.alipay.com/open/repo-0038oa

integrated payment

  1. Introduce dependencies

    <dependency>
        <groupId>com.alipay.sdk</groupId>
        <artifactId>alipay-sdk-java</artifactId>
        <version>4.34.0.ALL</version>
    </dependency>
    
  2. Write application.yml

    • gatewayUrl: Alipay gateway, the sandbox environment gateway is inconsistent with the production environment gateway , the following example is for the sandbox environment
    • appId: After the application is created on the Alipay open platform, each application has its own appid
    • appPrivateKey: application private key, this needs to fill in your own
    • format: message format, this does not need to be changed
    • charset: string encoding format, this does not need to be changed
    • signType: signature method, this generally does not need to be changed
    • alipayPublicKey: Alipay public key, you need to fill in your own
    • returnUrl: The path of the synchronous jump after the payment is completed. This needs to be filled in by yourself. I filled in the path of my front-end page
    • notifyUrl: Asynchronous notification url, used to process the business after the payment is completed. Note that this path needs to be tested locally 内网穿透, otherwise Alipay cannot access this path
    alipay:
      # 支付宝网关
      gatewayUrl: https://openapi.alipaydev.com/gateway.do
      # appid
      appId: 
      # 应用私钥
      appPrivateKey: 
      # 报文格式
      format: JSON
      # 字符串编码格式
      charset: utf-8
      # 签名方式
      signType: RSA2
      # 支付宝公钥
      alipayPublicKey: 
      # 支付完成后同步跳转的路径(一般写支付完成后想要跳转的路径)
      returnUrl: http://localhost:9999/#/personal/illegalInfo
      # 异步通知url
      notifyUrl: http://4get9t.natappfree.cc/aliPay/notify
    
  3. Create new AliPayProperties under the config package

    package com.lzj.config;
    
    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    
    /**
     * <p>
     *
     * </p>
     *
     * @author:雷子杰
     * @date:2023/2/15
     */
    @Data
    @Component
    @ConfigurationProperties(prefix = "alipay")
    public class AliPayProperties {
          
          
        /**
         * 支付宝网关
         */
        private String gatewayUrl;
        /**
         * 支付宝应用的appid
         */
        private String appId;
        /**
         * 应用私钥
         */
        private String appPrivateKey;
        /**
         * 报文格式
         */
        private String format;
        /**
         * 字符串编码格式
         */
        private String charset;
        /**
         * 签名方式
         */
        private String signType;
        /**
         * 支付宝公钥
         */
        private String alipayPublicKey;
        /**
         * 支付完成后同步跳转的路径(一般写支付完成后想要跳转的路径)
         */
        private String returnUrl;
        /**
         * 异步通知url
         */
        private String notifyUrl;
    
    
    }
    
  4. Create a new AlipayOrder under the entity package

    package com.lzj.entity;
    
    import io.swagger.annotations.ApiModel;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import lombok.experimental.Accessors;
    
    /**
     * <p>
     *
     * </p>
     *
     * @author:雷子杰
     * @date:2023/2/15
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Accessors(chain = true)
    @ApiModel("支付订单类")
    public class AlipayOrder {
          
          
    
        /**
         * 用户订单号,必填
         */
        private String out_trade_no;
    
        /**
         * 订单名称,必填
         */
        private String subject;
    
        /**
         * 订单总金额,必填
         */
        private String total_amount;
    
        /**
         * 销售产品码,必填,注:目前电脑支付场景下仅支持FAST_INSTANT_TRADE_PAY,所以可以写死
         */
        private final String product_code = "FAST_INSTANT_TRADE_PAY";
    
    }
    
  5. Create a new AliPayService under the service package

    package com.lzj.service;
    
    import com.alipay.api.AlipayApiException;
    import com.alipay.api.AlipayResponse;
    import com.alipay.api.response.AlipayTradePagePayResponse;
    import com.lzj.entity.AlipayOrder;
    
    import javax.servlet.http.HttpServletRequest;
    
    /**
     * <p>
     *
     * </p>
     *
     * @author:雷子杰
     * @date:2023/2/15
     */
    public interface AliPayService {
          
          
    
        AlipayResponse pay(AlipayOrder order) throws AlipayApiException;
    
        /**
         * 异步通知签名验证
         * @param request
         * @return
         */
        boolean notifySignVerified(HttpServletRequest request);
    }
    
    
  6. New AliPayServiceImpl

    package com.lzj.service.impl;
    
    import com.alibaba.fastjson.JSON;
    import com.alipay.api.*;
    import com.alipay.api.internal.util.AlipaySignature;
    import com.alipay.api.request.AlipayOpenPublicTemplateMessageIndustryModifyRequest;
    import com.alipay.api.request.AlipayTradePagePayRequest;
    import com.alipay.api.response.AlipayOpenPublicTemplateMessageIndustryModifyResponse;
    import com.alipay.api.response.AlipayTradePagePayResponse;
    import com.lzj.config.AliPayProperties;
    import com.lzj.entity.AlipayOrder;
    import com.lzj.service.AliPayService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.Map;
    
    /**
     * <p>
     *
     * </p>
     *
     * @author:雷子杰
     * @date:2023/2/15
     */
    @Service("aliPayService")
    @Transactional
    @Slf4j
    public class AliPayServiceImpl implements AliPayService {
          
          
    
        @Autowired
        private AliPayProperties aliPayProperties;
    
    
        @Override
        public AlipayResponse pay(AlipayOrder order) throws AlipayApiException {
          
          
            //初始化AlipayConfig
            AlipayConfig alipayConfig = new AlipayConfig();
            alipayConfig.setServerUrl(aliPayProperties.getGatewayUrl());
            alipayConfig.setAppId(aliPayProperties.getAppId());
            alipayConfig.setSignType(aliPayProperties.getSignType());
            alipayConfig.setAlipayPublicKey(aliPayProperties.getAlipayPublicKey());
            alipayConfig.setPrivateKey(aliPayProperties.getAppPrivateKey());
            alipayConfig.setFormat(aliPayProperties.getFormat());
            alipayConfig.setCharset(aliPayProperties.getCharset());
    
            //实例化客户端
            AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig);
    
            //设置请求参数
            //实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称
            AlipayTradePagePayRequest  request = new AlipayTradePagePayRequest ();
            //设置同步返回url
            request.setReturnUrl(aliPayProperties.getReturnUrl());
            //设置异步通知url
            request.setNotifyUrl(aliPayProperties.getNotifyUrl());
            log.debug("JSON.toJSONString(order):" + JSON.toJSONString(order));
            //转换为json字符串后传入
            request.setBizContent(JSON.toJSONString(order));
    
            AlipayTradePagePayResponse  response = alipayClient.pageExecute(request);
    
            //调用成功,则处理业务逻辑
            return response;
        }
    
        @Override
        public boolean notifySignVerified(HttpServletRequest request) {
          
          
    
            log.debug("进入了异步通知");
            Map<String, String> params = new HashMap<String, String>();
            Map<String, String[]> requestParams = request.getParameterMap();
    
            for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
          
          
                String name = (String) iter.next();
                String[] values = (String[]) requestParams.get(name);
                String valueStr = "";
                for (int i = 0; i < values.length; i++) {
          
          
                    valueStr = (i == values.length - 1) ? valueStr + values[i]
                            : valueStr + values[i] + ",";
                }
                params.put(name, valueStr);
            }
    
            //调用SDK验证签名
            System.out.println(params.toString());
            System.out.println(aliPayProperties.toString());
            boolean signVerified = false;
            try {
          
          
                signVerified = AlipaySignature.rsaCheckV1(params, aliPayProperties.getAlipayPublicKey(), aliPayProperties.getCharset(), aliPayProperties.getSignType());
            } catch (AlipayApiException e) {
          
          
                e.printStackTrace();
            }
            System.out.println("SDK验证签名结果1:" + signVerified);
    
            return signVerified;
        }
    }
    
  7. New AliPayController

    /notifyThe interface is used to receive the asynchronous notification returned after Alipay payment is successful

    /**
     * <p>
     *
     * </p>
     *
     * @author:雷子杰
     * @date:2023/2/16
     */
    @RestController
    @RequestMapping("aliPay")
    @Slf4j
    @Api(tags = "支付宝支付模块")
    public class AliPayController {
          
          
        @Autowired
        private AliPayService aliPayService;
        @Autowired
        private OrderService orderService;
        @Autowired
        private IllegalInfoService illegalInfoService;
        @Autowired
        private RedisClient redisClient;
    
    
    
        @GetMapping("/return")
        public JsonResult<Void> getReturnMsg() {
          
          
    
            return JsonResult.ok();
        }
    
        @PostMapping("/notify")
        public JsonResult<Void> getNotifyMsg(HttpServletRequest request) throws UnsupportedEncodingException {
          
          
    
            boolean signVerified = aliPayService.notifySignVerified(request);
    
            if (signVerified) {
          
          
                // 商户订单号
                String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"), "UTF-8");
    
                // 支付宝交易号
                String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"), "UTF-8");
    
                // 交易状态
                String trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"), "UTF-8");
    
                if (trade_status.equals("TRADE_FINISHED")) {
          
          
                    // 判断该笔订单是否在商户网站中已经做过处理
                    // 如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
                    // 如果有做过处理,不执行商户的业务程序
    
                    log.debug("成功:TRADE_FINISHED");
                    // 注意:
                    // 退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
                    return JsonResult.ok();
                } else if (trade_status.equals("TRADE_SUCCESS")) {
          
          
                    // 判断该笔订单是否在商户网站中已经做过处理
                    // 如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
                    // 如果有做过处理,不执行商户的业务程序
                    log.debug("成功:TRADE_SUCCESS");
    
                    // 注意:
                    // 付款完成后,支付宝系统发送该交易状态通知
                    return JsonResult.ok();
                } else {
          
          //验证失败
    
                    // 调试用,写文本函数记录程序运行情况是否正常
                    return JsonResult.fail(StatusCodeEnum.ALI_PAY_ERROR.getCode(), StatusCodeEnum.ALI_PAY_ERROR.getDesc());
                }
    
            }
    
            return JsonResult.ok();
        }
    }
    

business call

  1. Front-end page request related interface

    image-20230217195908324

  2. Call pay()the method of AliPayService in the interface to return alipayResponse, isSuccess()judge whether the request is successful according to the method of alipayResponse, then process the business logic, and then return getBody()the method of alipayResponse

    Example:

    image-20230217194639564

  3. The parameter returned by the method of the interface returning alipayResponse getBody()(is a form string)

  4. After receiving the returned string, the front-end page operates on it, and the newly opened window jumps

    **Note:** It is normal that the original page will be blank after the new window jumps

    payIllegalInfoMoney(row){
          
          
      payIllegalInfoMoney({
          
          
        illegalInfoId: row.illegalInfoId,
        illegalActCode: row.illegalActCode,
        amount: row.amount,
        userId: this.$store.getters.userInfo.userId
      }).then(res =>{
          
          
        //下面的操作不可删减,删减了后可能因为缓存导致第二次无法跳转窗口的问题
        const divForm = document.getElementsByTagName("div");
        if (divForm.length) {
          
          
          document.body.removeChild(divForm[0]);
        }
        const div = document.createElement("div");
        div.innerHTML = res.data; // data就是接口返回的form 表单字符串
        document.body.appendChild(div);
        document.forms[0].setAttribute("target", "_blank"); // 新开窗口跳转
        document.forms[0].submit();
    
      })
    }
    
  5. The interface after the jump is as follows

    **Note:** The Alipay sandbox version may be under maintenance from 12:00 noon on Sunday to 12:00 noon on Monday. If you visit at this time, the page may report 502 exceptions. If this problem occurs, we can wait until the next day for testing up

    image-20230217200410870

  6. Then use the sandbox version of Alipay to scan the code or log in directly to the website to pay

  7. After the scan code payment is completed, if you have specified it in the backend returnUrl, the page will jump to this link, and after the scan code payment is completed, Alipay will send a related request to the asynchronous notification url you set

  8. In the interface for receiving Alipay asynchronous notification, we can perform a business process after the payment is completed. The example is as follows

    image-20230217200711014

Summarize

It is not difficult to just make a simple computer website Alipay sandbox payment. Basically, it can be realized by adjusting the api.

I hope that I can encounter relevant knowledge in my work in the future so that I can learn in practice

Guess you like

Origin blog.csdn.net/qq_49137582/article/details/129134691