The integration steps

1. Preparation

  1. Enter alipay sandbox environment to generate RSA2(SHA256) key as prompted

  2. Get alipay – trade – sdkjar package

    Download pay-in-person Demo from Alipay open platform

    Obtain the jar package Alipay -trade- SDK-20161215.jar from the \TradePayDemo\WebRoot\ web-INF \lib path

  3. Package jars into the Maven repository

    Use the following command to package in the directory where the JAR package is located

    mvn install:install-file -DgroupId=com.alipay -DartifactId=alipay-trade-sdk -Dversion=20161215 -Dpackaging=jar -Dfile=alipay-trade-sdk-20161215.jar
    Copy the code

2. Introduce dependencies

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<! -- https://mvnrepository.com/artifact/com.alipay.sdk/alipay-sdk-java -->
<dependency>
    <groupId>com.alipay.sdk</groupId>
    <artifactId>alipay-sdk-java</artifactId>
    <version>4.9.106. ALL</version>
</dependency>
<! -- Local dependencies -->
<dependency>
    <groupId>com.alipay</groupId>
    <artifactId>alipay-trade-sdk</artifactId>
    <version>20161215</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<! -- Realize qr code scanning -->
<! -- https://mvnrepository.com/artifact/com.google.zxing/core -->
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.4.0</version>
</dependency>
<! -- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.6</version>
</dependency>

<! -- https://mvnrepository.com/artifact/commons-lang/commons-lang -->
<dependency>
    <groupId>commons-lang</groupId>
    <artifactId>commons-lang</artifactId>
    <version>2.6</version>
</dependency>

<! -- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.12</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
Copy the code

3. The configuration application. Yml

server:
  port: 8081

# sandbox configuration
alipay:
  gatewayUrl: https://openapi.alipaydev.com/gateway.do # Alipay Gateway
  appid: 2016101100659869 The APPID of the sandbox
  privateKey: * * * Fill in the application private key generated by alipay Open Platform Development Assistant
  alipayPublicKey: * * * #RSA2(SHA256) key in the alipay public key
  returnUrl: http://dqidj6.natappfree.cc/alipay/return The sync callback address can be accessed using natApp Intranet
  notifyUrl: http://dqidj6.natappfree.cc/alipay/notify Async callback address

spring:
  thymeleaf:
    prefix: classpath:/templates/ Set the path where the thymeleaf page will be stored
    suffix: .html Set the suffix of the thymeleaf page
    encoding: UTF-8 # Set the thymeleaf page encoding
    cache: false Thymeleaf cache development does not require a restart
    servlet:
      content-type: text/html
Copy the code

4. Read the configuration file

/** * Parameter configuration of Alipay payment **@author manaphy
 */
@Data
@Slf4j
@ConfigurationProperties(prefix = "alipay")
public class AlipayProperties {

    /** * alipay gatewayUrl */
    private String gatewayUrl;
    /** * Merchant application id */
    private String appid;
    /** * RSA private key, used to sign merchant request packets */
    private String privateKey;
    /** * Alipay RSA public key, used to check alipay response */
    private String alipayPublicKey;
    /**
     * 签名类型
     */
    private String signType = "RSA2";

    /** * format */
    private String format = "json";
    /** ** encoding */
    private String charset = "UTF-8";

    /** ** synchronize address */
    private String returnUrl;

    /** * asynchronous address */
    private String notifyUrl;

    /** * Maximum number of queries */
    private static int maxQueryRetry = 5;
    /** * Query interval (milliseconds) */
    private static long queryDuration = 5000;
    /** * Maximum number of revocations */
    private static int maxCancelRetry = 3;
    /**
     * 撤销间隔(毫秒)
     */
    private static long cancelDuration = 3000;

