Spring Boot入门教程(三十六):支付宝集成-当面付

一:简介

当面付分为三种,不同的方式有着不同的使用场景,当面付的字面含义是面对面的付也就是买家当着卖家的面来付款。其中条码支付和扫码支付使用的最多,本文主要介绍这两种。

  • 当面付

    • 条码支付(商家使用扫码设备扫描买家支付宝的付款码):条码支付是支付宝给到线下传统行业的一种收款方式。商户使用扫码枪等条码识别设备扫描用户支付宝钱包上的条码/二维码,完成收款。一般在超市、便利店、店铺等使用。
    • 扫码支付(买家使用支付宝扫一扫扫描卖家的二维码):指用户打开支付宝钱包中的“扫一扫”功能,扫描商户针对每个订单实时生成的订单二维码,并在手机端确认支付。一般在类似于无人售货机上使用,像地铁中的无人售货饮料机、医院中的自助挂号收费机等
    • 声波支付

在接入之前首先要熟悉一下当面付产品的业务文档:

当面付SDK&Demo 拿到demo我们熟悉一下demo,就可以依葫芦画瓢了,集成到自己工程中来。

通过下载Demo可以看到有两个工程,一个是TradePayDemo,一个是TradePaySDK,集成SDK必须要使用alipay-sdk-java20161213173952.jar,而TradePaySDK是对alipay-sdk-java.jar的进一步封装,在TradePayDemo中可以看到已经对TradePaySDK打成了jar包依赖进来了,当然也可以将整个TradePaySDK的src目录拖入到自己工程中, 当然也可以不使用alipay-trade-sdk.jar直接使用alipay-sdk-java.jar来开发。

集成当面付即可以参考TradePayDemo中的Main.java也可以参考WebRoot下的JSP,这里参考JSP中的代码来集成。
这里注意一点:jsp中的最上面的注释,所有的jsp文件都是一样的,作者粘贴了也不知道改一下,吐槽一下:这Demo写的真不细心

  • 条码付参考trade_pay.jsp
  • 扫码付参考trade_precreate.jsp
  • 退款参考trade_refund.jsp
  • 交易查询参考trade_query.jsp

这里写图片描述

这里写图片描述

条码支付流程图:
这里写图片描述

扫码支付流程图:
这里写图片描述

二:集成步骤

0. 创建应用、配置密钥

集成前需要先创建应用、配置密钥、回调地址等,具体操作请查看Spring Boot入门教程(三十五):支付宝集成-准备工作

1. 将Demo中的alipay-sdk-java和alipay-trade-sdk两个jar安装到本地maven仓库中

注意:将jar打到本地maven仓库时命令行最好是一行,不好有回车换行, 这里使用2017版本的,不是2016版本的,Demo中的还是2016的版本。

mvn install:install-file -DgroupId=com.alipay -DartifactId=alipay-sdk-java -Dversion= 20170725114550 -Dpackaging=jar -Dfile=alipay-sdk-java-20170725114550.jar
mvn install:install-file -DgroupId=com.alipay -DartifactId=alipay-trade-sdk -Dversion=20161215 -Dpackaging=jar -Dfile=alipay-trade-sdk-20161215.jar

2. 引入依赖

<!-- alipay begin -->
<dependency>
    <groupId>com.alipay</groupId>
    <artifactId>alipay-sdk-java</artifactId>
    <version>20170725114550</version>
</dependency>
<dependency>
    <groupId>com.alipay</groupId>
    <artifactId>alipay-trade-sdk</artifactId>
    <version>20161215</version>
</dependency>
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
</dependency>
<dependency>
    <groupId>commons-configuration</groupId>
    <artifactId>commons-configuration</artifactId>
    <version>1.10</version>
</dependency>
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.2.1</version>
</dependency>
<!-- alipay end -->

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

3. 配置application.yml

注意:配置appPrivateKey和alipayPublicKey时值不要换行,returnUrl在当面付中不是必须的
注意:这里的returnUrl和notifyUrl在测试的时候需要外网能访问,可以通过natapp来实现

