Alipay

1、支付宝支付介绍、如何接入

1.1、支付宝开放能力介绍

1.1.1、能⼒地图

        ⽀付能⼒、⽀付扩展、资⾦能⼒、⼝碑能⼒、营销能⼒、会员能⼒、⾏业能⼒、安全能⼒、基础能⼒

1.1.2、电脑⽹站⽀付产品介绍

        应⽤场景、准⼊条件、计费模式

 1.2、接入准备

1.2.1、开放平台账号注册

支付宝开放平台

1.2.2、常规接入流程

1. 创建应用
  • 选择应用类型:根据需求选择适合的应用类型,比如服务类应用、商品类应用等。
  • 填写应用基本信息:输入应用的名称、描述、类别等基本信息。
  • 添加应用功能:选择并添加应用需要具备的功能模块,比如支付功能、用户管理等。
  • 配置应用环境
    • 获取支付宝公钥:从支付宝平台获取公钥,用于加密数据传输。
    • 配置应用公钥和私钥:生成应用的公钥和私钥,并在支付宝平台进行配置。
    • 支付宝网关地址:配置支付宝提供的网关地址,确保应用能够正确与支付宝服务器通信。
    • 接口内容加密方式:配置数据传输时使用的加密方式,确保数据安全。
  • 查看 APPID:在应用创建完成后,获取并记录应用的APPID,用于后续的API调用和配置。
2. 绑定应用
  • 将开发者账号中的APPID和商家账号PID进行绑定:确保应用和商家账号的关联性,便于管理和操作。
3. 配置秘钥
  • 在创建应用中的“配置应用环境”步骤中,完成秘钥的配置。具体包括生成和配置公钥、私钥,以及其他安全设置。
4. 上线应用
  • 将应用提交审核:在开发和测试完成后,将应用提交到支付宝平台进行审核。审核通过后,应用才能正式上线并供用户使用。
5. 签约功能
  • 在商家中心上传营业执照、已备案网站信息等:提供必要的资质证明,确保商家身份的真实性。
  • 提交审核进行签约:等待支付宝平台的审核,审核通过后,即可正式签约并开通相关功能。

1.2.3、 使用沙箱

沙箱环境配置:小程序文档 - 支付宝文档中心
沙箱版支付宝的下载和登入:登录 - 支付宝

 2、案例项目

        基于微信支付的案例中的payment_demo.sql,执⾏以下命令还原数据库

1、还原数据库

mysql -uroot -p <D:\payment_demo.sql

2、运行后端代码

        ⽤idea打开payment-demo,确认maven仓库的位置,修改application.yml中的数据库连接配置,运⾏项⽬

3、运行前端代码

        安装node.js,如果你希望⽅便的查看和修改前端代码,可以安装⼀个VSCode和相关插件,⽤VSCode打 开
        前端项⽬payment-demo-front,运⾏前端项⽬

#先进入到前端项目目录
npm run serve

3、引入支付参数

1、引入沙箱配置文件

        将之前准备好的 alipay-sandbox.properties 复制到项⽬的 resources ⽬录中 并将其设置为 spring 配置⽂件

2、创建配置文件

        在config包中创建AlipayClientConfig

package com.atguigu.paymentdemo.config;
@Configuration
//加载配置文件
@PropertySource("classpath:alipay-sandbox.properties")
public class AlipayClientConfig {
}

3、测试配置文件的引入

package com.atguigu.paymentdemo;
@SpringBootTest
@Slf4j
public class AlipayTests {
    @Resource
    private Environment config;
    @Test
    void testGetAlipayConfig(){
    log.info("appid = " + config.getProperty("alipay.app-id"));
    }
}

4、引入服务端SDK

1、引入依赖

        参考⽂档:开放平台 => ⽂档 => 开发⼯具 => 服务端SDK => Java => 通⽤版 => Maven项⽬依赖

Maven Central Repository Search

<!--SDK-->
<dependency>
    <groupId>com.alipay.sdk</groupId>
    <artifactId>alipay-sdk-java</artifactId>
    <version>4.22.57.ALL</version>
</dependency>

2、创建客户端连接对象

创建带数据签名的客⼾端对象
参考⽂档:开放平台 => ⽂档 => 开发⼯具 => 技术接⼊指南 => 数据签名
小程序文档 - 支付宝文档中心
参考⽂档中 公钥方式 完善 AlipayClientConfig 类,添加 alipayClient() ⽅法 初始化 AlipayClient 对象

 