    private AlipayProperties(a) {}/** * PostConstruct is a Spring framework annotation. Adding this annotation to a method executes the method at project startup, or when the Spring container is initialized. * /
    @PostConstruct
    public void init(a) {
        log.info(description());
    }

    public String description(a) {

        return "\nConfigs{" + Alipay Gateway: + gatewayUrl + "\n" +
                ", appid: " + appid + "\n" +
                ", merchant RSA private key: + getKeyDescription(privateKey) + "\n" +
                ", Alipay RSA public key: + getKeyDescription(alipayPublicKey) + "\n" +
                Signature type: + signType + "\n" +
                ", query retry times:" + maxQueryRetry + "\n" +
                ", query interval (ms):" + queryDuration + "\n" +
                ", number of undo attempts:" + maxCancelRetry + "\n" +
                ", undo retry interval (ms):" + cancelDuration + "\n" +
                "}";
    }

    private String getKeyDescription(String key) {
        int showLength = 6;
        if(! StringUtils.isEmpty(key) && key.length() > showLength) {return key.substring(0, showLength) + "* * * * * *" +
                    key.substring(key.length() - showLength);
        }
        return null; }}Copy the code

5. Configure Alipay

/** * Alipay configuration * <p> * Two Alipay clients, users can use either one * Alipay -trade-SDK is the package of Alipay -SDK-Java, it is suggested to use Alipay -trade-sdk. **@author Manaphy
 */
@Configuration
@EnableConfigurationProperties(AlipayProperties.class)
public class AlipayConfig {

    @Resource
    private AlipayProperties properties;

    /** * Alipay client * Alipay - sdK-java **@return {@link AlipayClient}
     */
    @Bean
    public AlipayClient alipayClient(a) {
        return new DefaultAlipayClient(properties.getGatewayUrl(),
                properties.getAppid(),
                properties.getPrivateKey(),
                properties.getFormat(),
                properties.getCharset(),
                properties.getAlipayPublicKey(),
                properties.getSignType());
    }

    /** * Alipay transaction service * Alipay -trade- SDK **@return {@link AlipayTradeService}
     */
    @Bean
    public AlipayTradeService alipayTradeService(a) {
        return newAlipayTradeServiceImpl.ClientBuilder() .setGatewayUrl(properties.getGatewayUrl()) .setAppid(properties.getAppid()) .setPrivateKey(properties.getPrivateKey()) .setAlipayPublicKey(properties.getAlipayPublicKey()) .setSignType(properties.getSignType()) .build(); }}Copy the code

6. Payment tools

Generate qr code picture for face to face payment

/** ** Payment tool **@author Manaphy
 */
@Slf4j
public class PayUtil {

    /** * generate two image objects ** from the URL@paramCodeUrl Indicates the code of the URL@return {@link BufferedImage}
     * @throwsWriterException Write exception */
    public static BufferedImage getQrCodeImage(String codeUrl) throws WriterException {
        Map<EncodeHintType, Object> hints = new Hashtable(2);
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
        hints.put(EncodeHintType.CHARACTER_SET, "UTF8");
        int width = 256;
        int height = 256;
        BitMatrix bitMatrix = (new MultiFormatWriter()).encode(codeUrl, BarcodeFormat.QR_CODE, width, height, hints);
        BufferedImage image = new BufferedImage(width, height, 1);
        for (int x = 0; x < width; ++x) {
            for (int y = 0; y < width; ++y) {
                image.setRGB(x, y, bitMatrix.get(x, y) ? -16777216 : -1); }}returnimage; }}Copy the code

7. Alipay universal interface

Includes universal synchronous notification asynchronous notification Order query refund refund query close transaction query bill

/** * Alipay universal interface **@author Manaphy
 */
@Slf4j
@Controller
@RequestMapping("/alipay")
@SuppressWarnings("unused")
public class AlipayController {

    @Resource
    private AlipayProperties aliPayProperties;

    @Resource
    private AlipayTradeService alipayTradeService;

    @Resource
    private AlipayClient alipayClient;

    @Resource
    private AlipayController alipayController;