# 沙箱配置
pay:
  alipay:
    gatewayUrl: https://openapi.alipaydev.com/gateway.do
    appid: 2016091400508498
    appPrivateKey: MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCtXKWs+trRSuCxEUvlsEeSAuLWs3B/Dh74R8223BnfzoA29aGeoycAqfKlBMcbzU2G1KayESvZKGpwBLeejSbecRYjgZsQDyEaDimQQJtGFvZVV6u4XNnvIJ72eQzEaEIQfuiorjBTLm6DQuds4R0GxftqON6QFoIZkWB9ZrZKd02cuy16dW+UqtLVGGAHcCIAkB63zUiKSNfweMddneZ7MVs3lvu3xhMnD+5us/+n2Vp4qhfmpYLcdqIW6InU4GypeoOpyUTrfUGpgdR0l924vHy/GQJZEKFaRcK3cYK+ECyKpSIoqaJJFLHbkqsliuPpMUG+rM3jiqeIAH4psAznAgMBAAECggEBAJ5jyEbbxrJzrAh7GhHX1fwUQPYSadTbrPYAfHX2cHlnrQMJtsk+nTLhEv0r+VJwZ8WpYkfMong8kcqYtL7ajcmsHqMAFhE9EWxBxj2ymWsXLabZe93sj30IG9Rq0nxcGQgDO0RqKWLGSFgK93Al2KRInKT3InkY53K+vR61ihVLmGf7+GwPtIZteBy+tuAyvcj2RlkYvjiFi3ySyZ1wA3sJIlgrGiixd6fj20XFGNE3CnAwu0BJgXXbP/S9J4C0RRa3ZXj8fX7oONhVxz2xKxn7AT4e8OWjt7J41H2LRct8Fgl9pqgz2FJYv/WfbkG8x9fGiKYYvPXoxjjrk/tkewkCgYEA8f9Lcu5JPrE9rpw9zlwhm5cOO81xLxdwL5R5/1bRP48BZGIYuqlCbVvjJVqtO8eTnLhUwH7fG8B7cmoeO9bGr9GQrtfyCqz6FtVymTBieJlfgZDVhtzyv2qKOBMIFE8jsbSBK/NHHMvykJ+XdQ1riwCeQDdXICRuYTTFwGk2OsUCgYEAt2SoN95tVmVrvKG6ATLNEtge/ozeVywA4GjltrSw/G9vqp+DkkT2pY19uROuzMazoTzKWpPho2q/qzNlv/ANbOFM2GEmKamQ7CO88JgRxMsPTvc/HxCLU/ClMJU8LcOf9LfP2KYZpPwuheKJoF4vDGj8NsbFmccJyYSdpkNEk7sCgYBJlL2FMaz1sgC2Ue19DIhvfaunRV1P20mSPgwmNmijccETm7w3LXX0OIdFeV/JGHLqqSWj7i+6iXk/ncKZoUGCfi8G6sQ+uL/GJ5qTt6GJV+ExTS+PtSjeSO/EAw1m13Vb+C16hpstx1l23f+4aJ81gbecgPct38XsKpaiXZtOnQKBgQCMsN7QRYYxwoq9YoDUzIlAzKYyeBVWYL6najHYUZR5hG/xQIBqZRem9/4cTvpJxKInrwA6LrrqaEl0aHDFp75U6h7O3PCvA5PXZK9dD/yJsZIj7U/yX/nTQokn1UUegrYiwiTkusBvrrtuINWePsLvTVc4GpObHnPmsiNTWsWwYwKBgENaeTNOCHV2km/ysXQSEIhKbtlAMQPsgWHCt/bzHlF9m18izb1LrJyjzcSsd+Zy78R+pv4G50Q27c3e/DFPz/wYxN/yHWRbyLBA8ipJbCtMtPEdS9krpmN6cChIdLGbz4CVUqOPSRzNb9lhhgPCcCNRq6DG3HBceb1Se9VnO3zk
    alipayPublicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApFQKccMq+wPoWh93DcX3XYrnT7FJ3gntJA/jEwgk6Jd3iEVS2CyUCCgFVcQn8xjXT81YbZHYvoC50IBuu+A+Ey+J8VIgsBm5g9uwbOLRf66GrZjuKOlalHm5gHXjcL2gZRMBJEStOxstcO2YQriqhQzdL3EKp+HQc9u14IOVFpZdR8qq1l7CzKHn1vQo/1fUPfUrTLQqSuQTU9Ospv/QHFqOJA7DPetUQ+jnaZ082f3clr4ITw4EE8A6IWqTXcETTx5j/udCGP84g2Y4j+8i9DqYGyD5ePVgt4G0ICBX1bi1qNlylfxRg8Y3c1DFrRGyr0NpKQxSVXkYaVNvrCoudQIDAQAB
    returnUrl: http://fuzsmc.natappfree.cc/alipay/return
    notifyUrl: http://fuzsmc.natappfree.cc/alipay/notify

