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
为商家订单号
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
为商家订单号
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
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
访问返回的查询地址,即可下载查询账单
压缩包,内容如下:
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 同步、异步通知区别
-
同步通知(return_url ):
用于用户在支付宝页面付款成功后自动跳转回你自己的网址(同步通知是展示给用户看的)
, 你根据回传的参数告诉用户已经支付成功,会返回相应的页面,回调可以是后端的接口,也可以是具体的前端页面地址; -
异步通知(notify_url ):
其实是双保险机制, 如果同步通知后没有跳转到你的网址, 可能用户关了, 可能网速慢, 即无法触发你更新订单状态为已支付的controller;
这时候异步通知就有作用了, 不过你要判断一下, 如果订单已经变为已支付, 则不必再更新一次了, 只返回给支付success即可, 否则他会一直异步通知你,在异步通知中如果为支付成功,则可以更新数据库中的数据; -
return_url 与 notify_url 的区别:
- return_url: 买家付款成功后,如果接口中指定有return_url ,买家付完款后会跳到 return_url所在的页面,这个页面可以展示给客户看,这个页面只有付款成功才会跳转;
- notify_url: 服务器后台收到通知支付宝会调用notify_url这个接口,这个接口根据支付宝传递过来的参数,如果为支付成功,则修改网站订单的状态等数据,需要返回success给支付宝,如果反馈给支付宝的不是success,支付宝会继续调用这个页面;
5、源码参考
源码地址:https://gitee.com/lhzlx/alipay-boot.git