package com.atguigu.paymentdemo.config;
import com.alipay.api.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import javax.annotation.Resource;
@Configuration
//加载配置文件
@PropertySource("classpath:alipay-sandbox.properties")
public class AlipayClientConfig {
    //自动获取alipay-sandbox.properties中的配置
    @Resource
    private Environment config;
    @Bean
    public AlipayClient alipayClient() throws AlipayApiException {
    AlipayConfig alipayConfig = new AlipayConfig();
    //设置网关地址
    alipayConfig.setServerUrl(config.getProperty("alipay.gateway-url"));
    //设置应用Id
    alipayConfig.setAppId(config.getProperty("alipay.app-id"));
    //设置应用私钥
    alipayConfig.setPrivateKey(config.getProperty("alipay.merchant-privatekey"));
    //设置请求格式,固定值json
    alipayConfig.setFormat(AlipayConstants.FORMAT_JSON);
    //设置字符集
    alipayConfig.setCharset(AlipayConstants.CHARSET_UTF8);
    //设置支付宝公钥
    alipayConfig.setAlipayPublicKey(config.getProperty("alipay.alipay-publickey"));
    //设置签名类型
    alipayConfig.setSignType(AlipayConstants.SIGN_TYPE_RSA2);
    //构造client
    AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig);
    return alipayClient;
    }
}

5、支付功能开发

1、支付调用流程

小程序文档 - 支付宝文档中心

2、接口说明

小程序文档 - 支付宝文档中心
公共请求参数:所有接⼝都需要的参数
请求参数:当前接⼝需要的参数
公共响应参数:所有接⼝的响应中都包含的数据
响应参数:当前接⼝的响应中包含的数据

3、发起支付请求

(1)创建 AliPayController

package com.atguigu.paymentdemo.controller;
import com.atguigu.paymentdemo.service.AliPayService;
import com.atguigu.paymentdemo.vo.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@CrossOrigin
@RestController
@RequestMapping("/api/ali-pay")
@Api(tags = "网站支付宝支付")
@Slf4j
public class AliPayController {
    @Resource
    private AliPayService aliPayService;
    @ApiOperation("统一收单下单并支付页面接口的调用")
    @PostMapping("/trade/page/pay/{productId}")
    public R tradePagePay(@PathVariable Long productId){
    log.info("统一收单下单并支付页面接口的调用");
    //支付宝开放平台接受 request 请求对象后
    // 会为开发者生成一个html 形式的 form表单,包含自动提交的脚本
    String formStr = aliPayService.tradeCreate(productId);
    //我们将form表单字符串返回给前端程序,之后前端将会调用自动提交脚本,进行表单的提交
    //此时,表单会自动提交到action属性所指向的支付宝开放平台中,从而为用户展示一个支付页面
    return R.ok().data("formStr", formStr);
    }
}

(2)创建 AliPayService
接⼝
 

package com.atguigu.paymentdemo.service;
public interface AliPayService {
    String tradeCreate(Long productId);
    }

实现:

package com.atguigu.paymentdemo.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.response.AlipayTradePagePayResponse;
import com.atguigu.paymentdemo.entity.OrderInfo;
import com.atguigu.paymentdemo.service.AliPayService;
import com.atguigu.paymentdemo.service.OrderInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
@Service
@Slf4j
public class AliPayServiceImpl implements AliPayService {
@Resource
private OrderInfoService orderInfoService;
@Resource
private AlipayClient alipayClient;
@Resource
private Environment config;
@Transactional
@Override
public String tradeCreate(Long productId) {
    try {
    //生成订单
    log.info("生成订单");
    OrderInfo orderInfo =
    orderInfoService.createOrderByProductId(productId);
    //调用支付宝接口
    AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
    //配置需要的公共请求参数
    //request.setNotifyUrl("");
    //支付完成后,我们想让页面跳转回谷粒学院的页面,配置returnUrl
    request.setReturnUrl(config.getProperty("alipay.return-url"));
    //组装当前业务方法的请求参数
    JSONObject bizContent = new JSONObject();
    bizContent.put("out_trade_no", orderInfo.getOrderNo());
    BigDecimal total = new
    BigDecimal(orderInfo.getTotalFee().toString()).divide(new BigDecimal("100"));
    bizContent.put("total_amount", total);
    bizContent.put("subject", orderInfo.getTitle());
    bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
    request.setBizContent(bizContent.toString());
    //执行请求,调用支付宝接口
    AlipayTradePagePayResponse response =
    alipayClient.pageExecute(request);
    if(response.isSuccess()){
    log.info("调用成功,返回结果 ===> " + response.getBody());
    return response.getBody();
    } else {
    log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述
    ===> " + response.getMsg());
    throw new RuntimeException("创建支付交易失败");
    }
    } catch (AlipayApiException e) {
    e.printStackTrace();
    throw new RuntimeException("创建支付交易失败");
    	}
    }
}