4. AliPayProperties

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;

import javax.annotation.PostConstruct;

/**
 * @author mengday zhang
 */
@Data
@Slf4j
@ConfigurationProperties(prefix = "pay.alipay")
public class AlipayProperties {

    /** 支付宝gatewayUrl */
    private String gatewayUrl;
    /** 商户应用id */
    private String appid;
    /** RSA私钥,用于对商户请求报文加签 */
    private String appPrivateKey;
    /** 支付宝RSA公钥,用于验签支付宝应答 */
    private String alipayPublicKey;
    /** 签名类型 */
    private String signType = "RSA2";

    /** 格式 */
    private String formate = "json";
    /** 编码 */
    private String charset = "UTF-8";

    /** 同步地址 */
    private String returnUrl;

    /** 异步地址 */
    private String notifyUrl;

    /** 最大查询次数 */
    private static int maxQueryRetry = 5;
    /** 查询间隔(毫秒) */
    private static long queryDuration = 5000;
    /** 最大撤销次数 */
    private static int maxCancelRetry = 3;
    /** 撤销间隔(毫秒) */
    private static long cancelDuration = 3000;

    private AlipayProperties() {}

    /**
     * PostContruct是spring框架的注解,在方法上加该注解会在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法。
     */
    @PostConstruct
    public void init() {
        log.info(description());
    }

    public String description() {
        StringBuilder sb = new StringBuilder("\nConfigs{");
        sb.append("支付宝网关: ").append(gatewayUrl).append("\n");
        sb.append(", appid: ").append(appid).append("\n");
        sb.append(", 商户RSA私钥: ").append(getKeyDescription(appPrivateKey)).append("\n");
        sb.append(", 支付宝RSA公钥: ").append(getKeyDescription(alipayPublicKey)).append("\n");
        sb.append(", 签名类型: ").append(signType).append("\n");

        sb.append(", 查询重试次数: ").append(maxQueryRetry).append("\n");
        sb.append(", 查询间隔(毫秒): ").append(queryDuration).append("\n");
        sb.append(", 撤销尝试次数: ").append(maxCancelRetry).append("\n");
        sb.append(", 撤销重试间隔(毫秒): ").append(cancelDuration).append("\n");
        sb.append("}");
        return sb.toString();
    }

    private String getKeyDescription(String key) {
        int showLength = 6;
        if (StringUtils.isNotEmpty(key) && key.length() > showLength) {
            return new StringBuilder(key.substring(0, showLength)).append("******")
                    .append(key.substring(key.length() - showLength)).toString();
        }
        return null;
    }
}

5. AliPayConfiguration

@Configuration
@EnableConfigurationProperties(AlipayProperties.class)
public class AlipayConfiguration {

    private AlipayProperties properties;

    public AlipayConfiguration(AlipayProperties properties) {
        this.properties = properties;
    }

    @Bean
    public AlipayTradeService alipayTradeService() {
        return new AlipayTradeServiceImpl.ClientBuilder()
                .setGatewayUrl(properties.getGatewayUrl())
                .setAppid(properties.getAppid())
                .setPrivateKey(properties.getAppPrivateKey())
                .setAlipayPublicKey(properties.getAlipayPublicKey())
                .setSignType(properties.getSignType())
                .build();
    }
}

6. PayUtil

public class PayUtil {

