> 文档中心 > SpringBoot实现支付宝H5(移动端)支付(整合内网穿透进行测试)

SpringBoot实现支付宝H5(移动端)支付(整合内网穿透进行测试)

目录

  • 1、支付宝流程介绍
  • 2、接口交互图
  • 3、环境准备
    • 3.1 内网穿透配置
    • 3.2 沙箱环境配置
  • 4、SpringBoot整合
    • 4.1 建立工程
    • 4.2 统一下单接口
    • 4.3 查询订单接口
    • 4.4 关闭订单接口
    • 4.5 退款订单接口
    • 4.6 退款查询接口
    • 4.7 收单退款冲退接口
    • 4.8 查询对账单接口
    • 4.9 交易异步通知
    • 4.10 交易同步通知
    • 4.11 同步、异步通知区别
  • 5、源码参考

1、支付宝流程介绍

商家在网页应用中调用支付宝提供的网页支付接口,接口会调起支付宝客户端内的支付模块,此时会从商家网页应用跳转到支付宝客户端中并开始支付;支付完成后会跳转回商家网页应用内,最后商家展示支付结果。

步骤1:用户在浏览器中访问商家网页应用,选择商品下单、确认购买,进入支付环节,选择支付宝付款,用户点击去支付,如下图1;
步骤2:进入到支付宝支付路由页面,支付宝处理支付请求,并尝试唤起支付宝客户端,如下图2;
步骤3:进入到支付宝页面,调起支付宝客户端支付,出现确认支付界面,如下图3;
在这里插入图片描述
步骤4:用户确认收款方和金额,点击立即支付后出现输入密码界面,如下图4;
步骤5:输入正确密码后,支付宝端显示支付结果,如下图5;
步骤6:自动回跳到浏览器中,商家根据付款结果个性化展示订单处理结果,如下图6。
在这里插入图片描述

2、接口交互图

手机网站支付接入详细参见:https://docs.open.alipay.com/203/105285/

在这里插入图片描述

3、环境准备

为了在本地测试支付宝支付流程,我们需要继续内网穿透以及一个支付宝账户,如果没有实际的商家账户,可以采用支付宝提供沙箱环境

3.1 内网穿透配置

采用natapp实现内网穿透,参考《服务器内网穿透实现》

3.2 沙箱环境配置

登录支付宝开发者中心:https://openhome.alipay.com/platform/appDaily.htm

  • 获取APPID:20210xxxxx152
  • 获取商户PID:20886xxxx4796
    在这里插入图片描述
  • 获取应用私钥和支付宝公钥
    在这里插入图片描述
  • 获取沙箱登录账户、密码
    在这里插入图片描述

4、SpringBoot整合

官方流程-参考文档:https://opendocs.alipay.com/open/203/105285
官方流程-参考API:https://opendocs.alipay.com/open/02ivbs?scene=21