4、前端⽀付按钮

(1)index.vue

//确认支付
toPay() {
    //禁用按钮,防止重复提交
    this.payBtnDisabled = true
    //微信支付
    if (this.payOrder.payType === 'wxpay') {
    ......
    //支付宝支付
    } else if (this.payOrder.payType === 'alipay') {
    //调用支付宝统一收单下单并支付页面接口
    aliPayApi.tradePagePay(this.payOrder.productId).then((response) => {
    //将支付宝返回的表单字符串写在浏览器中,表单会自动触发submit提交
    document.write(response.data.formStr)
   	 })
    }
},

(2)aliPay.js

// axios 发送ajax请求
import request from '@/utils/request'
export default{
    //发起支付请求
    tradePagePay(productId) {
    return request({
    url: '/api/ali-pay/trade/page/pay/' + productId,
    method: 'post'
    	})
    }
}

5、设置异步通知地址

在 AliPayServiceImpl 的 tradeCreate ⽅法中设置异步通知地址

//配置需要的公共请求参数
//支付完成后,支付宝向谷粒学院发起异步通知的地址
request.setNotifyUrl(config.getProperty("alipay.notify-url"));

6、启动内⽹穿透ngrok

ngrok http 8090

7、开发异步通知接⼝
(1)AliPayController

@Resource
private Environment config;
@Resource
private OrderInfoService orderInfoService;
@ApiOperation("支付通知")
@PostMapping("/trade/notify")
public String tradeNotify(@RequestParam Map<String, String> params){
    log.info("支付通知正在执行");
    log.info("通知参数 ===> {}", params);
    String result = "failure";
    try {
    //异步通知验签
    boolean signVerified = AlipaySignature.rsaCheckV1(
    params,
    config.getProperty("alipay.alipay-public-key"),
    AlipayConstants.CHARSET_UTF8,
    AlipayConstants.SIGN_TYPE_RSA2); //调用SDK验证签名
    if(!signVerified){
    //验签失败则记录异常日志,并在response中返回failure.
    log.error("支付成功异步通知验签失败!");
    return result;
    }
    // 验签成功后
    log.info("支付成功异步通知验签成功!");
    //按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验,
    //1 商户需要验证该通知数据中的 out_trade_no 是否为商户系统中创建的订单号
    String outTradeNo = params.get("out_trade_no");
    OrderInfo order = orderInfoService.getOrderByOrderNo(outTradeNo);
    if(order == null){
    log.error("订单不存在");
    return result;
    }
    //2 判断 total_amount 是否确实为该订单的实际金额(即商户订单创建时的金额)
    String totalAmount = params.get("total_amount");
    int totalAmountInt = new BigDecimal(totalAmount).multiply(new
    BigDecimal("100")).intValue();
    int totalFeeInt = order.getTotalFee().intValue();
    if(totalAmountInt != totalFeeInt){
    log.error("金额校验失败");
    return result;
    }
    //3 校验通知中的 seller_id(或者 seller_email) 是否为 out_trade_no 这笔单据的对应
    的操作方
    String sellerId = params.get("seller_id");
    String sellerIdProperty = config.getProperty("alipay.seller-id");
    if(!sellerId.equals(sellerIdProperty)){
    log.error("商家pid校验失败");
    return result;
    }
    //4 验证 app_id 是否为该商户本身
    String appId = params.get("app_id");
    String appIdProperty = config.getProperty("alipay.app-id");
    if(!appId.equals(appIdProperty)){
    log.error("appid校验失败");
    return result;
    }
    //在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS时,
    // 支付宝才会认定为买家付款成功。
    String tradeStatus = params.get("trade_status");
    if(!"TRADE_SUCCESS".equals(tradeStatus)){
    log.error("支付未成功");
    return result;
    }
    //处理业务 修改订单状态 记录支付日志
    aliPayService.processOrder(params);
    //校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failure
    result = "success";
    } catch (AlipayApiException e) {
    e.printStackTrace();
    }
    return result;
}