    /**
     * 根据url生成二位图片对象
     *
     * @param codeUrl
     * @return
     * @throws WriterException
     */
    public static BufferedImage getQRCodeImge(String codeUrl) throws WriterException {
        Map<EncodeHintType, Object> hints = new Hashtable();
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
        hints.put(EncodeHintType.CHARACTER_SET, "UTF8");
        int width = 256;
        BitMatrix bitMatrix = (new MultiFormatWriter()).encode(codeUrl, BarcodeFormat.QR_CODE, width, width, hints);
        BufferedImage image = new BufferedImage(width, width, 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);
            }
        }

        return image;
    }
}

7. AlipayF2FController

package com.example.paydemo.controller;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.response.AlipayTradePrecreateResponse;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.alipay.demo.trade.model.GoodsDetail;
import com.alipay.demo.trade.model.builder.AlipayTradePayRequestBuilder;
import com.alipay.demo.trade.model.builder.AlipayTradePrecreateRequestBuilder;
import com.alipay.demo.trade.model.builder.AlipayTradeQueryRequestBuilder;
import com.alipay.demo.trade.model.builder.AlipayTradeRefundRequestBuilder;
import com.alipay.demo.trade.model.result.AlipayF2FPayResult;
import com.alipay.demo.trade.model.result.AlipayF2FPrecreateResult;
import com.alipay.demo.trade.model.result.AlipayF2FQueryResult;
import com.alipay.demo.trade.model.result.AlipayF2FRefundResult;
import com.alipay.demo.trade.service.AlipayTradeService;
import com.alipay.demo.trade.utils.ZxingUtils;
import com.example.paydemo.configuration.AlipayProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.util.*;

/**
 * 支付宝-当面付 控制器.
 * <p>
 * https://openclub.alipay.com/read.php?tid=1720&fid=40
 *
 * https://docs.open.alipay.com/203/105910
 *
 * @author Mengday Zhang
 * @version 1.0
 * @since 2018/6/4
 */
@Slf4j
@RestController
@RequestMapping("/alipay")
public class AlipayF2FController {

    @Autowired
    private AlipayTradeService alipayTradeService;

    @Autowired
    private AlipayProperties aliPayProperties;

    /**
     * 当面付-条码付
     *
     * 商家使用扫码工具(扫码枪等)扫描用户支付宝的付款码
     *
     * @param authCode
     */
    @PostMapping("/f2fpay/barCodePay")
    public String barCodePay(String authCode){
        // 实际使用时需要根据商品id查询商品的基本信息并计算价格(可能还有什么优惠),这里只是写死值来测试

        // (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
        String outTradeNo = UUID.randomUUID().toString();

        // (必填) 订单标题,粗略描述用户的支付目的。如“喜士多(浦东店)消费”
        String subject = "测试订单";

        // 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
        String body = "购买商品2件共20.05元";

        // (必填) 订单总金额,单位为元,不能超过1亿元
        // 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
        String totalAmount = "0.01";

        // (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
        // 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
        String undiscountableAmount = "";

        // (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
        String storeId = "test_store_id";

        // 商户操作员编号,添加此参数可以为商户操作员做销售统计
        String operatorId = "test_operator_id";


        // 商品明细列表,需填写购买商品详细信息,
        List<GoodsDetail> goodsDetailList = new ArrayList<>();
        GoodsDetail goods1 = GoodsDetail.newInstance("goods_id001", "全麦小面包", 1, 1);
        goodsDetailList.add(goods1);
        GoodsDetail goods2 = GoodsDetail.newInstance("goods_id002", "黑人牙刷", 1, 2);
        goodsDetailList.add(goods2);

        // 支付超时,线下扫码交易定义为5分钟
        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
        // 同步返回支付结果
        AlipayF2FPayResult f2FPayResult = alipayTradeService.tradePay(builder);
        // 注意:一定要处理支付的结果,因为不是每次支付都一定会成功,可能会失败
        switch (f2FPayResult.getTradeStatus()) {
            case SUCCESS:
                log.info("支付宝支付成功: )");
                break;

            case FAILED:
                log.error("支付宝支付失败!!!");
                break;

            case UNKNOWN:
                log.error("系统异常,订单状态未知!!!");
                break;

            default:
                log.error("不支持的交易状态,交易返回异常!!!");
                break;
        }

        /**
         * {
         *   "alipay_trade_pay_response": {
         *     "code": "10000",
         *     "msg": "Success",
         *     "buyer_logon_id": "ekf***@sandbox.com",
         *     "buyer_pay_amount": "0.01",
         *     "buyer_user_id": "2088102176027680",
         *     "buyer_user_type": "PRIVATE",
         *     "fund_bill_list": [
         *       {
         *         "amount": "0.01",
         *         "fund_channel": "ALIPAYACCOUNT"
         *       }
         *     ],
         *     "gmt_payment": "2018-06-10 14:54:16",
         *     "invoice_amount": "0.01",
         *     "out_trade_no": "91fbd3fa-8558-443a-82c2-bd8e941d7e71",
         *     "point_amount": "0.00",
         *     "receipt_amount": "0.01",
         *     "total_amount": "0.01",
         *     "trade_no": "2018061021001004680200326495"
         *   },
         *   "sign": "BNgMmA2t8fwFZNSa39kyEKgL6hV45DVOKOsdyyzTzsQnX8HEkKOzVevQEDyK083dNYewip1KK/K92BTDY2KMAsrOEqcCNxsk9NLAvK9ZQVxQzFbAFKqs5EBAEzncSWnChJcb7VMhDakUxHZFmclHg38dLJiHE2bEcF8ar9R1zj0p4V0Jr+BXO10kLtaSTc9NeaCwJZ89sPHKitNnUWRroU7t0xPHc1hWpstObwixKmAWnsFyb9eyGwPQnqNBsUVNSNWCJ7Pg3rb03Tx6J3zNsqH5f0YhWilMi09npPe33URFc6zG1HJSfhEm4Gq1zwQrPoA/anW8BbdmEUUmNo1dEw=="
         * }
         */
        String result = f2FPayResult.getResponse().getBody();

        return result;
    }