4.1 建立工程

  • 配置pom:

    SDK Maven参考:https://repo1.maven.org/maven2/com/alipay/sdk/alipay-sdk-java/

     <dependency>     <groupId>com.alipay.sdk</groupId>     <artifactId>alipay-sdk-java</artifactId>     <version>4.13.50.ALL</version> </dependency>  <dependency>     <groupId>commons-logging</groupId>     <artifactId>commons-logging</artifactId>     <version>1.2</version> </dependency>
  • 配置yml:
    #设置公用内容server:  port: 9090  servlet:    context-path: /alipayspring:  application:    name: alipayalipay:  # appid  app_id:   # 应用私钥  rsa_private_key:   # 服务器异步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问  notify_url: http://g74zxj.natappfree.cc/alipay/notify  # 页面跳转同步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问  return_url: http://g74zxj.natappfree.cc/alipay/return  # 请求网关地址  # 正式为:"https://openapi.alipay.com/gateway.do"  url: https://openapi.alipaydev.com/gateway.do  # 编码  charset: UTF-8  # 返回格式  format: json  # 支付宝公钥  alipay_public_key:   # RSA2  signtype: RSA2
  • AlipayConfig
    @Configuration@Datapublic class AlipayConfig {    @Value("${alipay.app_id}")    private String appId;    @Value("${alipay.rsa_private_key}")    private String rsaPrivateKey;    @Value("${alipay.url}")    private String url;    @Value("${alipay.charset}")    private String charset;    @Value("${alipay.format}")    private String format;    @Value("${alipay.alipay_public_key}")    private String alipayPublicKey;    @Value("${alipay.sign_type}")    private String signType;    /     * 获得初始化的AlipayClient     *     * @return     */    @Bean    public AlipayClient alipayClient() { // 获得初始化的AlipayClient return new DefaultAlipayClient(url, appId, rsaPrivateKey, format, charset, alipayPublicKey, signType);    }}

4.2 统一下单接口

说明:

前端通过请求该后端接口,后端请求支付宝接口获取SDK生成表单,后端将生成的表单封装好后返回给前端;

调用流程:
在这里插入图片描述

参考API:
https://opendocs.alipay.com/open/02ivbs?scene=21

代码实现:

@Controller@Slf4jpublic class TestController {    private final String TRADE_FINISHED = "TRADE_FINISHED";    private final String TRADE_SUCCESS = "TRADE_SUCCESS";    @Value("${alipay.app_id}")    private String notifyUrl;    @Value("${alipay.app_id}")    private String returnUrl;    @Value("${alipay.charset}")    private String charset;    @Value("${alipay.alipay_public_key}")    private String alipayPublicKey;    @Value("${alipay.sign_type}")    private String signType;    @Resource    private AlipayClient alipayClient; /     * 下单交易接口     *     * @param httpRequest     * @param httpResponse     * @throws IOException     * @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivbs?scene=21     */    @GetMapping("/pay")    public void pay(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws Exception { long outTradeNo = System.currentTimeMillis(); log.info("支付订单,订单号:" + outTradeNo); // 相应请求API的request AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest(); // 在公共参数中设置回跳和通知地址 alipayRequest.setReturnUrl(returnUrl); alipayRequest.setNotifyUrl(notifyUrl); // 封装商品信息 JSONObject bizContent = new JSONObject(); bizContent.put("out_trade_no", outTradeNo); bizContent.put("total_amount", 0.01); bizContent.put("subject", "测试商品"); // 沙箱默认值为:QUICK_WAP_WAY bizContent.put("product_code", "QUICK_WAP_WAY"); //bizContent.put("time_expire", "2022-08-01 22:00:00");  商品明细信息,按需传入 //JSONArray goodsDetail = new JSONArray(); //JSONObject goods1 = new JSONObject(); //goods1.put("goods_id", "goodsNo1"); //goods1.put("goods_name", "子商品1"); //goods1.put("quantity", 1); //goods1.put("price", 0.01); //goodsDetail.add(goods1); //bizContent.put("goods_detail", goodsDetail);  扩展信息,按需传入 //JSONObject extendParams = new JSONObject(); //extendParams.put("sys_service_provider_id", "2088511833207846"); //bizContent.put("extend_params", extendParams); alipayRequest.setBizContent(bizContent.toString()); //调用SDK生成表单 String form = ""; try {     form = alipayClient.pageExecute(alipayRequest).getBody(); } catch (AlipayApiException e) {     e.printStackTrace(); } httpResponse.setContentType("text/html;charset=" + charset); //直接将完整的表单html输出到页面 httpResponse.getWriter().write(form); httpResponse.getWriter().flush(); httpResponse.getWriter().close();    }}

演示截图:
通过手机或者PC浏览器访问:http://g74zxj.natappfree.cc/alipay/pay;由于是沙箱环境,所以不能使用正式的支付宝APP进行支付,我们直接使用浏览器登录支付测试,当发布正式环境后,就可以使用正式支付宝APP进行支付。

步骤如下:
在这里插入图片描述

4.3 查询订单接口

说明:
该接口提供所有支付宝支付订单的查询,商户可以通过该接口主动查询订单状态,完成下一步的业务逻辑。

前端通过请求该后端接口,并且传入商户网站唯一订单号(out_trade_no) 或支付宝交易号(trade_no),查询订单的内容。

调用流程:
在这里插入图片描述

参考API:
https://opendocs.alipay.com/open/02ivbt

代码实现:

@Controller@Slf4jpublic class TestController {    private final String TRADE_FINISHED = "TRADE_FINISHED";    private final String TRADE_SUCCESS = "TRADE_SUCCESS";    @Value("${alipay.app_id}")    private String notifyUrl;    @Value("${alipay.app_id}")    private String returnUrl;    @Value("${alipay.charset}")    private String charset;    @Value("${alipay.alipay_public_key}")    private String alipayPublicKey;    @Value("${alipay.sign_type}")    private String signType;    @Resource    private AlipayClient alipayClient; /     * @param tradeNum 商户网站唯一订单号(out_trade_no) 或 支付宝交易号(trade_no)     * @throws Exception     * @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivbt     */    @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); request.setBizContent(bizContent.toString()); AlipayTradeQueryResponse response = alipayClient.execute(request); // 打印结果 log.debug(response.getBody()); // 根据response中的结果继续业务逻辑处理 if (response.isSuccess()) {     log.info("调用支付宝成功");     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.getBuyerLogonId());     log.info("买家在支付宝的用户id:" + response.getBuyerUserId());     log.info("买家在支付宝的用户id:" + response.getBuyerUserId()); } else {     log.error("调用支付宝失败");     log.error(response.getSubCode());     log.error(response.getSubMsg()); } return "调用查询订单";    }}

演示截图:
通过手机或者PC浏览器访问:http://pvxpis.natappfree.cc/alipay/query/1645257277192,其中1645257277192商家订单号
SpringBoot实现支付宝H5(移动端)支付(整合内网穿透进行测试)

4.4 关闭订单接口

说明:
用于交易创建后,用户在一定时间内未进行支付,可调用该接口直接将未付款的交易进行关闭。

前端通过请求该后端接口,并且传入商户网站唯一订单号(out_trade_no) 或支付宝交易号(trade_no),进行取消;

调用流程:
在这里插入图片描述

参考API:
https://opendocs.alipay.com/open/02ivbu

代码实现:

@Controller@Slf4jpublic class TestController {    private final String TRADE_FINISHED = "TRADE_FINISHED";    private final String TRADE_SUCCESS = "TRADE_SUCCESS";    @Value("${alipay.app_id}")    private String notifyUrl;    @Value("${alipay.app_id}")    private String returnUrl;    @Value("${alipay.charset}")    private String charset;    @Value("${alipay.alipay_public_key}")    private String alipayPublicKey;    @Value("${alipay.sign_type}")    private String signType;    @Resource    private AlipayClient alipayClient; /     * @param tradeNum 商户网站唯一订单号(out_trade_no) 或 支付宝交易号(trade_no)     * @return     * @throws Exception     * @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivbu     */    @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); 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 "调用关闭订单";    }}

演示截图:
通过手机或者PC浏览器访问:http://pvxpis.natappfree.cc/alipay/close/1645257277192,其中1645257277192商家订单号

调用接口查询的商家订单号,交易状态必须是WAIT_BUYER_PAY(交易创建,等待买家付款)否则会报错,如下:
在这里插入图片描述

4.5 退款订单接口

说明:
当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,支付宝将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。

同一笔交易的退款至少间隔3s后发起。

交易超过约定时间(签约时设置的可退款时间)的订单无法进行退款。

支付宝退款支持单笔交易分多次退款,多次退款需要提交原支付订单的订单号和设置不同的退款请求号。一笔退款失败后重新提交,要保证重试时退款请求号不能变更,防止该笔交易重复退款。

同一笔交易累计提交的退款金额不能超过原始交易总金额。

退款是否成功可以根据响应的 fund_change 参数来判断,返回值为 Y 则表示退款成功。

调用流程:
在这里插入图片描述

参考API:
https://opendocs.alipay.com/open/02ivbx

代码实现:

@Controller@Slf4jpublic class TestController {    private final String TRADE_FINISHED = "TRADE_FINISHED";    private final String TRADE_SUCCESS = "TRADE_SUCCESS";    @Value("${alipay.app_id}")    private String notifyUrl;    @Value("${alipay.app_id}")    private String returnUrl;    @Value("${alipay.charset}")    private String charset;    @Value("${alipay.alipay_public_key}")    private String alipayPublicKey;    @Value("${alipay.sign_type}")    private String signType;    @Resource    private AlipayClient alipayClient; /     * @return     * @throws Exception     * @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivbx     */    @PostMapping("/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 表示退款成功      */     String fundChange = 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 "调用订单退款";    }}

演示截图:
通过手机或者PC浏览器访问:http://pvxpis.natappfree.cc/alipay/refund/1645257277192,其中1645257277192商家订单号
在这里插入图片描述

4.6 退款查询接口

说明:
可使用该接口查询提交的退款请求是否执行成功。

当接口返回的refund_status值为REFUND_SUCCESS时表示退款成功,否则表示退款没有执行成功。

如果退款未成功,商户可以调用退款接口重试,重试时请务必保证退款请求号和退款金额一致,防止重复退款。

注:发起退款查询接口的时间不能离退款请求时间太短,建议之间间隔10秒以上。

调用流程:
在这里插入图片描述

参考API:
https://opendocs.alipay.com/open/02ivbv

代码实现:

@Controller@Slf4jpublic class TestController {    private final String TRADE_FINISHED = "TRADE_FINISHED";    private final String TRADE_SUCCESS = "TRADE_SUCCESS";    @Value("${alipay.app_id}")    private String notifyUrl;    @Value("${alipay.app_id}")    private String returnUrl;    @Value("${alipay.charset}")    private String charset;    @Value("${alipay.alipay_public_key}")    private String alipayPublicKey;    @Value("${alipay.sign_type}")    private String signType;    @Resource    private AlipayClient alipayClient; /     * @return     * @throws Exception     * @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivbv     */    @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"); 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 "调用订单退款查询";    }}

演示截图:
通过手机或者PC浏览器访问:https://pvxpis.natappfree.cc/alipay/refund/query/1645257277192,其中1645257277192商家订单号
SpringBoot实现支付宝H5(移动端)支付(整合内网穿透进行测试)

4.7 收单退款冲退接口

说明:
退款存在退到银行卡场景下时,收单会根据银行回执消息发送退款完成信息。

参考API:
https://opendocs.alipay.com/open/02ivby

代码实现:

@Controller@Slf4jpublic class TestController {    private final String TRADE_FINISHED = "TRADE_FINISHED";    private final String TRADE_SUCCESS = "TRADE_SUCCESS";    @Value("${alipay.app_id}")    private String notifyUrl;    @Value("${alipay.app_id}")    private String returnUrl;    @Value("${alipay.charset}")    private String charset;    @Value("${alipay.alipay_public_key}")    private String alipayPublicKey;    @Value("${alipay.sign_type}")    private String signType;    @Resource    private AlipayClient alipayClient; /     * @return     * @throws Exception     * @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivby     */    @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"); }    }}

演示截图:
通过手机或者PC浏览器访问:http://pvxpis.natappfree.cc/alipay/completed
SpringBoot实现支付宝H5(移动端)支付(整合内网穿透进行测试)

4.8 查询对账单接口

说明:
为方便商户快速查账,支持商户通过本接口获取商户离线账单下载地址

调用流程:
在这里插入图片描述

参考API:
https://opendocs.alipay.com/open/02ivbw

代码实现:

@Controller@Slf4jpublic class TestController {    private final String TRADE_FINISHED = "TRADE_FINISHED";    private final String TRADE_SUCCESS = "TRADE_SUCCESS";    @Value("${alipay.app_id}")    private String notifyUrl;    @Value("${alipay.app_id}")    private String returnUrl;    @Value("${alipay.charset}")    private String charset;    @Value("${alipay.alipay_public_key}")    private String alipayPublicKey;    @Value("${alipay.sign_type}")    private String signType;    @Resource    private AlipayClient alipayClient; /     * @return     * @throws Exception     * @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivbw     */    @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", "2016-04-05"); 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 "调用查询对账单下载地址";    }}

演示截图:
通过手机或者PC浏览器访问:http://pvxpis.natappfree.cc/alipay/bill

在这里插入图片描述
访问返回的查询地址,即可下载查询账单压缩包,内容如下:
SpringBoot实现支付宝H5(移动端)支付(整合内网穿透进行测试)

4.9 交易异步通知

说明:
在唤起支付宝并且成功输入支付密码进行支付时,会异步调用交易异步通知交易异步通知接口。

参考API:
https://opendocs.alipay.com/open/203/105286

代码实现:

@Controller@Slf4jpublic class TestController {    private final String TRADE_FINISHED = "TRADE_FINISHED";    private final String TRADE_SUCCESS = "TRADE_SUCCESS";    @Value("${alipay.app_id}")    private String notifyUrl;    @Value("${alipay.app_id}")    private String returnUrl;    @Value("${alipay.charset}")    private String charset;    @Value("${alipay.alipay_public_key}")    private String alipayPublicKey;    @Value("${alipay.sign_type}")    private String signType;    @Resource    private AlipayClient alipayClient; /     * 服务器异步通知页面路径,接收支付宝回调后,再封装页面数据,直接返回相应页面到前端     *     * @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(); //乱码解决,这段代码在出现乱码时使用 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, alipayPublicKey, charset, signType); if (!signVerified) {     log.error("验签失败");     out.print("fail");     return; } //商户订单号,之前生成的带用户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 (tradeStatus.equals(TRADE_FINISHED)) {     /*此处可自由发挥*/     //判断该笔订单是否在商户网站中已经做过处理     //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序     //如果有做过处理,不执行商户的业务程序     //注意:     //退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知 } else if (tradeStatus.equals(TRADE_SUCCESS)) {     //判断该笔订单是否在商户网站中已经做过处理     //如果没有做过处理,根据订单号(out_trade_no在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序     //如果有做过处理,不执行商户的业务程序     // 此处代表交易已经成果,编写实际页面代码     // 比如:重置成功,那么往数据库中写入重置金额     log.info("trade_no:" + tradeNo);     log.info("outTradeNo:" + outTradeNo);     log.info("totalAmount:" + totalAmount); } out.print("success");    }}

4.10 交易同步通知

说明:

在唤起支付宝并且成功输入支付密码进行支付时,会先异步调用交易异步通知接口,当成功支付完成以后,可以直接调用同步回调接口,直接返回到指定的前端页面地址;

代码实现:

@Controller@Slf4jpublic class TestController {    private final String TRADE_FINISHED = "TRADE_FINISHED";    private final String TRADE_SUCCESS = "TRADE_SUCCESS";    @Value("${alipay.app_id}")    private String notifyUrl;    @Value("${alipay.app_id}")    private String returnUrl;    @Value("${alipay.charset}")    private String charset;    @Value("${alipay.alipay_public_key}")    private String alipayPublicKey;    @Value("${alipay.sign_type}")    private String signType;    @Resource    private AlipayClient alipayClient; /     * 完成支付后的同步通知     * 页面跳转同步通知页面路径,接收支付宝回调后,再封装页面数据,直接返回相应页面到前端     *     * @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 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("trade_no:" + tradeNo); log.info("outTradeNo:" + outTradeNo); log.info("totalAmount:" + totalAmount); //调用SDK验证签名 boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayPublicKey, charset, signType); if (signVerified) {     System.out.println("验签成功-跳转到成功后页面");     //跳转至支付成功后的页面,     return "redirect:payment"; } else {     System.out.println("验签失败-跳转到充值页面让用户重新充值");     return "redirect:false"; }    }}

4.11 同步、异步通知区别

  1. 同步通知(return_url ):
    用于用户在支付宝页面付款成功后自动跳转回你自己的网址(同步通知是展示给用户看的), 你根据回传的参数告诉用户已经支付成功,会返回相应的页面,回调可以是后端的接口,也可以是具体的前端页面地址;

  2. 异步通知(notify_url ):
    其实是双保险机制, 如果同步通知后没有跳转到你的网址, 可能用户关了, 可能网速慢, 即无法触发你更新订单状态为已支付的controller;
    这时候异步通知就有作用了, 不过你要判断一下, 如果订单已经变为已支付, 则不必再更新一次了, 只返回给支付success即可, 否则他会一直异步通知你,在异步通知中如果为支付成功,则可以更新数据库中的数据;

  3. return_url 与 notify_url 的区别:

    • return_url: 买家付款成功后,如果接口中指定有return_url ,买家付完款后会跳到 return_url所在的页面,这个页面可以展示给客户看,这个页面只有付款成功才会跳转;
    • notify_url: 服务器后台收到通知支付宝会调用notify_url这个接口,这个接口根据支付宝传递过来的参数,如果为支付成功,则修改网站订单的状态等数据,需要返回success给支付宝,如果反馈给支付宝的不是success,支付宝会继续调用这个页面;

5、源码参考

源码地址:https://gitee.com/lhzlx/alipay-boot.git