(2)AliPayService

void processOrder(Map<String, String> params);
/**
* 处理订单
* @param params
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void processOrder(Map<String, String> params) {
    log.info("处理订单");
    //获取订单号
    String orderNo = params.get("out_trade_no");
    //更新订单状态
    orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
    //记录支付日志
    paymentInfoService.createPaymentInfoForAliPay(params);
}

8、记录⽀付⽇志

void createPaymentInfoForAliPay(Map<String, String> params);
/**
* 记录支付日志:支付宝
* @param params
*/
@Override
public void createPaymentInfoForAliPay(Map<String, String> params) {
    log.info("记录支付日志");
    //获取订单号
    String orderNo = params.get("out_trade_no");
    //业务编号
    String transactionId = params.get("trade_no");
    //交易状态
    String tradeStatus = params.get("trade_status");
    //交易金额
    String totalAmount = params.get("total_amount");
    int totalAmountInt = new BigDecimal(totalAmount).multiply(new
    BigDecimal("100")).intValue();
    PaymentInfo paymentInfo = new PaymentInfo();
    paymentInfo.setOrderNo(orderNo);
    paymentInfo.setPaymentType(PayType.ALIPAY.getType());
    paymentInfo.setTransactionId(transactionId);
    paymentInfo.setTradeType("电脑网站支付");
    paymentInfo.setTradeState(tradeStatus);
    paymentInfo.setPayerTotal(totalAmountInt);
    Gson gson = new Gson();
    String json = gson.toJson(params, HashMap.class);
    paymentInfo.setContent(json);
    baseMapper.insert(paymentInfo);
}

9、更新订单状态记录⽀付⽇志

 在 processOrder ⽅法中,更新订单状态之前,添加如下代码

//处理重复通知
//接口调用的幂等性:无论接口被调用多少次,以下业务执行一次
String orderStatus = orderInfoService.getOrderStatus(orderNo);
if (!OrderStatus.NOTPAY.getType().equals(orderStatus)) {
    return;
}
/**
* 处理订单
* @param params
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void processOrder(Map<String, String> params) {
    log.info("处理订单");
    //获取订单号
    String orderNo = params.get("out_trade_no");
    /*在对业务数据进行状态检查和处理之前,
    要采用数据锁进行并发控制,
    以避免函数重入造成的数据混乱*/
    //尝试获取锁:
    // 成功获取则立即返回true,获取失败则立即返回false。不必一直等待锁的释放
    if(lock.tryLock()) {
    try {
    //处理重复通知
    //接口调用的幂等性:无论接口被调用多少次,以下业务执行一次
    String orderStatus = orderInfoService.getOrderStatus(orderNo);
    if (!OrderStatus.NOTPAY.getType().equals(orderStatus)) {
    return;
    }
    //更新订单状态
    orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
    //记录支付日志
    paymentInfoService.createPaymentInfoForAliPay(params);
    } finally {
    //要主动释放锁
    lock.unlock();
   		}
    }
}

6、订单表优化

1、表修改

t_order_info 表中添加 payment_type 字段

2、业务修改

(1)修改⽀付业务代码
        修 改 AliPayServiceImpl 、 WxPayServiceImpl 代 码 中 对 如 下 ⽅ 法 的 调 ⽤ , 添 加 参 数 PayType.ALIPAY.getType()

log.info("生成订单");
OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId,
PayType.ALIPAY.getType());

        实现类的 createOrderByProductId ⽅法中添加参数 String paymentType 对 getNoPayOrderByProductId ⽅法的调⽤时添加参数 paymentType ⽣成订单的过程中添加 orderInfo.setPaymentType(paymentType);

@Override
public OrderInfo createOrderByProductId(Long productId, String paymentType) {
    //查找已存在但未支付的订单
    OrderInfo orderInfo = this.getNoPayOrderByProductId(productId, paymentType);
    if( orderInfo != null){
    return orderInfo;
    }
    //获取商品信息
    Product product = productMapper.selectById(productId);
    //生成订单
    orderInfo = new OrderInfo();
    orderInfo.setTitle(product.getTitle());
    orderInfo.setOrderNo(OrderNoUtils.getOrderNo()); //订单号
    orderInfo.setProductId(productId);
    orderInfo.setTotalFee(product.getPrice()); //分
    orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType()); //未支付
    orderInfo.setPaymentType(paymentType);
    baseMapper.insert(orderInfo);
    return orderInfo;
}

        对 getNoPayOrderByProductId ⽅法的定义时添加参数 paymentType 添加查询条件 queryWrapper.eq("payment_type", paymentType);