    /** ** Alipay synchronous notification **@paramRequest request *@paramThe response response * /
    @RequestMapping("/return")
    public String callback(HttpServletRequest request, HttpServletResponse response) {
        log.info("------ Enter Alipay synchronous notification ------");
        boolean verifyResult = alipayController.rsaCheckV1(request);
        if (verifyResult) {
            Enumeration<String> enu = request.getParameterNames();
            while (enu.hasMoreElements()) {
                String paraName = enu.nextElement();
                System.out.println(paraName + ":" + request.getParameter(paraName));
            }
            return "success";
        }
        return "fail";
    }
    

    /** * pay asynchronous notification * <p> * After receiving and verifying the asynchronous notification, be sure to check the contents of the notification, including whether the app_id, out_trade_NO, total_amount in the notification are consistent with the request. * And perform subsequent business processing according to trade_status. * <p> * https://docs.open.alipay.com/194/103296 * *@paramRequest request *@return {@link String}
     */
    @RequestMapping("/notify")
    public String notify(HttpServletRequest request) {
        log.info("------ Enter Alipay asynchronous notification ------");
        Map<String, String[]> parameterMap = request.getParameterMap();
        StringBuilder notifyBuild = new StringBuilder();
        parameterMap.forEach((key, value) -> notifyBuild.append(key).append("=").append(value[0]).append("\n"));
        log.info(notifyBuild.toString());
        // Check to prevent hackers from tampering with parameters
        boolean flag = this.rsaCheckV1(request);
        if (flag) {
            * * Merchant needs to verify whether out_trade_NO in the notification data is the order number created in the merchant system, * and determine whether total_amount is indeed the actual amount of the order (i.e., the amount at the time when the merchant order was created), * Also check whether the seller_id (or seller_email) in the notification is the corresponding operator of out_trade_no (sometimes a merchant may have multiple seller_id/seller_email). * * If any of the preceding verification fails, it indicates that this notification is abnormal and must be ignored. * After passing the above verification, the merchant must correctly conduct different business processing according to different types of business notices of Alipay, and filter repeated notification result data. * In alipay's business notification, only when the transaction notification status is TRADE_SUCCESS or TRADE_FINISHED, Alipay will consider the buyer's payment as successful. * /

            // Transaction status
            String tradeStatus = new String(request.getParameter("trade_status").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
            // Merchant order number
            String outTradeNo = new String(request.getParameter("out_trade_no").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
            // Alipay transaction number
            String tradeNo = new String(request.getParameter("trade_no").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
            // Amount of payment
            String totalAmount = new String(request.getParameter("total_amount").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
            TRADE_FINISHED(indicates that the trade has successfully ended and no further action can be taken on the trade);
            TRADE_SUCCESS(indicates that the trade has been successfully ended and subsequent operations can be performed on the trade, such as distribution, refund, etc.);
            if ("TRADE_FINISHED".equals(tradeStatus)) {
                // Determine whether the order has already been processed on the merchant's website
                // If the order has not been processed, the details of the order can be found in the order system of the merchant website according to the order number (outTradeNo).
                // And determine whether total_amount is indeed the actual amount of the order (i.e., the amount at the time the merchant order was created) and execute the merchant's business procedure
                // Make sure that the total_fee and seller_id obtained at the request are the same as the total_fee and seller_id obtained at the notification
                // If any processing has been done, the merchant's business procedures are not executed

                / / note:
                // If you have signed a refundable agreement, the alipay system will send the transaction status notification after the refund date exceeds the refundable period (e.g., three months refundable)
                // If no refundable agreement is signed, the Alipay system will send the transaction status notification after the payment is completed.
            } else if ("TRADE_SUCCESS".equals(tradeStatus)) {
                // Determine whether the order has already been processed on the merchant's website
                // If the order has not been processed, the details of the order can be found in the order system of the merchant website according to the order number (outTradeNo).
                // And determine whether total_amount is indeed the actual amount of the order (i.e., the amount at the time the merchant order was created) and execute the merchant's business procedure
                // Make sure that the total_fee and seller_id obtained at the request are the same as the total_fee and seller_id obtained at the notification
                // If any processing has been done, the merchant's business procedures are not executed
                / / note:
                // If you have signed a refundable agreement, the Alipay system will send the transaction status notification after the payment is completed.
            }
            return "success";
        }
        return "fail";
    }

    /** * order query (mainly used to query the payment status of the order) **@paramOrderNo Merchant order number *@return {@link String}
     */
    @GetMapping("/query")
    @ResponseBody
    public String query(String orderNo) {
        AlipayTradeQueryRequestBuilder builder = new AlipayTradeQueryRequestBuilder().setOutTradeNo(orderNo);
        AlipayF2FQueryResult result = alipayTradeService.queryTradeResult(builder);
        switch (result.getTradeStatus()) {
            case SUCCESS:
                log.info("Query returned this order payment success!!");
                AlipayTradeQueryResponse resp = result.getResponse();
                log.info(resp.getTradeStatus());
                break;
            case FAILED:
                log.error("Query returns payment failure for this order!!");
                break;
            case UNKNOWN:
                log.error("System exception, order payment status unknown!!");
                break;
            default:
                log.error("Unsupported transaction status, transaction return exception!!");
                break;
        }
        return result.getResponse().getBody();
    }

    /** ** Refund **@paramOrderNo Merchant order number *@return {@link String}
     */
    @PostMapping("/refund")
    @ResponseBody
    public String refund(String orderNo, String amount) throws AlipayApiException {
        AlipayTradeRefundRequest alipayRequest = new AlipayTradeRefundRequest();
        AlipayTradeRefundModel model = new AlipayTradeRefundModel();
        // Merchant order number
        model.setOutTradeNo(orderNo);
        // Amount of refund
        model.setRefundAmount(amount);
        // Reason for refund
        model.setRefundReason("Return without reason");
        // Refund order Number (the same order can be divided into multiple partial refunds, which will be transmitted when divided into multiple times)
        //model.setOutRequestNo(UUID.randomUUID().toString());

        alipayRequest.setBizModel(model);
        AlipayTradeRefundResponse alipayResponse = alipayClient.execute(alipayRequest);
        return alipayResponse.getBody();
    }

    /** ** Refund enquiry **@paramOrderNo Merchant order number *@paramRefundOrderNo The refund request number passed in when the refund request interface is requested. If it is not passed in when the refund request is made, this value is the external order number * when the transaction was created@return {@link String}
     * @throwsAlipayApiException AlipayApiException */
    @GetMapping("/refundQuery")
    @ResponseBody
    public String refundQuery(String orderNo, String refundOrderNo) throws AlipayApiException {
        AlipayTradeFastpayRefundQueryRequest alipayRequest = new AlipayTradeFastpayRefundQueryRequest();
        AlipayTradeFastpayRefundQueryModel model = new AlipayTradeFastpayRefundQueryModel();
        model.setOutTradeNo(orderNo);
        model.setOutRequestNo(refundOrderNo);
        alipayRequest.setBizModel(model);

        AlipayTradeFastpayRefundQueryResponse alipayResponse = alipayClient.execute(alipayRequest);
        return alipayResponse.getBody();
    }

    /** * close the transaction **@paramOrderNo Merchant order number *@return {@link String}
     * @throwsAlipayApiException AlipayApiException */
    @PostMapping("/close")
    @ResponseBody
    public String close(String orderNo) throws AlipayApiException {
        AlipayTradeCloseRequest alipayRequest = new AlipayTradeCloseRequest();
        AlipayTradeCloseModel model = new AlipayTradeCloseModel();
        model.setOutTradeNo(orderNo);
        alipayRequest.setBizModel(model);
        AlipayTradeCloseResponse alipayResponse = alipayClient.execute(alipayRequest);
        return alipayResponse.getBody();
    }


    /** * Query the bill * billDate: time of the bill: The format of the daily bill is YYYY-MM-DD and the format of the monthly bill is YYYY-MM. * query statements download address: https://docs.open.alipay.com/api_15/alipay.data.dataservice.bill.downloadurl.query/ * *@paramBillDate Date of the bill */
    @GetMapping("/bill")
    @ResponseBody
    public void queryBill(String billDate) {
        // 1. Query the statement download address
        AlipayDataDataserviceBillDownloadurlQueryRequest request = new AlipayDataDataserviceBillDownloadurlQueryRequest();
        AlipayDataDataserviceBillDownloadurlQueryModel model = new AlipayDataDataserviceBillDownloadurlQueryModel();
        model.setBillType("trade");
        model.setBillDate(billDate);
        request.setBizModel(model);
        try {
            AlipayDataDataserviceBillDownloadurlQueryResponse response = alipayClient.execute(request);
            if (response.isSuccess()) {
                String billDownloadUrl = response.getBillDownloadUrl();
                System.out.println(billDownloadUrl);

                // 2. Download the statement
                List<String> orderList = this.downloadBill(billDownloadUrl);
                System.out.println(orderList);
                // 3. First compare whether the transaction total/refund total/paid-in amount of Alipay is consistent with the data in our own database. If the inconsistency proves to be abnormal, then specifically find out which orders are abnormal
                // Find the records of alipay payment success but their payment failure and the abnormal orders of Alipay payment failure but their payment success record to the database
            } else {
                / / failString code = response.getCode(); String msg = response.getMsg(); String subCode = response.getSubCode(); String subMsg = response.getSubMsg(); }}catch(AlipayApiException | IOException e) { e.printStackTrace(); }}/** * download a [account_date.csv. zip] file * account_date_service details: Alipay service details * Account_date_service details (summary) : Alipay service details * <p> * Note: If the data volume is large, this method may take longer to execute * *@paramBillDownLoadUrl billDownLoadUrl *@return {@link List<String>}
     * @throws IOException IOException
     */
    private List<String> downloadBill(String billDownLoadUrl) throws IOException {
        String ordersStr = "";
        CloseableHttpClient httpClient = HttpClients.createDefault();
        RequestConfig config = RequestConfig.custom()
                .setConnectTimeout(60000)
                .setConnectionRequestTimeout(60000)
                .setSocketTimeout(60000)
                .build();
        HttpGet httpRequest = new HttpGet(billDownLoadUrl);
        httpRequest.setConfig(config);
        CloseableHttpResponse response = null;
        byte[] data;
        try {
            response = httpClient.execute(httpRequest);
            HttpEntity entity = response.getEntity();
            data = EntityUtils.toByteArray(entity);
        } finally {
            assertresponse ! =null;
            response.close();
            httpClient.close();
        }
        try (ZipInputStream zipInputStream = new ZipInputStream(new ByteArrayInputStream(data), Charset.forName("GBK"))) {
            ZipEntry zipEntry;
            while((zipEntry = zipInputStream.getNextEntry()) ! =null) {
                try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
                    String name = zipEntry.getName();
                    // Just do not summarize the details
                    if (name.contains("Summary")) {
                        continue;
                    }
                    byte[] byteBuff = new byte[4096];
                    int bytesRead;
                    while((bytesRead = zipInputStream.read(byteBuff)) ! = -1) {
                        byteArrayOutputStream.write(byteBuff, 0, bytesRead);
                    }
                    ordersStr = byteArrayOutputStream.toString("GBK");
                } finally{ zipInputStream.closeEntry(); }}}if ("".equals(ordersStr)) {
            return null;
        }
        String[] bills = ordersStr.split("\r\n");
        List<String> billList = Arrays.asList(bills);
        return billList.parallelStream().map(item -> item.replace("\t"."")).collect(Collectors.toList());
    }

    /** * Verify signature **@paramRequest request *@return boolean
     */
    public boolean rsaCheckV1(HttpServletRequest request) {
        // https://docs.open.alipay.com/54/106370
        // Get feedback from Alipay POST
        Map<String, String> params = new HashMap<>(1);
        Map requestParams = request.getParameterMap();
        for (Iterator 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);
        }
        try {
            return AlipaySignature.rsaCheckV1(params, aliPayProperties.getAlipayPublicKey(), aliPayProperties.getCharset(), aliPayProperties.getSignType());
        } catch (AlipayApiException e) {
            log.debug("verify sign error, exception is:", e);
            return false; }}}Copy the code

Access to various payment scenarios

1. Alipay in-person payment (offline payment by scanning code)

Includes merchant scan code gun scan code payment own scan code payment

/** * Alipay face to face payment controller **@author Manaphy
 */
@Slf4j
@RestController
@RequestMapping("/alipay/f2fPay")
public class AlipayFace2FaceController {

    @Resource
    private AlipayProperties aliPayProperties;

    @Resource
    private AlipayTradeService alipayTradeService;

    /** * Face to face payment - bar code payment * <p> * Merchants use scanning tools (scanning code guns, etc.) to scan users' Alipay payment code **@paramAuthCode Alipay payment code *@return {@link String}
     */
    @PostMapping("/barCodePay")
    public String barCodePay(String authCode) {
        // If you want to use the product id, you need to query the basic information of the product and calculate the price (there may be any discount)

        // (Required) The unique order number in the order system of the merchant website. It contains no more than 64 characters and can only contain letters, digits, and underscores (_).
        String outTradeNo = UUID.randomUUID().toString();

        // (required) Order title, which roughly describes the user's payment purpose. For example, "Costal (Pudong Store) consumption"
        String subject = Test Order;

        // Order description, can be a detailed description of the transaction or goods, for example, fill in "purchase 2 items total 15.00 yuan"
        String body = "Buy 2 items total 5.05 yuan";

        // (required) Total amount of the order, unit: YUAN, no more than 100 million yuan
        // If [discount amount], [non-discount amount] and [Total order Amount] are passed at the same time, the following conditions must be met: [Total order Amount] = [Discount amount] + [Non-discount amount]
        String totalAmount = "5.05";

        // (Optional) There is no discount amount for orders. You can configure discount activities with the merchant platform. If drinks do not participate in the discount, fill in the corresponding amount in this field
        // If the value is not passed, but [total order amount], [discount amount], the default value is [total order amount] - [discount amount].
// String nonDiscountableAmount = "";

        // (Required) Store number of the merchant. Accurate discount information to the store can be configured through the store number and merchant background. Please refer to alipay technical support for details
        String storeId = "test_store_id";

        // Merchant operator id. Add this parameter to make sales statistics for merchant operator
        String operatorId = "test_operator_id";


        // The detailed list of goods, need to fill in the details of the purchase of goods,
        List<GoodsDetail> goodsDetailList = new ArrayList<>();
        GoodsDetail goods1 = GoodsDetail.newInstance("goods_id001"."Whole wheat bun.".1.1);
        goodsDetailList.add(goods1);
        GoodsDetail goods2 = GoodsDetail.newInstance("goods_id002"."Black Toothbrush.".1.2);
        goodsDetailList.add(goods2);

        // Payment timeout, offline scan transaction is defined as 5 minutes
        String timeoutExpress = "5m";

        AlipayTradePayRequestBuilder builder = new AlipayTradePayRequestBuilder()
                .setOutTradeNo(outTradeNo)
                .setSubject(subject)
                .setBody(body)
                .setTotalAmount(totalAmount)
                .setAuthCode(authCode)
                .setTotalAmount(totalAmount)
                .setStoreId(storeId)
                .setOperatorId(operatorId)
                .setGoodsDetailList(goodsDetailList)
                .setTimeoutExpress(timeoutExpress);

        Face to face pay -> face 2 face pay -> f2f pay
        // Return the payment result synchronously
        AlipayF2FPayResult f2fPayResult = alipayTradeService.tradePay(builder);
        // Note: Be sure to deal with the outcome of the payment, as not every payment is guaranteed to be successful and may fail
        switch (f2fPayResult.getTradeStatus()) {
            case SUCCESS:
                log.info("Alipay payment success:)");
                break;
            case FAILED:
                log.error("Alipay payment failed!!");
                break;
            case UNKNOWN:
                log.error("System exception, order status unknown!!");
                break;
            default:
                log.error("Unsupported transaction status, transaction return exception!!");
                break;
        }
        return f2fPayResult.getResponse().getBody();
    }

    /** * In-person payment - Scan code payment * < P > * Scan code payment means that the user opens the "Scan" function in Alipay wallet, scans the QR code generated by the merchant for each order in real time, and confirms the payment on the mobile phone. * < P > * Initiate a pre-order request and return the qr code of the order synchronously * < P > * Applicable scenario: the merchant obtains the QR code to display on the screen, and then the user goes to scan the QR code on the screen * *@paramThe response response *@throwsThe Exception Exception * /
    @GetMapping("/preCreate")
    public void preCreate(HttpServletResponse response) throws Exception {
        // If you want to use the product id, you need to query the basic information of the product and calculate the price (there may be any discount)

        // (Required) The unique order number in the order system of the merchant website. It contains no more than 64 characters and can only contain letters, digits, and underscores (_).
        String outTradeNo = UUID.randomUUID().toString();
        log.info(outTradeNo);
        // (required) Order title, which roughly describes the user's payment purpose. For example, "Costal (Pudong Store) consumption"
        String subject = Test Order;

        // Order description, can be a detailed description of the transaction or goods, for example, fill in "purchase 2 items total 15.00 yuan"
        String body = "2.05 yuan for 2 items";

        // (required) Total amount of the order, unit: YUAN, no more than 100 million yuan
        // If [discount amount], [non-discount amount] and [Total order Amount] are passed at the same time, the following conditions must be met: [Total order Amount] = [Discount amount] + [Non-discount amount]
        String totalAmount = "2.05";

        // (Optional) There is no discount amount for orders. You can configure discount activities with the merchant platform. If drinks do not participate in the discount, fill in the corresponding amount in this field
        // If the value is not passed, but [total order amount], [discount amount], the default value is [total order amount] - [discount amount].
        String nonDiscountableAmount = "";

        // The seller's Alipay account ID is used to support the payment to different receiving accounts under one signed account (payment to the alipay account corresponding to sellerId)
        // If this field is empty, it defaults to the PID of the merchant signed with Alipay, that is, the PID corresponding to the APPID
        String sellerId = "";

        // (Required) Store number of the merchant. Accurate discount information to the store can be configured through the store number and merchant background. Please refer to alipay technical support for details
        String storeId = "test_store_id";

        // Merchant operator id. Add this parameter to make sales statistics for merchant operator
        String operatorId = "test_operator_id";

        // The detailed list of goods, need to fill in the details of the purchase of goods,
        List<GoodsDetail> goodsDetailList = new ArrayList<>();
        GoodsDetail goods1 = GoodsDetail.newInstance("goods_id003"."Colgate Toothpaste".1.1);
        goodsDetailList.add(goods1);
        GoodsDetail goods2 = GoodsDetail.newInstance("goods_id004"."Black Toothbrush.".1.2);
        goodsDetailList.add(goods2);

        // Payment timeout, offline scan transaction is defined as 5 minutes
        String timeoutExpress = "5m";

        AlipayTradePrecreateRequestBuilder builder = new AlipayTradePrecreateRequestBuilder()
                .setSubject(subject)
                .setTotalAmount(totalAmount)
                .setOutTradeNo(outTradeNo)
                .setUndiscountableAmount(nonDiscountableAmount)
                .setSellerId(sellerId)
                .setBody(body)
                .setOperatorId(operatorId)
                .setStoreId(storeId)
                .setTimeoutExpress(timeoutExpress)
                // The Alipay server actively informs the merchant of the specified page HTTP path in the server, which can be set as required
                .setNotifyUrl(aliPayProperties.getNotifyUrl())
                .setGoodsDetailList(goodsDetailList);

        AlipayF2FPrecreateResult result = alipayTradeService.tradePrecreate(builder);
// String qrCodeUrl = null;
        switch (result.getTradeStatus()) {
            case SUCCESS:
                log.info("Alipay pre-order successful:)");
                AlipayTradePrecreateResponse res = result.getResponse();
                BufferedImage image = PayUtil.getQrCodeImage(res.getQrCode());

                response.setContentType("image/jpeg");
                response.setHeader("Pragma"."no-cache");
                response.setHeader("Cache-Control"."no-cache");
                response.setIntHeader("Expires", -1);
                ImageIO.write(image, "JPEG", response.getOutputStream());
                break;
            case FAILED:
                log.error("Alipay pre-order failed!!");
                break;
            case UNKNOWN:
                log.error("System abnormal, pre-order status unknown!!");
                break;
            default:
                log.error("Unsupported transaction status, transaction return exception!!");
                break; }}}Copy the code

2. Alipay web payment

/** * Alipay webpage payment controller **@author Manaphy
 */
@Controller
@RequestMapping("/alipay/web")
public class AlipayWebPayController {

    @Resource
    private AlipayProperties aliPayProperties;

    @Resource
    private AlipayClient alipayClient;

    @PostMapping("/pay")
    public void testPay(HttpServletResponse response) {
        try {
            // Order model
            String productCode = "FAST_INSTANT_TRADE_PAY";
            AlipayTradePagePayModel model = new AlipayTradePagePayModel();
            model.setOutTradeNo(UUID.randomUUID().toString());
            model.setSubject("Test Web payment");
            model.setTotalAmount("5");
            model.setBody("Webpage payment test, total $5");
            model.setTimeoutExpress("2m");
            model.setProductCode(productCode);

            AlipayTradePagePayRequest pagePayRequest = new AlipayTradePagePayRequest();
            pagePayRequest.setReturnUrl(aliPayProperties.getReturnUrl());
            pagePayRequest.setNotifyUrl(aliPayProperties.getNotifyUrl());
            pagePayRequest.setBizModel(model);

            // Call the SDK to generate the form and print the complete form HTML directly to the page
            String retStr = alipayClient.pageExecute(pagePayRequest).getBody();
            response.setContentType("text/html; charset=utf-8");
            response.getWriter().write(retStr);
            response.getWriter().flush();
            response.getWriter().close();
        } catch(AlipayApiException | IOException e) { e.printStackTrace(); }}}Copy the code

3. Alipay mobile payment

/** * Alipay mobile payment controller **@author Manaphy
 */
@Controller
@Slf4j
@RequestMapping("/alipay/wap")
public class AlipayWapPayController {

    @Resource
    private AlipayProperties aliPayProperties;

    @Resource
    private AlipayClient alipayClient;

    @PostMapping("/pay")
    public void testPay(HttpServletResponse response) {
        try {
            // Order model
            String productCode = "QUICK_WAP_WAY";
            AlipayTradePagePayModel model = new AlipayTradePagePayModel();
            model.setOutTradeNo(UUID.randomUUID().toString());
            model.setSubject("Mobile payment Test");
            model.setTotalAmount("10");
            model.setBody("Mobile payment test, total $10.");
            model.setTimeoutExpress("2m");
            model.setProductCode(productCode);

            AlipayTradeWapPayRequest wapPayRequest = new AlipayTradeWapPayRequest();
            wapPayRequest.setReturnUrl(aliPayProperties.getReturnUrl());
            wapPayRequest.setNotifyUrl(aliPayProperties.getNotifyUrl());
            wapPayRequest.setBizModel(model);

            // Call the SDK to generate the form and print the complete form HTML directly to the page
            String retStr = alipayClient.pageExecute(wapPayRequest).getBody();
            response.setContentType("text/html; charset=" + aliPayProperties.getCharset());
            response.getWriter().write(retStr);
            response.getWriter().flush();
            response.getWriter().close();
        } catch(IOException | AlipayApiException e) { e.printStackTrace(); }}}Copy the code

test

1. Configure view forwarding

/** * View configuration class * handles page jumps **@author Manaphy
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/alipay/webTest").setViewName("WebPay");
        registry.addViewController("/alipay/wapTest").setViewName("WapPay"); }}Copy the code

2. Test web pages

PC (WebPay. HTML)

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body style="font-size: 30px">

<form method="post" action="/alipay/web/pay">
  <h3>Buy goods: Test goods</h3>
  <h3>Price: 5</h3>
  <h3>Unit: Each</h3>

  <button style="width: 100%; height: 60px; alignment: center; background: #b49e8f" type="submit">Immediate payment</button>
</form>

</body>
</html>
Copy the code

The mobile end (WebPay. HTML)

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body style="font-size: 30px">

<form method="post" action="/alipay/wap/pay">
  <h3>Buy goods: Test goods</h3>
  <h3>Price: 10</h3>
  <h3>Unit: Each</h3>

  <button style="width: 100%; height: 60px; alignment: center; background: blue" type="submit">pay</button>
</form>
</body>
</html>
Copy the code

3. Skip page of payment results

Success (success. HTML)

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<h1>Pay for success</h1>
</body>
</html>
Copy the code

Failure (fail. HTML)

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<h1>Pay for failure</h1>
</body>
</html>
Copy the code

4. Test

  1. Web payment test:

The browser visit: http://localhost:8081/alipay/webTest

  1. Mobile payment test:

Mobile phone browser to access: dqidj6 natappfree. Cc/alipay/wapT…

Or visit LAN address: http://pc IP/Alipay /wapTest

  1. Face to face test:

Using interface testing tools such as the POSTMAN visit: http://localhost:8081/alipay/f2fPay/barCodePay? AuthCode = payment code

  1. Offline scan code payment test:

In the browser visit: http://localhost:8081/alipay/f2fPay/preCreate