    /**
     * 当面付-扫码付
     *
     * 扫码支付,指用户打开支付宝钱包中的“扫一扫”功能,扫描商户针对每个订单实时生成的订单二维码,并在手机端确认支付。
     *
     * 发起预下单请求,同步返回订单二维码
     *
     * 适用场景:商家获取二维码展示在屏幕上,然后用户去扫描屏幕上的二维码
     * @return
     * @throws AlipayApiException
     */
    @PostMapping("/precreate")
    public void precreate(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 实际使用时需要根据商品id查询商品的基本信息并计算价格(可能还有什么优惠),这里只是写死值来测试

        // (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
        String outTradeNo = UUID.randomUUID().toString();

        // (必填) 订单标题,粗略描述用户的支付目的。如“喜士多(浦东店)消费”
        String subject = "测试订单";

        // 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
        String body = "购买商品2件共20.05元";

        // (必填) 订单总金额,单位为元,不能超过1亿元
        // 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
        String totalAmount = "0.01";

        // (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
        // 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
        String undiscountableAmount = "";

        // 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
        // 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
        String sellerId = "";

        // (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
        String storeId = "test_store_id";

        // 商户操作员编号,添加此参数可以为商户操作员做销售统计
        String operatorId = "test_operator_id";


        // 商品明细列表,需填写购买商品详细信息,
        List<GoodsDetail> goodsDetailList = new ArrayList<>();
        GoodsDetail goods1 = GoodsDetail.newInstance("goods_id001", "全麦小面包", 1, 1);
        goodsDetailList.add(goods1);
        GoodsDetail goods2 = GoodsDetail.newInstance("goods_id002", "黑人牙刷", 1, 2);
        goodsDetailList.add(goods2);

        // 支付超时,线下扫码交易定义为5分钟
        String timeoutExpress = "5m";

        AlipayTradePrecreateRequestBuilder builder =new AlipayTradePrecreateRequestBuilder()
                .setSubject(subject)
                .setTotalAmount(totalAmount)
                .setOutTradeNo(outTradeNo)
                .setUndiscountableAmount(undiscountableAmount)
                .setSellerId(sellerId)
                .setBody(body)
                .setOperatorId(operatorId)
                .setStoreId(storeId)
                .setTimeoutExpress(timeoutExpress)
                //支付宝服务器主动通知商户服务器里指定的页面http路径,根据需要设置
                .setNotifyUrl(aliPayProperties.getNotifyUrl())
                .setGoodsDetailList(goodsDetailList);

        AlipayF2FPrecreateResult result = alipayTradeService.tradePrecreate(builder);
        String qrCodeUrl = null;
        switch (result.getTradeStatus()) {
            case SUCCESS:
                log.info("支付宝预下单成功: )");

                AlipayTradePrecreateResponse res = result.getResponse();
                BufferedImage image = PayUtil.getQRCodeImge(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("支付宝预下单失败!!!");
                break;

            case UNKNOWN:
                log.error("系统异常,预下单状态未知!!!");
                break;

            default:
                log.error("不支持的交易状态,交易返回异常!!!");
                break;
        }
    }

    /**
     * 订单查询(最主要用于查询订单的支付状态)
     * @param orderNo 商户订单号
     * @return
     */
    @GetMapping("/query")
    public String query(String orderNo){

        AlipayTradeQueryRequestBuilder builder = new AlipayTradeQueryRequestBuilder()
                .setOutTradeNo(orderNo);
        AlipayF2FQueryResult result = alipayTradeService.queryTradeResult(builder);
        switch (result.getTradeStatus()) {
            case SUCCESS:
                log.info("查询返回该订单支付成功: )");

                AlipayTradeQueryResponse resp = result.getResponse();
                log.info(resp.getTradeStatus());
//                log.info(resp.getFundBillList());
                break;

            case FAILED:
                log.error("查询返回该订单支付失败!!!");
                break;

            case UNKNOWN:
                log.error("系统异常,订单支付状态未知!!!");
                break;

            default:
                log.error("不支持的交易状态,交易返回异常!!!");
                break;
        }
        return result.getResponse().getBody();
    }

    /**
     * 退款
     * @param orderNo 商户订单号
     * @return
     */
    @PostMapping("/refund")
    public String refund(String orderNo){
        AlipayTradeRefundRequestBuilder builder = new AlipayTradeRefundRequestBuilder()
                .setOutTradeNo(orderNo)
                .setRefundAmount("0.01")
                .setRefundReason("当面付退款测试")
                .setOutRequestNo(String.valueOf(System.nanoTime()))
                .setStoreId("A1");
        AlipayF2FRefundResult result = alipayTradeService.tradeRefund(builder);
        switch (result.getTradeStatus()) {
            case SUCCESS:
                log.info("支付宝退款成功: )");
                break;

            case FAILED:
                log.error("支付宝退款失败!!!");
                break;

            case UNKNOWN:
                log.error("系统异常,订单退款状态未知!!!");
                break;

            default:
                log.error("不支持的交易状态,交易返回异常!!!");
                break;
        }

        return result.getResponse().getBody();
    }


    /**
     * 扫码付异步结果通知
     * https://docs.open.alipay.com/194/103296
     * @param request
     */
    @RequestMapping("/notify")
    public String notify(HttpServletRequest request) throws AlipayApiException {
        // 一定要验签,防止黑客篡改参数
        Map<String, String[]> parameterMap = request.getParameterMap();
        parameterMap.forEach((key, value) -> System.out.println(key+"="+value[0]));

        // https://docs.open.alipay.com/54/106370
        // 获取支付宝POST过来反馈信息
        Map<String,String> params = new HashMap<>();
        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);
        }

        boolean flag = AlipaySignature.rsaCheckV1(params,
                aliPayProperties.getAlipayPublicKey(),
                aliPayProperties.getCharset(),
                aliPayProperties.getSignType());

        if (flag) {
            /**
             * TODO 需要严格按照如下描述校验通知数据的正确性
             *
             * 商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
             * 并判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
             * 同时需要校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email),
             *
             * 上述有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。
             * 在上述验证通过后商户必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。
             * 在支付宝的业务通知中,只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时,支付宝才会认定为买家付款成功。
             */

            return "success";
        }

        return null;
    }
}

结尾

以上代码都实际测试过,所有功能均可用!

猜你喜欢

转载自blog.csdn.net/vbirdbest/article/details/80655716
今日推荐