/**
* 根据商品id查询未支付订单
* 防止重复创建订单对象
* @param productId
* @return
*/
private OrderInfo getNoPayOrderByProductId(Long productId, String paymentType)
    {
    QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("product_id", productId);
    queryWrapper.eq("order_status", OrderStatus.NOTPAY.getType());
    queryWrapper.eq("payment_type", paymentType);
    // queryWrapper.eq("user_id", userId);
    OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);
    return orderInfo;
}

7、统⼀收单交易关闭

1、定义⽤⼾取消订单接⼝

在 AliPayController 中添加⽅法

/**
* 用户取消订单
* @param orderNo
* @return
*/
@ApiOperation("用户取消订单")
@PostMapping("/trade/close/{orderNo}")
public R cancel(@PathVariable String orderNo){
    log.info("取消订单");
    aliPayService.cancelOrder(orderNo);
    return R.ok().setMessage("订单已取消");
}

2、关单并修改订单状态

void cancelOrder(String orderNo);
/**
* 用户取消订单
* @param orderNo
*/
@Override
public void cancelOrder(String orderNo) {
    //调用支付宝提供的统一收单交易关闭接口
    this.closeOrder(orderNo);
    //更新用户订单状态
    orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CANCEL);
}

3、调⽤⽀付宝接⼝

AliPayServiceImpl 中添加辅助⽅法

/**
* 关单接口的调用
* @param orderNo 订单号
*/
private void closeOrder(String orderNo) {
    try {
    log.info("关单接口的调用,订单号 ===> {}", orderNo);
    AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();
    JSONObject bizContent = new JSONObject();
    bizContent.put("out_trade_no", orderNo);
    request.setBizContent(bizContent.toString());
    AlipayTradeCloseResponse response = alipayClient.execute(request);
    if(response.isSuccess()){
    log.info("调用成功,返回结果 ===> " + response.getBody());
    } else {
    log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述
    ===> " + response.getMsg());
    //throw new RuntimeException("关单接口的调用失败");
    }
    } catch (AlipayApiException e) {
    e.printStackTrace();
    throw new RuntimeException("关单接口的调用失败");
    }
}

4、测试

        注意:针对⼆维码⽀付,只有经过扫码的订单才在⽀付宝端有交易记录。针对⽀付宝账号⽀付,只有经 过登录的订单才在⽀付宝端有交易记录。

 8、统⼀收单线下交易查询

1、查单接⼝的调⽤

        商⼾后台未收到异步⽀付结果通知时,商⼾应该主动调⽤《统⼀收单线下交易查询接⼝》,同步订单状 态。

(1)AliPayController

/**
* 查询订单
* @param orderNo
* @return
* @throws Exception
*/
@ApiOperation("查询订单:测试订单状态用")
@GetMapping("/trade/query/{orderNo}")
public R queryOrder(@PathVariable String orderNo) {
    log.info("查询订单");
    String result = aliPayService.queryOrder(orderNo);
    return R.ok().setMessage("查询成功").data("result", result);
}

(2)AliPayService 接⼝

String queryOrder(String orderNo);
/**
* 查询订单
* @param orderNo
* @return 返回订单查询结果,如果返回null则表示支付宝端尚未创建订单
*/
@Override
public String queryOrder(String orderNo) {
    try {
    log.info("查单接口调用 ===> {}", orderNo);
    AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
    JSONObject bizContent = new JSONObject();
    bizContent.put("out_trade_no", orderNo);
    request.setBizContent(bizContent.toString());
    AlipayTradeQueryResponse response = alipayClient.execute(request);
    if(response.isSuccess()){
    log.info("调用成功,返回结果 ===> " + response.getBody());
    return response.getBody();
    } else {
    log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> "
    + response.getMsg());
    //throw new RuntimeException("查单接口的调用失败");
    return null;//订单不存在
    }
    } catch (AlipayApiException e) {
    e.printStackTrace();
    throw new RuntimeException("查单接口的调用失败");
    }
}

2、定时查单

(1)创建AliPayTask

