• 支付宝常用接口统一封装,可直接支付参数使用(适用于H5、PC、APP)


      😊 @ 作者: 一恍过去
      🎊 @ 社区: Java技术栈交流
      🎉 @ 主题: 支付宝常用接口统一封装,可直接支付参数使用(适用于H5、PC、APP)
      ⏱️ @ 创作时间: 2022年07月22日

      1、前言

      沙箱环境获取AppId、商户PID、支付私钥/公钥等信息,不需要自己创建应用。
      地址:https://openhome.alipay.com/develop/sandbox/app

      AppId、商户PID:
      在这里插入图片描述
      公钥、私钥信息:

      在这里插入图片描述

      2、POM

              
              <dependency>
                  <groupId>com.alipay.sdkgroupId>
                  <artifactId>alipay-sdk-javaartifactId>
                  <version>4.31.48.ALLversion>
              dependency>
      
              
              <dependency>
                  <groupId>commons-logginggroupId>
                  <artifactId>commons-loggingartifactId>
                  <version>1.2version>
              dependency>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      3、配置Yaml

      alipay:
        # appid
        appId: 
        # 商户PID,卖家支付宝账号ID
        sellerId: 
        # 私钥 pkcs8格式的,rsc中的私钥:https://openhome.alipay.com/platform/appDaily.htm?tab=info
        privateKey: 
        # 支付宝公钥:https://openhome.alipay.com/platform/appDaily.htm?tab=info
        publicKey: 
        # 服务器异步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
        notifyUrl: http://zhbexg.natappfree.cc/alipay/notify
        # 页面跳转同步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
        returnUrl: https://zhbexg.natappfree.cc/alipay/return
        # 请求网关地址
        # 正式为:"https://openapi.alipay.com/gateway.do"
        serverUrl: https://openapi.alipaydev.com/gateway.do
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16

      4、支付配置类

      AlipayConfig:

      @Component
      @Data
      @ConfigurationProperties(prefix = "alipay")
      public class AlipayConfig {
      
          /**
           * 商户appid
           */
          private String appId;
      
          /**
           * 商户PID,卖家支付宝账号ID
           */
          private String sellerId;
      
          /**
           * 私钥 pkcs8格式的,rsc中的私钥
           */
          private String privateKey;
      
          /**
           * 支付宝公钥
           */
          private String publicKey;
      
          /**
           * 请求网关地址
           */
          private String serverUrl;
      
          /**
           * 页面跳转同步通知(可以直接返回前端页面、或者通过后端进行跳转)
           */
          private String returnUrl;
      
          /**
           * 服务器异步通知
           */
          private String notifyUrl;
      
          /**
           * 获得初始化的AlipayClient
           *
           * @return
           */
          @Bean
          public AlipayClient alipayClient() {
              // 获得初始化的AlipayClient
              return new DefaultAlipayClient(serverUrl, appId, privateKey,
                      AlipayConstants.FORMAT_JSON, AlipayConstants.CHARSET_UTF8,
                      publicKey, AlipayConstants.SIGN_TYPE_RSA2);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53

      5、定义枚举

      PayTypeEnum :

      @Getter
      @AllArgsConstructor
      public enum PayTypeEnum {
          /**
           * PC
           */
          PC("pc","FAST_INSTANT_TRADE_PAY"),
          /**
           * H5
           */
          H5("h5","QUICK_WAP_WAY"),
          /**
           * App
           */
          APP("app","QUICK_MSECURITY_PAY");
      
      
          String type;
          String code;
      
          public static String  getCode(String type) {
              for (PayTypeEnum payType : PayTypeEnum.values()) {
                  if (payType.getType().equals(type)) {
                      return payType.getCode();
                  }
              }
              return null;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29

      6、统一API封装

      AliPayApi:

      public class AliPayApi {
      
          public static String h5Pay(AlipayClient alipayClient, String bizContent, String returnUrl, String notifyUrl, String appAuthToken) throws AlipayApiException {
              // 相应请求API的request
              AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();
              // 在公共参数中设置回跳和通知地址
              alipayRequest.setReturnUrl(returnUrl);
              alipayRequest.setNotifyUrl(notifyUrl);
              alipayRequest.setBizContent(bizContent);
      
              // 服务商模式,第三方授权的token
              if (appAuthToken != null) {
                  alipayRequest.putOtherTextParam("app_auth_token", appAuthToken);
              }
      
              AlipayTradeWapPayResponse payResponse = alipayClient.pageExecute(alipayRequest);
              return readData(payResponse.isSuccess(), payResponse.getBody());
          }
      
          public static String appPay(AlipayClient alipayClient, String bizContent, String returnUrl, String notifyUrl, String appAuthToken) throws AlipayApiException {
              // 相应请求API的request
              AlipayTradeAppPayRequest alipayRequest = new AlipayTradeAppPayRequest();
              // 在公共参数中设置回跳和通知地址
              alipayRequest.setReturnUrl(returnUrl);
              alipayRequest.setNotifyUrl(notifyUrl);
              alipayRequest.setBizContent(bizContent);
      
              // 服务商模式,第三方授权的token
              if (appAuthToken != null) {
                  alipayRequest.putOtherTextParam("app_auth_token", appAuthToken);
              }
              AlipayTradeAppPayResponse payResponse = alipayClient.pageExecute(alipayRequest);
              return readData(payResponse.isSuccess(), payResponse.getBody());
          }
      
          public static String pcPay(AlipayClient alipayClient, String bizContent, String returnUrl, String notifyUrl, String appAuthToken) throws AlipayApiException {
              // 相应请求API的request
              AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
              // 在公共参数中设置回跳和通知地址
              alipayRequest.setReturnUrl(returnUrl);
              alipayRequest.setNotifyUrl(notifyUrl);
              alipayRequest.setBizContent(bizContent.toString());
      
              // 服务商模式,第三方授权的token
              if (appAuthToken != null) {
                  alipayRequest.putOtherTextParam("app_auth_token", appAuthToken);
              }
      
              AlipayTradePagePayResponse payResponse = alipayClient.pageExecute(alipayRequest);
              return readData(payResponse.isSuccess(), payResponse.getBody());
          }
      
          private static String readData(boolean isSuccess, String body) {
              if (isSuccess) {
                  return body;
              } else {
                  throw new RuntimeException(body);
              }
      
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61

      7、下单、退款、查询等7类接口

      PayController:

      
      import com.alibaba.fastjson.JSON;
      import com.alibaba.fastjson.JSONArray;
      import com.alibaba.fastjson.JSONObject;
      import com.alipay.api.AlipayClient;
      import com.alipay.api.request.*;
      import com.alipay.api.response.*;
      import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
      import com.lhz.config.AlipayConfig;
      import com.lhz.enums.PayTypeEnum;
      import io.swagger.annotations.Api;
      import io.swagger.annotations.ApiOperation;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.stereotype.Controller;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.ResponseBody;
      
      import javax.annotation.Resource;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.io.IOException;
      import java.io.PrintWriter;
      import java.util.HashMap;
      import java.util.Map;
      
      /**
       * @Author: 
       * @Description:
       **/
      @Api(tags = "统一支付接口")
      @Controller
      @Slf4j
      public class TestController {
      
          @Resource
          private AlipayClient alipayClient;
      
          @Resource
          private AlipayConfig alipayConfig;
      
          /**
           * 下单交易接口,在测试时因为没有App环境,没有对App进行验证,H5和PC都有测试完整的流程
           * 下单接口会返回一个字符串,改字符串为支付宝支付的一个表单页面,由前端直接加载即可
           *
           * @param type pc、h5、app
           * @throws IOException
           * @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivbs?scene=21
           */
          @ApiOperation(value = "支付统一下单", notes = "支付统一下单")
          @ApiOperationSupport(order = 1)
          @GetMapping("/pay/{type}")
          @ResponseBody
          public Map<String, String> pay(@PathVariable("type") String type) {
      
              String body = null;
      
              long outTradeNo = System.currentTimeMillis();
              log.info("支付订单,订单号:" + outTradeNo);
      
              // 封装商品信息
              JSONObject bizContent = new JSONObject();
              bizContent.put("out_trade_no", outTradeNo);
              bizContent.put("total_amount", 0.01);
              bizContent.put("subject", "测试商品");
              // H5:QUICK_WAP_WAY,PC:FAST_INSTANT_TRADE_PAY,APP:QUICK_MSECURITY_PAY
              String payCode = PayTypeEnum.getCode(type);
              bizContent.put("product_code", payCode);
              // 用户付款中途退出返回商户网站的地址
              bizContent.put("quit_url", "https://www.baidu.com");
      
              // 商品明细信息,按需传入
              JSONArray goodsDetail = new JSONArray();
              JSONObject goods = new JSONObject();
              goods.put("goods_id", "goodsNo1");
              goods.put("goods_name", "子商品1");
              // 商品数量
              goods.put("quantity", 1);
              goods.put("price", 0.01);
              goods.put("body", "商品描述信息");
              goodsDetail.add(goods);
              bizContent.put("goods_detail", goodsDetail);
      
              // 扩展信息,按需传入
              JSONObject extendParams = new JSONObject();
              extendParams.put("sys_service_provider_id", "2088511833207846");
              bizContent.put("extend_params", extendParams);
      
              // H5、App公共参数
              if (payCode.equals(PayTypeEnum.H5.getCode())) {
                  // 公用回传参数,如果请求时传递了该参数,则返回给商户时会回传该参数
                  // 本参数必须进行UrlEncode之后才可以发送给支付宝
                  bizContent.put("passback_params", "passbackParams");
      
                  body = requestPay(PayTypeEnum.H5.getCode(), bizContent.toString());
              }
      
              // App独有参数
              if (payCode.equals(PayTypeEnum.APP.getCode())) {
                  // 公用回传参数,如果请求时传递了该参数,则返回给商户时会回传该参数
                  // 本参数必须进行UrlEncode之后才可以发送给支付宝
                  bizContent.put("passback_params", "passbackParams");
                  // 外部指定买家
                  JSONObject extUserInfo = new JSONObject();
                  // 指定买家姓名,need_check_info=T时该参数才有效
                  extUserInfo.put("name", "李四");
                  // 指定买家手机号
                  extUserInfo.put("mobile", "17607235621");
                  /*指定买家证件类型,  need_check_info=T时该参数才有效
                   * IDENTITY_CARD:身份证;
                   * PASSPORT:护照;
                   * OFFICER_CARD:军官证;
                   * SOLDIER_CARD:士兵证;
                   * HOKOU:户口本。如有其它类型需要支持,请与支付宝工作人员联系。
                   */
                  extUserInfo.put("cert_type", "123");
                  // 买家证件号
                  extUserInfo.put("cert_no", "123");
                  // 允许的最小买家年龄,need_check_info=T时该参数才有效,为整数,必须大于等于0
                  extUserInfo.put("min_age", 10);
                  // 是否强制校验买家信息;值为T、F
                  extUserInfo.put("need_check_info", "F");
      
                  bizContent.put("ext_user_info", extUserInfo);
                  /*
                   * 返回参数选项,数组格式, 商户通过传递该参数来定制同步需要额外返回的信息字段
                   * fund_bill_list:交易支付使用的资金渠道;
                   * voucher_detail_list:交易支付时使用的所有优惠券信息;
                   * discount_goods_detail:交易支付所使用的单品券优惠的商品优惠信息;
                   * mdiscount_amount:商家优惠金额
                   */
                  bizContent.put("query_options", "[fund_bill_list]");
      
                  body = requestPay(PayTypeEnum.APP.getCode(), bizContent.toString());
              }
      
              // PC独有参数
              if (payCode.equals(PayTypeEnum.PC.getCode())) {
                  /*
                   * 请求后页面的集成方式。
                   * 枚举值:
                   * ALIAPP:支付宝钱包内
                   * PCWEB:PC端访问
                   * 默认值为PCWEB。
                   */
                  bizContent.put("integration_type", "PCWEB");
                  // 请求来源地址。如果使用ALIAPP的集成方式,用户中途取消支付会返回该地址
                  bizContent.put("request_from_url", "https://www.alipay.com/");
                  // 商户门店编号
                  bizContent.put("store_id", "NJ_001");
      
                  /*
                  // 开票信息
                  JSONObject invoiceInfo = new JSONObject();
                  InvoiceKeyInfo invoiceKeyInfo = new InvoiceKeyInfo();
                  // 开票关键信息
                  invoiceInfo.put("key_info", invoiceKeyInfo);
                  // 开票内容
                  invoiceInfo.put("details", "[{\"code\":\"100294400\",\"name\":\"服饰\",\"num\":\"2\",\"sumPrice\":\"200.00\",\"taxRate\":\"6%\"}]");
      
                  bizContent.put("invoice_info", invoiceInfo);
                  */
      
                  body = requestPay(PayTypeEnum.PC.getCode(), bizContent.toString());
              }
              log.info("支付表单:{}", body);
              Map<String, String> map = new HashMap<>();
              map.put("body", body);
              return map;
          }
      
          private String requestPay(String payType, String bizContent) {
              try {
                  String body = null;
                  if (PayTypeEnum.H5.getCode().equals(payType)) {
                      body = AliPayApi.h5Pay(alipayClient, bizContent, alipayConfig.getReturnUrl(), alipayConfig.getNotifyUrl(), null);
                  }
                  if (PayTypeEnum.APP.getCode().equals(payType)) {
                      body = AliPayApi.appPay(alipayClient, bizContent, alipayConfig.getReturnUrl(), alipayConfig.getNotifyUrl(), null);
                  }
                  if (PayTypeEnum.PC.getCode().equals(payType)) {
                      body = AliPayApi.pcPay(alipayClient, bizContent, alipayConfig.getReturnUrl(), alipayConfig.getNotifyUrl(), null);
                  }
                  return body;
              } catch (Exception e) {
                  log.error(e.getMessage());
                  throw new RuntimeException("创建支付交易失败");
              }
          }
      
          /**
           * @param tradeNum 商户网站唯一订单号(out_trade_no) 或 支付宝交易号(trade_no)
           * @throws Exception
           * @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivbt
           */
          @ApiOperation(value = "统一查询订单", notes = "统一查询订单")
          @ApiOperationSupport(order = 2)
          @GetMapping("/query/{tradeNum}")
          @ResponseBody
          public Object query(@PathVariable("tradeNum") String tradeNum) throws Exception {
              //创建API对应的request类
              AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
              JSONObject bizContent = new JSONObject();
              // 商户网站唯一订单号
              bizContent.put("out_trade_no", tradeNum);
              // 支付宝交易号
              //bizContent.put("trade_no", tradeNum);
      
              /*
               * 指定接口同步响应额外返回的信息字段,数组格式
               * trade_settle_info:返回的交易结算信息,包含分账、补差等信息;
               * fund_bill_list:交易支付使用的资金渠道;
               * voucher_detail_list:交易支付时使用的所有优惠券信息;
               * discount_goods_detail:交易支付所使用的单品券优惠的商品优惠信息;
               * mdiscount_amount:商家优惠金额;
               */
              //  bizContent.put("query_options", []);
      
              request.setBizContent(bizContent.toString());
              AlipayTradeQueryResponse response = alipayClient.execute(request);
      
              // 打印结果
              log.debug(response.getBody());
      
              // 根据response中的结果继续业务逻辑处理
              if (response.isSuccess()) {
                  log.info("调用支付宝订单查询成功,结果:{}", JSON.toJSONString(response));
                  log.info(response.getSubMsg());
                  /**
                   * 获取状态交易状态:
                   * WAIT_BUYER_PAY(交易创建,等待买家付款)
                   * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)
                   * TRADE_SUCCESS(交易支付成功)
                   * TRADE_FINISHED(交易结束,不可退款)
                   */
                  String tradeStatus = response.getTradeStatus();
                  log.info("交易状态:" + tradeStatus);
      
                  log.info("支付宝交易号:" + response.getTradeNo());
                  log.info("商家订单号:" + response.getOutTradeNo());
                  log.info("商家订单号:" + response.getOutTradeNo());
                  log.info("买家支付宝账号:" + response.getBuyerLogonId());
                  log.info("买家在支付宝的用户id:" + response.getBuyerUserId());
                  log.info("交易的订单金额:" + response.getTotalAmount());
                  // 入参的query_options中指定时才返回该字段信息
                  log.info("交易支付使用的资金渠道:" + response.getFundBillList());
                  log.info("商户店铺的名称:" + response.getStoreName());
      
              } else {
                  log.error("调用支付宝失败");
                  log.error(response.getSubCode());
                  log.error(response.getSubMsg());
              }
              return "调用查询订单";
          }
      
      
          /**
           * @param tradeNum 商户网站唯一订单号(out_trade_no) 或 支付宝交易号(trade_no)
           * @return
           * @throws Exception
           * @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivbu
           */
          @ApiOperation(value = "关闭订单", notes = "关闭订单")
          @ApiOperationSupport(order = 3)
          @PostMapping("/close/{tradeNum}")
          @ResponseBody
          public Object close(@PathVariable("tradeNum") String tradeNum) throws Exception {
              //创建API对应的request类
              AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();
              JSONObject bizContent = new JSONObject();
              // 商户网站唯一订单号
              bizContent.put("out_trade_no", tradeNum);
              // 支付宝交易号
              //bizContent.put("trade_no", tradeNum);
              // 商家操作员编号 id,由商家自定义
              bizContent.put("operator_id", "admin");
      
              request.setBizContent(bizContent.toString());
              AlipayTradeCloseResponse response = alipayClient.execute(request);
      
              // 打印结果
              log.debug(response.getBody());
      
              // 根据response中的结果继续业务逻辑处理
              if (response.isSuccess()) {
                  log.info("调用支付宝成功");
                  log.info(response.getSubMsg());
      
                  log.info("支付宝交易号:" + response.getTradeNo());
                  log.info("商家订单号:" + response.getOutTradeNo());
              } else {
                  log.error("调用支付宝失败");
                  log.error(response.getSubCode());
                  log.error(response.getSubMsg());
              }
              return "调用关闭订单";
          }
      
          /**
           * @return
           * @throws Exception 同一笔交易的退款至少间隔3s后发起
           * @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivbx
           */
          @ApiOperation(value = "订单退款", notes = "订单退款")
          @ApiOperationSupport(order = 3)
          @GetMapping("/refund/{tradeNum}")
          @ResponseBody
          public Object refund(@PathVariable("tradeNum") String tradeNum) throws Exception {
              //创建API对应的request类
              AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
              JSONObject bizContent = new JSONObject();
              // 商户网站唯一订单号
              bizContent.put("out_trade_no", tradeNum);
              // 支付宝交易号
      //        bizContent.put("trade_no", "2021081722001419121412730660");
              // 退款金额
              bizContent.put("refund_amount", 0.01);
              // 退款原因
              bizContent.put("refund_reason", "申请退款");
      
              // 退款请求号,标识一次退款请求,需要保证在交易号下唯一,如需部分退款,则此参数必传。
              // 针对同一次退款请求,如果调用接口失败或异常了,重试时需要保证退款请求号不能变更。
              bizContent.put("out_request_no", "1122334455");
      
              request.setBizContent(bizContent.toString());
              AlipayTradeRefundResponse response = alipayClient.execute(request);
      
              // 打印结果
              log.debug(response.getBody());
      
              // 根据response中的结果继续业务逻辑处理
              if (response.isSuccess()) {
                  log.info("调用支付宝成功");
                  log.info(response.getSubMsg());
      
                  /**
                   * 本次退款是否发生了资金变化
                   * Y 表示退款成功
                   */
                  log.info("是否发生了资金变化:" + response.getFundChange());
                  log.info("支付宝交易号:" + response.getTradeNo());
                  log.info("商家订单号:" + response.getOutTradeNo());
                  log.info("已退款的总金额:" + response.getRefundFee());
                  log.info("买家支付宝账号:" + response.getBuyerLogonId());
                  log.info("买家在支付宝的用户id:" + response.getBuyerUserId());
                  log.info("买家在支付宝的用户id:" + response.getBuyerUserId());
              } else {
                  log.error("调用支付宝失败");
                  log.error(response.getSubCode());
                  log.error(response.getSubMsg());
              }
              return "调用订单退款";
          }
      
          /**
           * @return
           * @throws Exception
           * @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivbv
           */
          @ApiOperation(value = "订单退款查询", notes = "订单退款查询")
          @ApiOperationSupport(order = 4)
          @GetMapping("/refund/query/{tradeNum}")
          @ResponseBody
          public Object refundQuery(@PathVariable("tradeNum") String tradeNum) throws Exception {
              //创建API对应的request类
              AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest();
              JSONObject bizContent = new JSONObject();
              // 商户网站唯一订单号
              bizContent.put("out_trade_no", tradeNum);
              // 支付宝交易号
              // bizContent.put("trade_no", "2021081722001419121412730660");
              // 退款请求号,标识一次退款请求,需要保证在交易号下唯一,如需部分退款,则此参数必传。
              // 针对同一次退款请求,如果调用接口失败或异常了,重试时需要保证退款请求号不能变更。
              bizContent.put("out_request_no", "1122334455");
      
              /**
               * 商户通过上送该参数来定制同步需要额外返回的信息字段,数组格式。
               * refund_detail_item_list:退款使用的资金渠道;
               * deposit_back_info:触发银行卡冲退信息通知,设置该类型后会回调收单退款冲退通知;
               */
              // bizContent.put("query_options", []);
      
              request.setBizContent(bizContent.toString());
              AlipayTradeFastpayRefundQueryResponse response = alipayClient.execute(request);
      
              // 打印结果
              log.debug(response.getBody());
      
              // 根据response中的结果继续业务逻辑处理
              if (response.isSuccess()) {
                  log.info("调用支付宝成功");
                  log.info(response.getSubMsg());
      
                  /**
                   * 退款状态
                   * REFUND_SUCCESS 退款处理成功;
                   * 未返回该字段表示退款请求未收到或者退款失败;
                   * 注:建议商户在退款发起后间隔10秒以上再发起退款查询请求。
                   */
                  String refundStatus = response.getRefundStatus();
      
                  log.info("支付宝交易号:" + response.getTradeNo());
                  log.info("商家订单号:" + response.getOutTradeNo());
                  log.info("本笔退款对应的退款请求号:" + response.getOutRequestNo());
                  log.info("该笔退款所对应的交易的订单金额:" + response.getTotalAmount());
                  log.info("本次退款请求,对应的退款金额:" + response.getRefundAmount());
                  log.info("退分账明细信息:" + response.getRefundRoyaltys());
                  log.info("银行卡冲退信息:" + response.getDepositBackInfo());
              } else {
                  log.error("调用支付宝失败");
                  log.error(response.getSubCode());
                  log.error(response.getSubMsg());
              }
              return "调用订单退款查询";
          }
      
      
          /**
           * @return
           * @throws Exception
           * @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivbw
           */
          @ApiOperation(value = "查询对账单下载地址", notes = "查询对账单下载地址")
          @ApiOperationSupport(order = 5)
          @GetMapping("/bill")
          @ResponseBody
          public Object bill() throws Exception {
              //创建API对应的request类
              AlipayDataDataserviceBillDownloadurlQueryRequest request = new AlipayDataDataserviceBillDownloadurlQueryRequest();
              JSONObject bizContent = new JSONObject();
              /*
               * 账单类型,通过接口可以获取以下账单类型,支持:
               * trade:商户基于支付宝交易收单的业务账单;
               * signcustomer:基于商户支付宝余额收入及支出等资金变动的账务账单。
               */
              bizContent.put("bill_type", "trade");
              /*
               * 账单时间:
               * 日账单格式为yyyy-MM-dd,最早可下载2016年1月1日开始的日账单;
               * 月账单格式为yyyy-MM,最早可下载2016年1月开始的月账单。
               */
              bizContent.put("bill_date", "2022-07-10");
      
              request.setBizContent(bizContent.toString());
              AlipayDataDataserviceBillDownloadurlQueryResponse response = alipayClient.execute(request);
      
              // 打印结果
              log.debug(response.getBody());
      
              // 根据response中的结果继续业务逻辑处理
              if (response.isSuccess()) {
                  log.info("调用支付宝成功");
                  log.info(response.getSubMsg());
      
                  log.info("账单地址:" + response.getBillDownloadUrl());
                  return response.getBillDownloadUrl();
              } else {
                  log.error("调用支付宝失败");
                  log.error(response.getSubCode());
                  log.error(response.getSubMsg());
              }
              return "调用查询对账单下载地址";
          }
      
      
          /**
           * @return
           * @throws Exception
           * @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivby
           */
          @ApiOperation(value = "收单退款冲退", notes = "收单退款冲退")
          @ApiOperationSupport(order = 99)
          @GetMapping("/completed")
          @ResponseBody
          public void completed(HttpServletRequest request, HttpServletResponse response) throws Exception {
              log.info("收单退款冲退");
              PrintWriter out = response.getWriter();
              //乱码解决,这段代码在出现乱码时使用
              request.setCharacterEncoding("utf-8");
      
              try {
                  //获取支付宝GET过来反馈信息
                  Map<String, String> params = new HashMap<>(8);
                  Map<String, String[]> requestParams = request.getParameterMap();
                  for (Map.Entry<String, String[]> stringEntry : requestParams.entrySet()) {
                      String[] values = stringEntry.getValue();
                      String valueStr = "";
                      for (int i = 0; i < values.length; i++) {
                          valueStr = (i == values.length - 1) ? valueStr + values[i]
                                  : valueStr + values[i] + ",";
                      }
                      params.put(stringEntry.getKey(), valueStr);
                  }
      
                  // 比如:解析到的biz_content内容如下,此处代码因为无法通过银行卡退款测试,所以模拟已经解析除了biz_content值
                  // biz_content={"trade_no":"2022021922001486510501693053","out_trade_no":"1645257277192","out_request_no":"HZ01RF001","dback_status":"S","dback_amount":"1.01","bank_ack_time":"2020-06-02 14:03:48","est_bank_receipt_time":"2020-06-02 14:03:48"}
                  String str = "{\"trade_no\":\"2022021922001486510501693053\",\"out_trade_no\":\"1645257277192\",\"out_request_no\":\"HZ01RF001\",\"dback_status\":\"S\",\"dback_amount\":\"1.01\",\"bank_ack_time\":\"2020-06-02 14:03:48\",\"est_bank_receipt_time\":\"2020-06-02 14:03:48\"}";
      
                  // 将biz_content值转为Map对象
                  Map map = JSON.parseObject(str, Map.class);
      
                  log.info("支付宝交易号:" + map.get("trade_no"));
                  log.info("商家订单号:" + map.get("out_trade_no"));
                  log.info("退款请求号:" + map.get("out_request_no"));
                  // 银行卡冲退状态。S-成功,F-失败。银行卡冲退失败,资金自动转入用户支付宝余额
                  log.info("银行卡冲退状态:" + map.get("dback_status"));
                  log.info("银行卡冲退金额:" + map.get("dback_amount"));
                  log.info("银行响应时间:" + map.get("bank_ack_time"));
                  log.info("预估银行入账时间:" + map.get("est_bank_receipt_time"));
                  out.print("success");
              } catch (Exception e) {
                  out.print("fail");
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
      • 97
      • 98
      • 99
      • 100
      • 101
      • 102
      • 103
      • 104
      • 105
      • 106
      • 107
      • 108
      • 109
      • 110
      • 111
      • 112
      • 113
      • 114
      • 115
      • 116
      • 117
      • 118
      • 119
      • 120
      • 121
      • 122
      • 123
      • 124
      • 125
      • 126
      • 127
      • 128
      • 129
      • 130
      • 131
      • 132
      • 133
      • 134
      • 135
      • 136
      • 137
      • 138
      • 139
      • 140
      • 141
      • 142
      • 143
      • 144
      • 145
      • 146
      • 147
      • 148
      • 149
      • 150
      • 151
      • 152
      • 153
      • 154
      • 155
      • 156
      • 157
      • 158
      • 159
      • 160
      • 161
      • 162
      • 163
      • 164
      • 165
      • 166
      • 167
      • 168
      • 169
      • 170
      • 171
      • 172
      • 173
      • 174
      • 175
      • 176
      • 177
      • 178
      • 179
      • 180
      • 181
      • 182
      • 183
      • 184
      • 185
      • 186
      • 187
      • 188
      • 189
      • 190
      • 191
      • 192
      • 193
      • 194
      • 195
      • 196
      • 197
      • 198
      • 199
      • 200
      • 201
      • 202
      • 203
      • 204
      • 205
      • 206
      • 207
      • 208
      • 209
      • 210
      • 211
      • 212
      • 213
      • 214
      • 215
      • 216
      • 217
      • 218
      • 219
      • 220
      • 221
      • 222
      • 223
      • 224
      • 225
      • 226
      • 227
      • 228
      • 229
      • 230
      • 231
      • 232
      • 233
      • 234
      • 235
      • 236
      • 237
      • 238
      • 239
      • 240
      • 241
      • 242
      • 243
      • 244
      • 245
      • 246
      • 247
      • 248
      • 249
      • 250
      • 251
      • 252
      • 253
      • 254
      • 255
      • 256
      • 257
      • 258
      • 259
      • 260
      • 261
      • 262
      • 263
      • 264
      • 265
      • 266
      • 267
      • 268
      • 269
      • 270
      • 271
      • 272
      • 273
      • 274
      • 275
      • 276
      • 277
      • 278
      • 279
      • 280
      • 281
      • 282
      • 283
      • 284
      • 285
      • 286
      • 287
      • 288
      • 289
      • 290
      • 291
      • 292
      • 293
      • 294
      • 295
      • 296
      • 297
      • 298
      • 299
      • 300
      • 301
      • 302
      • 303
      • 304
      • 305
      • 306
      • 307
      • 308
      • 309
      • 310
      • 311
      • 312
      • 313
      • 314
      • 315
      • 316
      • 317
      • 318
      • 319
      • 320
      • 321
      • 322
      • 323
      • 324
      • 325
      • 326
      • 327
      • 328
      • 329
      • 330
      • 331
      • 332
      • 333
      • 334
      • 335
      • 336
      • 337
      • 338
      • 339
      • 340
      • 341
      • 342
      • 343
      • 344
      • 345
      • 346
      • 347
      • 348
      • 349
      • 350
      • 351
      • 352
      • 353
      • 354
      • 355
      • 356
      • 357
      • 358
      • 359
      • 360
      • 361
      • 362
      • 363
      • 364
      • 365
      • 366
      • 367
      • 368
      • 369
      • 370
      • 371
      • 372
      • 373
      • 374
      • 375
      • 376
      • 377
      • 378
      • 379
      • 380
      • 381
      • 382
      • 383
      • 384
      • 385
      • 386
      • 387
      • 388
      • 389
      • 390
      • 391
      • 392
      • 393
      • 394
      • 395
      • 396
      • 397
      • 398
      • 399
      • 400
      • 401
      • 402
      • 403
      • 404
      • 405
      • 406
      • 407
      • 408
      • 409
      • 410
      • 411
      • 412
      • 413
      • 414
      • 415
      • 416
      • 417
      • 418
      • 419
      • 420
      • 421
      • 422
      • 423
      • 424
      • 425
      • 426
      • 427
      • 428
      • 429
      • 430
      • 431
      • 432
      • 433
      • 434
      • 435
      • 436
      • 437
      • 438
      • 439
      • 440
      • 441
      • 442
      • 443
      • 444
      • 445
      • 446
      • 447
      • 448
      • 449
      • 450
      • 451
      • 452
      • 453
      • 454
      • 455
      • 456
      • 457
      • 458
      • 459
      • 460
      • 461
      • 462
      • 463
      • 464
      • 465
      • 466
      • 467
      • 468
      • 469
      • 470
      • 471
      • 472
      • 473
      • 474
      • 475
      • 476
      • 477
      • 478
      • 479
      • 480
      • 481
      • 482
      • 483
      • 484
      • 485
      • 486
      • 487
      • 488
      • 489
      • 490
      • 491
      • 492
      • 493
      • 494
      • 495
      • 496
      • 497
      • 498
      • 499
      • 500
      • 501
      • 502
      • 503
      • 504
      • 505
      • 506
      • 507
      • 508
      • 509
      • 510
      • 511
      • 512
      • 513
      • 514
      • 515
      • 516
      • 517

      8、回调接口

      NotifyController :

      
      import com.alipay.api.AlipayConstants;
      import com.alipay.api.internal.util.AlipaySignature;
      import com.lhz.config.AlipayConfig;
      import io.swagger.annotations.Api;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      import javax.annotation.Resource;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.io.IOException;
      import java.io.PrintWriter;
      import java.nio.charset.StandardCharsets;
      import java.util.HashMap;
      import java.util.Map;
      import java.util.concurrent.locks.ReentrantLock;
      
      /**
       * @Author: 
       * @Description:
       **/
      @Api(tags = "回调接口(API3)")
      @RestController
      @Slf4j
      public class NotifyController {
      
          private final ReentrantLock lock = new ReentrantLock();
      
          @Resource
          private AlipayConfig alipayConfig;
      
          /**
           * 服务器异步通知页面路径,接收支付宝回调后,再封装页面数据,直接返回相应页面到前端
           * 支付、退款都会通过此回调接口
           *
           * @param request
           * @param response
           * @throws IOException
           * @deprecated API地址:https://opendocs.alipay.com/open/203/105286
           */
          @PostMapping("/notify")
          public void notifyUrl(HttpServletRequest request, HttpServletResponse response) throws Exception {
              log.info("异步通知");
              PrintWriter out = response.getWriter();
              if (lock.tryLock()) {
                  try {
                      //乱码解决,这段代码在出现乱码时使用
                      request.setCharacterEncoding("utf-8");
                      //获取支付宝POST过来反馈信息
                      Map<String, String> params = new HashMap<>(8);
                      Map<String, String[]> requestParams = request.getParameterMap();
                      for (Map.Entry<String, String[]> stringEntry : requestParams.entrySet()) {
                          String[] values = stringEntry.getValue();
                          String valueStr = "";
                          for (int i = 0; i < values.length; i++) {
                              valueStr = (i == values.length - 1) ? valueStr + values[i]
                                      : valueStr + values[i] + ",";
                          }
                          params.put(stringEntry.getKey(), valueStr);
                      }
      
                      //调用SDK验证签名
                      boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayConfig.getPublicKey(), AlipayConstants.CHARSET_UTF8, AlipayConstants.SIGN_TYPE_RSA2);
      
                      if (!signVerified) {
                          log.error("验签失败");
                          out.print("fail");
                          return;
                      }
      
                      log.warn("=========== 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱 ===========");
      
                      // 通知ID
                      String notifyId = new String(params.get("notify_id").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
                      // 通知时间
                      String notifyTime = new String(params.get("notify_time").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
                      //商户订单号,之前生成的带用户ID的订单号
                      String outTradeNo = new String(params.get("out_trade_no").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
                      //支付宝交易号
                      String tradeNo = new String(params.get("trade_no").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
                      //付款金额
                      String totalAmount = new String(params.get("total_amount").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
                      //交易状态
                      String tradeStatus = new String(params.get("trade_status").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
      
                      /*
                       * 交易状态
                       * TRADE_SUCCESS 交易完成
                       * TRADE_FINISHED 支付成功
                       * WAIT_BUYER_PAY 交易创建
                       * TRADE_CLOSED 交易关闭
                       */
                      log.info("tradeStatus:" + tradeStatus);
      
                      if ("TRADE_FINISHED".equals(tradeStatus)) {
                          /*此处可自由发挥*/
                          //判断该笔订单是否在商户网站中已经做过处理
                          //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
                          //如果有做过处理,不执行商户的业务程序
                          //注意:
                          //退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
                      } else if ("TRADE_SUCCESS".equals(tradeStatus)) {
                          // TODO 根据订单号,做幂等处理,并且在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
                          log.warn("=========== 根据订单号,做幂等处理 ===========");
      
                          //判断该笔订单是否在商户网站中已经做过处理
                          //如果没有做过处理,根据订单号(out_trade_no在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
                          //如果有做过处理,不执行商户的业务程序
      
                          // 此处代表交易已经成果,编写实际页面代码
                          // 比如:重置成功,那么往数据库中写入重置金额
                          log.info("notifyId:" + notifyId);
                          log.info("notifyTime:" + notifyTime);
                          log.info("trade_no:" + tradeNo);
                          log.info("outTradeNo:" + outTradeNo);
                          log.info("totalAmount:" + totalAmount);
                      }
                  } finally {
                      //要主动释放锁
                      lock.unlock();
                  }
              }
              out.print("success");
      
          }
      
          /**
           * 完成支付后的同步通知
           * 页面跳转同步通知页面路径,接收支付宝回调后,再封装页面数据,直接返回相应页面到前端
           *
           * @param request
           * @param response
           * @throws IOException
           */
          @GetMapping("/return")
          public String returnUrl(HttpServletRequest request, HttpServletResponse response) throws Exception {
              log.info("同步通知");
      
              //乱码解决,这段代码在出现乱码时使用
              request.setCharacterEncoding("utf-8");
      
              //获取支付宝GET过来反馈信息
              Map<String, String> params = new HashMap<>(8);
              Map<String, String[]> requestParams = request.getParameterMap();
              for (Map.Entry<String, String[]> stringEntry : requestParams.entrySet()) {
                  String[] values = stringEntry.getValue();
                  String valueStr = "";
                  for (int i = 0; i < values.length; i++) {
                      valueStr = (i == values.length - 1) ? valueStr + values[i]
                              : valueStr + values[i] + ",";
                  }
                  params.put(stringEntry.getKey(), valueStr);
              }
      
              // 通知ID
              String notifyId = new String(params.get("notify_id").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
              // 通知时间
              String notifyTime = new String(params.get("notify_time").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
              //商户订单号,之前生成的带用户ID的订单号
              String outTradeNo = new String(params.get("out_trade_no").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
              //支付宝交易号
              String tradeNo = new String(params.get("trade_no").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
              //付款金额
              String totalAmount = new String(params.get("total_amount").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
      
              log.info("notifyId:" + notifyId);
              log.info("notifyTime:" + notifyTime);
              log.info("trade_no:" + tradeNo);
              log.info("outTradeNo:" + outTradeNo);
              log.info("totalAmount:" + totalAmount);
      
              //调用SDK验证签名
              boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayConfig.getPublicKey(), AlipayConstants.CHARSET_UTF8, AlipayConstants.SIGN_TYPE_RSA2);
      
              if (signVerified) {
                  System.out.println("验签成功-跳转到成功后页面");
                  //跳转至支付成功后的页面,
                  return "redirect:https://baidu.com";
              } else {
                  System.out.println("验签失败-跳转到充值页面让用户重新充值");
                  return "redirect:https://google.com";
              }
          }
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
      • 97
      • 98
      • 99
      • 100
      • 101
      • 102
      • 103
      • 104
      • 105
      • 106
      • 107
      • 108
      • 109
      • 110
      • 111
      • 112
      • 113
      • 114
      • 115
      • 116
      • 117
      • 118
      • 119
      • 120
      • 121
      • 122
      • 123
      • 124
      • 125
      • 126
      • 127
      • 128
      • 129
      • 130
      • 131
      • 132
      • 133
      • 134
      • 135
      • 136
      • 137
      • 138
      • 139
      • 140
      • 141
      • 142
      • 143
      • 144
      • 145
      • 146
      • 147
      • 148
      • 149
      • 150
      • 151
      • 152
      • 153
      • 154
      • 155
      • 156
      • 157
      • 158
      • 159
      • 160
      • 161
      • 162
      • 163
      • 164
      • 165
      • 166
      • 167
      • 168
      • 169
      • 170
      • 171
      • 172
      • 173
      • 174
      • 175
      • 176
      • 177
      • 178
      • 179
      • 180
      • 181
      • 182
      • 183
      • 184
      • 185
      • 186
      • 187
      • 188
    • 相关阅读:
      c++11~c++20 -06-字节对齐alignof、alignas
      每日一题 2530. 执行 K 次操作后的最大分数(中等,最大根堆)
      规则解读(三)| 本地资源检测 For Unreal
      上传ipa到appstore工具
      docker 部署 kafka-ui
      java计算机毕业设计风情旅游网站MyBatis+系统+LW文档+源码+调试部署
      初探富文本之基于虚拟滚动的大型文档性能优化方案
      公钥、私钥、证书、加密、解密、加签、验签
      Win10系统如何关闭防火墙?
      车规级MCU进入「新周期」,中国本土供应商竞逐竞争力TOP10
    • 原文地址:https://blog.csdn.net/zhuocailing3390/article/details/125837799