@Slf4j
@Component
public class AliPayTask {
@Resource
private OrderInfoService orderInfoService;
@Resource
private AliPayService aliPayService;
/**
* 从第0秒开始每隔30秒执行1次,查询创建超过5分钟,并且未支付的订单
*/
@Scheduled(cron = "0/30 * * * * ?")
public void orderConfirm() throws Exception {
    log.info("orderConfirm 被执行......");
    List<OrderInfo> orderInfoList = orderInfoService.getNoPayOrderByDuration(1,
    PayType.ALIPAY.getType());
    for (OrderInfo orderInfo : orderInfoList) {
    String orderNo = orderInfo.getOrderNo();
    log.warn("超时订单 ===> {}", orderNo);
    //核实订单状态:调用支付宝查单接口
    //aliPayService.checkOrderStatus(orderNo);
    		}
    	}
    }

(2)修改OrderInfoService
接⼝添加参数 String paymentType

List<OrderInfo> getNoPayOrderByDuration(int minutes, String paymentType);

实 现 添 加 参 数 String paymentType , 添 加 查 询 条 件 queryWrapper.eq("payment_type", paymentType);

/**
* 找出创建超过minutes分钟并且未支付的订单
* @param minutes
* @return
*/
@Override
public List<OrderInfo> getNoPayOrderByDuration(int minutes, String paymentType) {
    //minutes分钟之前的时间
    Instant instant = Instant.now().minus(Duration.ofMinutes(minutes));
    QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("order_status", OrderStatus.NOTPAY.getType());
    queryWrapper.le("create_time", instant);
    queryWrapper.eq("payment_type", paymentType);
    List<OrderInfo> orderInfoList = baseMapper.selectList(queryWrapper);
    return orderInfoList;
}

(3)修改WxPayTask

        将之前微信⽀付的⽅法调⽤也做⼀个优化 orderConfirm ⽅法中对 getNoPayOrderByDuration 的调⽤添加参数 PayType.WXPAY.getType()

List<OrderInfo> orderInfoList = orderInfoService.getNoPayOrderByDuration(1,
PayType.WXPAY.getType());

3、处理查询到的订单

(1)AliPayTask
在定时任务的for循环最后添加以下代码

//核实订单状态:调用支付宝查单接口
aliPayService.checkOrderStatus(orderNo);

(2)AliPayService
核实订单状态

void checkOrderStatus(String orderNo);
/**
* 根据订单号调用支付宝查单接口,核实订单状态
* 如果订单未创建,则更新商户端订单状态
* 如果订单未支付,则调用关单接口关闭订单,并更新商户端订单状态
* 如果订单已支付,则更新商户端订单状态,并记录支付日志
* @param orderNo
*/
@Override
public void checkOrderStatus(String orderNo) {
    log.warn("根据订单号核实订单状态 ===> {}", orderNo);
    String result = this.queryOrder(orderNo);
    //订单未创建
    if(result == null){
    log.warn("核实订单未创建 ===> {}", orderNo);
    //更新本地订单状态
    orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CLOSED);
    }
    //解析查单响应结果
    Gson gson = new Gson();
    HashMap<String, LinkedTreeMap> resultMap = gson.fromJson(result,
    HashMap.class);
    LinkedTreeMap alipayTradeQueryResponse =
    resultMap.get("alipay_trade_query_response");
    String tradeStatus = (String)alipayTradeQueryResponse.get("trade_status");
    if(AliPayTradeState.NOTPAY.getType().equals(tradeStatus)){
    log.warn("核实订单未支付 ===> {}", orderNo);
    //如果订单未支付,则调用关单接口关闭订单
    this.closeOrder(orderNo);
    // 并更新商户端订单状态
    orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CLOSED);
    }
    if(AliPayTradeState.SUCCESS.getType().equals(tradeStatus)){
    log.warn("核实订单已支付 ===> {}", orderNo);
    //如果订单已支付,则更新商户端订单状态
    orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
    //并记录支付日志
    paymentInfoService.createPaymentInfoForAliPay(alipayTradeQueryResponse);
    }
}

9、统⼀收单交易退款

1、退款接⼝

(1)AliPayController

/**
* 申请退款
* @param orderNo
* @param reason
* @return
*/
@ApiOperation("申请退款")
@PostMapping("/trade/refund/{orderNo}/{reason}")
public R refunds(@PathVariable String orderNo, @PathVariable String reason){
    log.info("申请退款");
    aliPayService.refund(orderNo, reason);
    return R.ok();
}

(2)AliPayService

void refund(String orderNo, String reason);
/**
* 退款
* @param orderNo
* @param reason
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void refund(String orderNo, String reason) {
    try {
    log.info("调用退款API");
    //创建退款单
    RefundInfo refundInfo =
    refundsInfoService.createRefundByOrderNoForAliPay(orderNo, reason);
    //调用统一收单交易退款接口
    AlipayTradeRefundRequest request = new AlipayTradeRefundRequest ();
    //组装当前业务方法的请求参数
    JSONObject bizContent = new JSONObject();
    bizContent.put("out_trade_no", orderNo);//订单编号
    BigDecimal refund = new
    BigDecimal(refundInfo.getRefund().toString()).divide(new BigDecimal("100"));
    //BigDecimal refund = new BigDecimal("2").divide(new BigDecimal("100"));
    bizContent.put("refund_amount", refund);//退款金额:不能大于支付金额
    bizContent.put("refund_reason", reason);//退款原因(可选)
    request.setBizContent(bizContent.toString());
    //执行请求,调用支付宝接口
    AlipayTradeRefundResponse response = alipayClient.execute(request);
    if(response.isSuccess()){
    log.info("调用成功,返回结果 ===> " + response.getBody());
    //更新订单状态
    orderInfoService.updateStatusByOrderNo(orderNo,
    OrderStatus.REFUND_SUCCESS);
    //更新退款单
    refundsInfoService.updateRefundForAliPay(
    refundInfo.getRefundNo(),
    response.getBody(),
    AliPayTradeState.REFUND_SUCCESS.getType()); //退款成功
    } else {
    log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> "
    + response.getMsg());
    //更新订单状态
    orderInfoService.updateStatusByOrderNo(orderNo,
    OrderStatus.REFUND_ABNORMAL);
    //更新退款单
    refundsInfoService.updateRefundForAliPay(
    refundInfo.getRefundNo(),
    response.getBody(),
    AliPayTradeState.REFUND_ERROR.getType()); //退款失败
    }
    } catch (AlipayApiException e) {
    e.printStackTrace();
    throw new RuntimeException("创建退款申请失败");
    }
}

2、创建退款记录

RefundInfo createRefundByOrderNoForAliPay(String orderNo, String reason);
/**
* 根据订单号创建退款订单
* @param orderNo
* @return
*/
@Override
public RefundInfo createRefundByOrderNoForAliPay(String orderNo, String reason) {
    //根据订单号获取订单信息
    OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo);
    //根据订单号生成退款订单
    RefundInfo refundInfo = new RefundInfo();
    refundInfo.setOrderNo(orderNo);//订单编号
    refundInfo.setRefundNo(OrderNoUtils.getRefundNo());//退款单编号
    refundInfo.setTotalFee(orderInfo.getTotalFee());//原订单金额(分)
    refundInfo.setRefund(orderInfo.getTotalFee());//退款金额(分)
    refundInfo.setReason(reason);//退款原因
    //保存退款订单
    baseMapper.insert(refundInfo);
    return refundInfo;
}

3、更新退款记录

void updateRefundForAliPay(String refundNo, String content, String refundStatus);
/**
* 更新退款记录
* @param refundNo
* @param content
* @param refundStatus
*/
@Override
public void updateRefundForAliPay(String refundNo, String content, String
refundStatus) {
    //根据退款单编号修改退款单
    QueryWrapper<RefundInfo> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("refund_no", refundNo);
    //设置要修改的字段
    RefundInfo refundInfo = new RefundInfo();
    refundInfo.setRefundStatus(refundStatus);//退款状态
    refundInfo.setContentReturn(content);//将全部响应结果存入数据库的content字段
    //更新退款单
    baseMapper.update(refundInfo, queryWrapper);
}

10、统⼀收单交易退款查询

1、退款查询

(1)AliPayController

/**
* 查询退款
* @param orderNo
* @return
* @throws Exception
*/
@ApiOperation("查询退款:测试用")
@GetMapping("/trade/fastpay/refund/{orderNo}")
public R queryRefund(@PathVariable String orderNo) throws Exception {
    log.info("查询退款");
    String result = aliPayService.queryRefund(orderNo);
    return R.ok().setMessage("查询成功").data("result", result);
}

(2)AliPayService

String queryRefund(String orderNo);
/**
* 查询退款
* @param orderNo
* @return
*/
@Override
public String queryRefund(String orderNo) {
    try {
    log.info("查询退款接口调用 ===> {}", orderNo);
    AlipayTradeFastpayRefundQueryRequest request = new
    AlipayTradeFastpayRefundQueryRequest();
    JSONObject bizContent = new JSONObject();
    bizContent.put("out_trade_no", orderNo);
    bizContent.put("out_request_no", orderNo);
    request.setBizContent(bizContent.toString());
    AlipayTradeFastpayRefundQueryResponse response =
    alipayClient.execute(request);
    if(response.isSuccess()){
    log.info("调用成功,返回结果 ===> " + response.getBody());
    return response.getBody();
    } else {
    log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> "
    + response.getMsg());
    //throw new RuntimeException("查单接口的调用失败");
    return null;//订单不存在
    }
    } catch (AlipayApiException e) {
    e.printStackTrace();
    throw new RuntimeException("查单接口的调用失败");
    }
}

11、收单退款冲退完成通知

        退款存在退到银⾏卡场景下时,收单会根据银⾏回执消息发送退款完成信息。 开发流程类似⽀付结果通知。

 12、对账

查询对账单下载地址接⼝

(1)AliPayController

/**
* 根据账单类型和日期获取账单url地址
*
* @param billDate
* @param type
* @return
*/
@ApiOperation("获取账单url")
@GetMapping("/bill/downloadurl/query/{billDate}/{type}")
public R queryTradeBill(
@PathVariable String billDate,
@PathVariable String type) {
    log.info("获取账单url");
    String downloadUrl = aliPayService.queryBill(billDate, type);
    return R.ok().setMessage("获取账单url成功").data("downloadUrl", downloadUrl);
}

(2)AliPayService

String queryBill(String billDate, String type);
/**
* 申请账单
* @param billDate
* @param type
* @return
*/
@Override
public String queryBill(String billDate, String type) {
    try {
    AlipayDataDataserviceBillDownloadurlQueryRequest request = new
    AlipayDataDataserviceBillDownloadurlQueryRequest();
    JSONObject bizContent = new JSONObject();
    bizContent.put("bill_type", type);
    bizContent.put("bill_date", billDate);
    request.setBizContent(bizContent.toString());
    AlipayDataDataserviceBillDownloadurlQueryResponse response =
    alipayClient.execute(request);
    if(response.isSuccess()){
    log.info("调用成功,返回结果 ===> " + response.getBody());
    //获取账单下载地址
    Gson gson = new Gson();
    HashMap<String, LinkedTreeMap> resultMap =
    gson.fromJson(response.getBody(), HashMap.class);
    LinkedTreeMap billDownloadurlResponse =
    resultMap.get("alipay_data_dataservice_bill_downloadurl_query_response");
    String billDownloadUrl =
    (String)billDownloadurlResponse.get("bill_download_url");
    return billDownloadUrl;
    } else {
    log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> "
    + response.getMsg());
    throw new RuntimeException("申请账单失败");
    }
    } catch (AlipayApiException e) {
    e.printStackTrace();
    throw new RuntimeException("申请账单失败!");
    }
}

  • 35
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Alipay Record(支付宝账单)是支付宝用户在使用支付宝进行各类交易时所产生的记录和明细。支付宝作为中国最大的第三方支付平台之一,为用户提供了快捷、方便、安全的电子支付方式。通过支付宝,用户可以进行线上和线下的各类消费、转账、缴费等操作。而Alipay Record就是对这些交易的详细记录和总结。 Alipay Record包含了用户在支付宝进行的各类交易的信息,如交易时间、交易金额、交易对象、交易方式等。用户可以在支付宝APP或网页端查看自己的账单,并根据自己的需求进行查询和筛选。支付宝账单可以帮助用户了解自己的消费情况,方便自己的财务管理和预算规划。 通过支付宝账单,用户可以清晰地看到自己的消费流水,掌握自己的消费习惯和消费趋势,从而更好地进行理财规划和支出控制。支付宝账单还可以帮助用户核对自己的支付情况,防止账单错误和消费纠纷的发生。 同时,支付宝账单还提供了一些其他的功能,如账单分享、账单合并、账单记账等。用户可以将账单分享给亲友,共同查看交易明细。账单合并功能可以将多个支付宝账号的交易记录合并在一起,方便多账户管理。账单记账则可以帮助用户记录自己的现金流情况,方便自己的财务管理。 总之,Alipay Record对于支付宝用户来说是一个重要的工具,它帮助用户全面了解自己的支付情况和消费习惯,方便个人财务管理和预算规划。支付宝账单的功能还在不断更新和完善中,为用户提供更加便捷和智能的金融服务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-Z_Nuyoah

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值