【网课平台】Day13.订单支付模式:生成支付二维码与查询支付

一、需求:生成支付二维码

1、需求分析

UI设计图:

  • 点击支付宝支付

在这里插入图片描述

  • 生成支付二维码

逻辑设计:

在这里插入图片描述

  • 点击支付,前端调用学习中心服务的添加选课接口
  • 添加选课成功请求订单服务生成支付二维码接口
  • 生成二维码接口进行:创建商品订单、生成支付交易记录、生成二维码
  • 将二维码返回到前端,用户扫码

2、表设计

订单支付模式的核心由三张表组成:

  • 订单表
  • 订单明细表
  • 支付交易记录表

其中:订单表记录订单信息
在这里插入图片描述

注意最后一个字段:out_business_id

这是一个订单表中的记录,订单类型是选课(一对一答疑、电子书..),那这个订单是哪个选课记录对应的

即此时out_business_id等于选课表的id

订单明细表记录订单的详细信息(一个订单可能有多个商品,因此订单表和订单明细表是一对多的关系)

在这里插入图片描述
支付交易记录表记录每次支付的交易明细

在这里插入图片描述
三张表的关系为:

在这里插入图片描述

为什么要有支付交易记录表?
在请求微信或支付宝下单接口时需要传入 商品订单号,在与第三方支付平台对接时发现,当用户支付失败或因为其它原因最终该订单没有支付成功,此时再次调用第三方支付平台的下单接口发现报错“订单号已存在”,此时如果我们传入一个没有使用过的订单号就可以解决问题,但是商品订单已经创建,因为没有支付成功重新创建一个新订单是不合理的。

解决以上问题的方案是:

  • 用户每次发起都创建一个新的支付交易记录 ,此交易记录与商品订单关联。
  • 将支付交易记录的流水号传给第三方支付系统下单接口,这样就即使没有支付成功就不会出现上边的问题。
  • 需要提醒用户不要重复支付。
    在这里插入图片描述

注意订单号要唯一、安全,生成思路有:

  • 时间戳+随机数:年月日时分秒毫秒+随机数
  • 高并发下使用:年月日时分秒毫秒+随机数+redis自增序列
  • 订单号中加上业务标识:如拼接出来的订单号的第十位代表业务类型,最后一位代表用户类型。这样也方便客服识别。
  • 雪花算法:推特内部使用的分布式环境下的唯一ID生成算法,它基于时间戳生成,保证有序递增。可以满足高并发环境下ID不重复

生成订单号的雪花算法工具类:

package com.xuecheng.base.utils;
 
import java.util.Random;
 
/**
 * snow flow .
 *
 */
public final class IdWorkerUtils {
 
    private static final Random RANDOM = new Random();
 
    private static final long WORKER_ID_BITS = 5L;
 
    private static final long DATACENTERIDBITS = 5L;
 
    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
 
    private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTERIDBITS);
 
    private static final long SEQUENCE_BITS = 12L;
 
    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
 
    private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
 
    private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTERIDBITS;
 
    private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);
 
    private static final IdWorkerUtils ID_WORKER_UTILS = new IdWorkerUtils();
 
    private long workerId;
 
    private long datacenterId;
 
    private long idepoch;
 
    private long sequence = '0';
 
    private long lastTimestamp = -1L;
 
    private IdWorkerUtils() {
        this(RANDOM.nextInt((int) MAX_WORKER_ID), RANDOM.nextInt((int) MAX_DATACENTER_ID), 1288834974657L);
    }
 
    private IdWorkerUtils(final long workerId, final long datacenterId, final long idepoch) {
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", MAX_WORKER_ID));
        }
        if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", MAX_DATACENTER_ID));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
        this.idepoch = idepoch;
    }
 
    /**
     * Gets instance.
     *
     * @return the instance
     */
    public static IdWorkerUtils getInstance() {
        return ID_WORKER_UTILS;
    }
 
    public synchronized long nextId() {
        long timestamp = timeGen();
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & SEQUENCE_MASK;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }
 
        lastTimestamp = timestamp;
 
        return ((timestamp - idepoch) << TIMESTAMP_LEFT_SHIFT)
                | (datacenterId << DATACENTER_ID_SHIFT)
                | (workerId << WORKER_ID_SHIFT) | sequence;
    }
 
    private long tilNextMillis(final long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }
 
    private long timeGen() {
        return System.currentTimeMillis();
    }
 
    /**
     * Build part number string.
     *
     * @return the string
     */
    public String buildPartNumber() {
        return String.valueOf(ID_WORKER_UTILS.nextId());
    }
 
    /**
     * Create uuid string.
     *
     * @return the string
     */
    public String createUUID() {
        return String.valueOf(ID_WORKER_UTILS.nextId());
    }
 
    public static void main(String[] args) {
        System.out.println(IdWorkerUtils.getInstance().nextId());
    }
}

3、接口定义

在订单服务中定义生成支付二维码的接口。根据请求参数写dto类:

package com.xuecheng.orders.model.dto;

import com.xuecheng.orders.model.po.XcOrders;
import lombok.Data;
import lombok.ToString;

@Data
@ToString
public class AddOrderDto  {

    /**
     * 总价
     */
    private Float totalPrice;

    /**
     * 订单类型
     */
    private String orderType;

    /**
     * 订单名称
     */
    private String orderName;
    /**
     * 订单描述
     */
    private String orderDescrip;

    /**
     * 订单明细json,不可为空
     * [{"goodsId":"","goodsType":"","goodsName":"","goodsPrice":"","goodsDetail":""},{...}]
     */
    private String orderDetail;

    /**
     * 外部系统业务id
     */
    private String outBusinessId;

}

需要返回二维码与支付相关的信息,定义Vo:

//继承支付记录Po类
@Data
@ToString
public class PayRecordVo extends XcPayRecord {

    //二维码
    private String qrcode;

}

接口定义:

@Api(value = "订单支付接口", tags = "订单支付接口")
@Slf4j
@Controller
public class OrderController {

    @ApiOperation("生成支付二维码")
    @PostMapping("/generatepaycode")
    @ResponseBody
    public PayRecordVo generatePayCode(@RequestBody AddOrderDto addOrderDto) {
        	//订单信息插入
        	//插入支付记录
        	//生成二维码
            return null;
    }

}

用户扫码下单,和二维码关联的下单接口如下:

@ApiOperation("扫码下单接口")
@GetMapping("/requestpay")
public void requestpay(String payNo,HttpServletResponse httpResponse) throws IOException {

}

4、接口实现

定义Service接口:

public interface OrderService {


   /**
    * @param addOrderDto 订单信息
    * @return PayRecordVo 支付交易记录(包括二维码)
	*/
	public PayRecordVo createOrder(String userId,AddOrderDto addOrderDto);

写实现类:(先注释写每一步的逻辑

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    XcOrdersMapper ordersMapper;
    @Autowired
    XcOrdersGoodsMapper ordersGoodsMapper;
    
    @Autowired
    XcPayRecordMapper payRecordMapper;

    @Transactional
    @Override
    public PayRecordDto createOrder(String userId, AddOrderDto addOrderDto) {

		//一个选课记录只能有一个订单,插入前要进行幂等性判断
		
        //添加商品订单(订单表+订单明细表,同成功同失败,要事务控制)

        //添加支付交易记录
        
        //生成二维码
        
        return null;
    }
}

注释中的逻辑,分别定义不同的方法实现。编写创建商品订单方法,商品订单的数据来源于选课记录,在订单表需要存入选课记录的ID,这里需要作好幂等处理

@Transactional
public XcOrders saveXcOrders(String userId,AddOrderDto addOrderDto){
    //幂等性处理
    XcOrders order = getOrderByBusinessId(addOrderDto.getOutBusinessId());
    if(order!=null){
        return order;
    }
    //接下来插入订单表,先new一个对象
    order = new XcOrders();
    //生成订单号
    long orderId = IdWorkerUtils.getInstance().nextId(); //雪花算法工具类
    order.setId(orderId);
    order.setTotalPrice(addOrderDto.getTotalPrice());
    order.setCreateDate(LocalDateTime.now());
    order.setStatus("600001");//未支付
    order.setUserId(userId);
    order.setOrderType(addOrderDto.getOrderType());
    order.setOrderName(addOrderDto.getOrderName());
    order.setOrderDetail(addOrderDto.getOrderDetail());
    order.setOrderDescrip(addOrderDto.getOrderDescrip());
    order.setOutBusinessId(addOrderDto.getOutBusinessId());//选课记录id
    int insert = ordersMapper.insert(order);
    if(insert <= 0){
		MyException.cast("添加订单失败"); //添加失败时抛出异常,好让事务回滚
	}
	//前端传入的订单明细json串[{"goodsId":"","goodsType":"","goodsName":"","goodsPrice":"","goodsDetail":""},{...}]
    String orderDetailJson = addOrderDto.getOrderDetail();
    //json转List<订单明细表PO>
    List<XcOrdersGoods> xcOrdersGoodsList = JSON.parseArray(orderDetailJson, XcOrdersGoods.class);
    xcOrdersGoodsList.forEach(goods->{
        XcOrdersGoods xcOrdersGoods = new XcOrdersGoods();
        BeanUtils.copyProperties(goods,xcOrdersGoods);
        xcOrdersGoods.setOrderId(orderId);//订单号
        ordersGoodsMapper.insert(xcOrdersGoods);
    });
    return order;
}

//根据业务id查询订单
//这里的业务id即选课记录的id,选课记录表的主键
public XcOrders getOrderByBusinessId(String businessId) {
    XcOrders orders = ordersMapper.selectOne(new LambdaQueryWrapper<XcOrders>().eq(XcOrders::getOutBusinessId, businessId));
    return orders;
}

接下来写注释逻辑中的第二个方法(保存支付记录):

public XcPayRecord createPayRecord(XcOrders orders){
    if(order==null){
       MyException.cast("订单不存在");
    }
    if(orders.getStatus().equals("600002")){
        MyException.cast("订单已支付"); //避免重复支付
    }
    XcPayRecord payRecord = new XcPayRecord();
    //生成支付交易流水号,还是用雪花
    long payNo = IdWorkerUtils.getInstance().nextId();
    payRecord.setPayNo(payNo);
    payRecord.setOrderId(orders.getId());//商品订单号
    payRecord.setOrderName(orders.getOrderName());
    payRecord.setTotalPrice(orders.getTotalPrice());
    payRecord.setCurrency("CNY");
    payRecord.setCreateDate(LocalDateTime.now());
    payRecord.setStatus("601001");//未支付
    payRecord.setUserId(orders.getUserId());
    int insert = payRecordMapper.insert(payRecord);
    if(insert <= 0){
    	MyException.cast("插入支付记录失败");
    }
    return payRecord;

}

最后完善注释代码中的最后一步:生成支付二维码

# 扫描二维码要请求支付接口
# 配置二维码的url直接写代码里,硬编码不合适,直接写nacos,%s占位符
pay:
 qrcodeurl: http://192.168.101.1/api/orders/requestpay?payNo=%s

写方法的具体内容,完善整个方法:

@Value("${pay.qrcodeurl}")
String qrcodeurl;

@Transactional
@Override
public PayRecordVo createOrder(String userId, AddOrderDto addOrderDto) {
    //创建商品订单
    XcOrders orders = saveXcOrders(userId, addOrderDto);
    if(orders==null){
        XueChengPlusException.cast("订单创建失败");
    }
    if(orders.getStatus().equals("600002")){
        XueChengPlusException.cast("订单已支付");
    }
    //生成支付记录
    XcPayRecord payRecord = createPayRecord(orders);
    //生成二维码
    String qrCode = null;
    try {
        //传入支付记录id,拼接出url(即支付接口传参)
        String url = String.format(qrcodeurl, payRecord.getPayNo());
        qrCode = new QRCodeUtil().createQRCode(url, 200, 200);
    } catch (IOException e) {
        MyException.cast("生成二维码出错");
    }
    PayRecordVo payRecordVo = new PayRecordVo();
    BeanUtils.copyProperties(payRecord,payRecordVo);
    payRecordDto.setQrcode(qrCode);

    return payRecordVo;
}

5、完善controller

@Autowired
OrderService orderService;
    
@ApiOperation("生成支付二维码")
@PostMapping("/generatepaycode")
@ResponseBody
public PayRecordDto generatePayCode(@RequestBody AddOrderDto addOrderDto) {
    //工具类获取当前登录用户
    SecurityUtil.XcUser user = SecurityUtil.getUser();
    if(user == null){
        XueChengPlusException.cast("请登录后继续选课");
    }
   return orderService.createOrder(user.getId(), addOrderDto);
   
}

到此,调试一下,二维码可以成功生成。接下来完成扫描二维码时请求的接口,进行向支付宝下单,支付宝响应js唤起支付宝进行支付。

@ApiOperation("扫码下单接口")
@GetMapping("/requestpay")
public void requestpay(String payNo,HttpServletResponse httpResponse) throws IOException {

}

先定义查询支付交易记录的Service接口与实现方法:

/**
 * @description 查询支付交易记录
 * @param payNo  交易记录号
*/
public XcPayRecord getPayRecordByPayno(String payNo);

//根据支付记录号查询支付记录
public XcPayRecord getPayRecordByPayno(String payNo) {
    XcPayRecord xcPayRecord = payRecordMapper.selectOne(new LambdaQueryWrapper<XcPayRecord>().eq(XcPayRecord::getPayNo, payNo));
    return xcPayRecord;
}

完善controller中的方法:

@Value("${pay.alipay.APP_ID}")
String APP_ID;

@Value("${pay.alipay.APP_PRIVATE_KEY}")
String APP_PRIVATE_KEY;

@Value("${pay.alipay.ALIPAY_PUBLIC_KEY}")
String ALIPAY_PUBLIC_KEY;

    @ApiOperation("扫码下单接口")
    @GetMapping("/requestpay")
    public void requestpay(String payNo,HttpServletResponse httpResponse) throws IOException {
        //如果payNo不存在则提示重新发起支付
        XcPayRecord payRecord = orderService.getPayRecordByPayno(payNo);
        if(payRecord == null){
           MyException.cast("支付记录不存在,请重新点击支付获取二维码");
        }
        //支付状态
        String status = payRecord.getStatus();
        if("601002".equals(status)){
            MyException.cast("订单已支付,请勿重复支付。");
        }
        //构造sdk的客户端对象
        AlipayClient client = new DefaultAlipayClient(AlipayConfig.URL, APP_ID, APP_PRIVATE_KEY, AlipayConfig.FORMAT, AlipayConfig.CHARSET, ALIPAY_PUBLIC_KEY, AlipayConfig.SIGNTYPE);//获得初始化的AlipayClient
        AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();//创建API对应的request
        //alipayRequest.setReturnUrl("http://domain.com/CallBack/return_url.jsp");
        //alipayRequest.setNotifyUrl("http://tjxt-user-t.itheima.net/xuecheng/orders/paynotify");//在公共参数中设置回跳和通知地址
        //通知地址先注释掉
        alipayRequest.setBizContent("{" +
                " \"out_trade_no\":\""+payRecord.getPayNo()+"\"," +
                " \"total_amount\":\""+payRecord.getTotalPrice()+"\"," +
                " \"subject\":\""+payRecord.getOrderName()+"\"," +
                " \"product_code\":\"QUICK_WAP_PAY\"" +
                " }");//填充业务参数
        String form = "";
        try {
            //请求支付宝下单接口,发起http请求
            form = client.pageExecute(alipayRequest).getBody(); //调用SDK生成表单
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        httpResponse.setContentType("text/html;charset=" + AlipayConfig.CHARSET);
        httpResponse.getWriter().write(form);//直接将完整的表单html输出到页面,唤起手机支付宝客户端进行支付
        httpResponse.getWriter().flush();
        httpResponse.getWriter().close();
    }

到此,可以生成支付二维码、扫描二维码后唤起支付宝客户端进行支付。

二、需求:查询支付结果

1、需求分析

UI设计图:

  • 用户支付完成后,可点击支付完成,不用等系统自动跳回上一页面
    在这里插入图片描述

逻辑设计:

获取支付宝的支付结果,可以选择:

  • 主动查询支付结果
  • 被动接收支付结果

这里实现主动查询支付结果,当用户点击“支付完成”,通过接口请求第三方支付平台,查询支付结果。查询结果为已付款时,要更新订单表和支付记录表的支付状态字段。

2、表设计与模型类

无新增表

3、接口定义

@ApiOperation("查询支付结果")
@GetMapping("/payresult")
@ResponseBody
public PayRecordVo payresult(String payNo) throws IOException {

    //查询支付结果
    
    return null;

}

4、接口实现

Service层接口:

PayRecord queryPayResult(String payNo);

Service接口的实现,在这里做两件事:

  • 调用支付宝的接口查询支付结果
  • 拿到支付结果为成功时,更新支付记录表和订单记录表
@Override
public PayRecordVo queryPayResult(String payNo){

	//查询支付结果

	//保存支付结果到支付记录表和订单记录表
}

步骤一:查询支付结果

对这两个步骤写方法:

//定义从支付宝查询结果中封装出一个Vo返回(别光拿个支付状态,后面更新表还得要交易号等字段)
@Data
public class PayStatusVo{
	
	String out_trade_no;
	String trade_no;
	String trade_status;
	String app_id;
	String total_amount;
	
}
/**
 * 请求支付宝查询支付结果
 * @param payNo 支付交易号
 * @return 支付结果
 */
public PayStatusVo queryPayResultFromAlipay(String payNo) {

    //========请求支付宝查询支付结果=============
    AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.URL, APP_ID, APP_PRIVATE_KEY, "json", AlipayConfig.CHARSET, ALIPAY_PUBLIC_KEY, AlipayConfig.SIGNTYPE); //获得初始化的AlipayClient
    AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
    JSONObject bizContent = new JSONObject();
    bizContent.put("out_trade_no", payNo); //传入支付记录号
    request.setBizContent(bizContent.toString());
    AlipayTradeQueryResponse response = null;
    try {
        response = alipayClient.execute(request);
        if (!response.isSuccess()) {
           MyException.cast("请求支付查询查询失败");
        }
    } catch (AlipayApiException e) {
        log.error("请求支付宝查询支付结果异常:{}", e.toString(), e);
        MyException.cast("请求支付查询查询失败");
    }

    //获取支付结果
    String resultJson = response.getBody();
    //转map
    Map resultMap = JSON.parseObject(resultJson, Map.class);
    Map alipay_trade_query_response = (Map) resultMap.get("alipay_trade_query_response");
    //支付结果
    String trade_status = (String) alipay_trade_query_response.get("trade_status");
    String total_amount = (String) alipay_trade_query_response.get("total_amount");
    String trade_no = (String) alipay_trade_query_response.get("trade_no");
    //保存支付结果
    PayStatusVo payStatusVo = new PayStatusVo();
    payStatusVo.setOut_trade_no(payNo);
    payStatusVo.setTrade_status(trade_status);
    payStatusVo.setApp_id(APP_ID);
    payStatusVo.setTrade_no(trade_no);
    payStatusVo.setTotal_amount(total_amount);
    return payStatusVo;

}

//到这儿先测测这个方法,下一步的保存得用这步的数据

get、set时截图出来照着写就行
在这里插入图片描述

这里和前端联调时发现,Long类型的支付记录id,前端拿到后有精度损失。这里需要解决《Long转String后的精度损失问题》

//Long转String后的精度损失问题
//可直接在属性上加序列化注解,但Long属性太多时,挨个加很低效,直接配置这个转换类
//这样就不用在每个属性上去加
package com.xuecheng.base.config;
 
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
 
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
 
@Configuration
public class LocalDateTimeConfig {
 
    /*
     * 序列化内容
     *   LocalDateTime -> String
     * 服务端返回给客户端内容
     * */
    @Bean
    public LocalDateTimeSerializer localDateTimeSerializer() {
        return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }
 
    /*
     * 反序列化内容
     *   String -> LocalDateTime
     * 客户端传入服务端数据
     * */
    @Bean
    public LocalDateTimeDeserializer localDateTimeDeserializer() {
        return new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }
 
    //long转string避免精度损失
    @Bean
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        //忽略value为null 时 key的输出
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        SimpleModule module = new SimpleModule();
        module.addSerializer(Long.class, ToStringSerializer.instance);
        module.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(module);
        return objectMapper;
    }
 
 
    // 配置
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return builder -> {
            builder.serializerByType(LocalDateTime.class, localDateTimeSerializer());
            builder.deserializerByType(LocalDateTime.class, localDateTimeDeserializer());
        };
    }
 
}


步骤二:保存支付结果(更新订单相关表)

拿到支付结果对象后,支付成功时要更新订单表和支付记录表。(很明显,这两个动作要事务控制)注意这时调用它的方法不是事务方法。要注入自身使用代理对象,否则事务失效。

/**
 * @description 保存支付宝支付结果
 * @param payStatusDto  支付结果信息
 * @return void
 */
public void saveAliPayStatus(PayStatusVo payStatusVo) ;

接口方法实现:

@Transactional
@Override
public void saveAliPayStatus(PayStatusVo payStatusVo) {
    //支付流水号
    String payNo = payStatusVo.getOut_trade_no();
    XcPayRecord payRecord = getPayRecordByPayno(payNo);
    if (payRecord == null) {
        MyException.cast("支付记录找不到");
    }
    //支付结果
    String trade_status = payStatusVo.getTrade_status();
    log.debug("收到支付结果:{},支付记录:{}}", payStatusVo.toString(),payRecord.toString());
    if (trade_status.equals("TRADE_SUCCESS")) {

        //支付金额变为分
        Float totalPrice = payRecord.getTotalPrice() * 100;
        Float total_amount = Float.parseFloat(payStatusVo.getTotal_amount()) * 100;
        //校验是否一致
        if (!payStatusVo.getApp_id().equals(APP_ID) || totalPrice.intValue() != total_amount.intValue()) {
            //校验失败
            log.info("校验支付结果失败,支付记录:{},APP_ID:{},totalPrice:{}" ,payRecord.toString(),payStatusDto.getApp_id(),total_amount.intValue());
            MyException.cast("校验支付结果失败");
        }
        log.debug("更新支付结果,支付交易流水号:{},支付结果:{}", payNo, trade_status);
        XcPayRecord payRecord_u = new XcPayRecord();
        payRecord_u.setStatus("601002");//支付成功
        payRecord_u.setOutPayChannel("Alipay");
        payRecord_u.setOutPayNo(payStatusVo.getTrade_no());//支付宝交易号
        payRecord_u.setPaySuccessTime(LocalDateTime.now());//通知时间
        int update1 = payRecordMapper.update(payRecord_u, new LambdaQueryWrapper<XcPayRecord>().eq(XcPayRecord::getPayNo, payNo));
        if (update1 > 0) {
            log.info("更新支付记录状态成功:{}", payRecord_u.toString());
        } else {
            log.info("更新支付记录状态失败:{}", payRecord_u.toString());
            XueChengPlusException.cast("更新支付记录状态失败");
        }
        //关联的订单号
        Long orderId = payRecord.getOrderId();
        XcOrders orders = ordersMapper.selectById(orderId);
        if (orders == null) {
            log.info("根据支付记录[{}}]找不到订单", payRecord_u.toString());
            MyException.cast("根据支付记录找不到订单");
        }
        XcOrders order_u = new XcOrders();
        order_u.setStatus("600002");//支付成功
        int update = ordersMapper.update(order_u, new LambdaQueryWrapper<XcOrders>().eq(XcOrders::getId, orderId));
        if (update > 0) {
            log.info("更新订单表状态成功,订单号:{}", orderId);
        } else {
            log.info("更新订单表状态失败,订单号:{}", orderId);
            MyException.cast("更新订单表状态失败");
        }
    }

}

两个步骤的方法都写完了,完善Service中总的方法:

@Override
public PayRecordVo queryPayResult(String payNo){
    XcPayRecord payRecord = getPayRecordByPayno(payNo);
    if (payRecord == null) {
        MyException.cast("没有支付记录,请重新点击支付获取二维码");
    }
    //支付状态
    String status = payRecord.getStatus();
    //如果查到的支付巨鹿中,已经是支付成功,则直接返回
    if ("601002".equals(status)) {
        PayRecordVo payRecordVo = new PayRecordVo();
        BeanUtils.copyProperties(payRecord, payRecordVo);
        return payRecordVo;
    }
    //从支付宝查询支付结果
    PayStatusVo payStatusVo = queryPayResultFromAlipay(payNo);
    //保存支付结果
    //代理对象,避免事务控制失效
    currentProxy.saveAliPayStatus( payStatusVo);
    //更新保存完支付结果后,重新查询支付记录
    payRecord = getPayRecordByPayno(payNo);
    PayRecordVo payRecordVo = new PayRecordVo();
    BeanUtils.copyProperties(payRecord, payRecordVo);
    return payRecordVo;

}


5、完善Controller

@ApiOperation("查询支付结果")
@GetMapping("/payresult")
@ResponseBody
public PayRecordVo payresult(String payNo) throws IOException {
    //调用支付宝接口查询
    PayRecordVo payRecordVo = orderService.queryPayResult(payNo);
    return payRecordVo;
}

到此,点击支付完成,可主动查询支付结果

三、接收支付通知

支付成功后,第三方支付系统会主动通知支付结果,要想收到通知,得在请求支付系统下单时传入两个URL:

  • ReturnUrl:支付完成后支付系统携带支付结果重定向到ReturnUrl地址
  • NotifyUrl:支付完成后支付系统在后台异步定时去通知,比使用ReturnUrl更有保证

接口定义与实现:

@ApiOperation("接收支付结果通知")
@PostMapping("/receivenotify")
public void receivenotify(HttpServletRequest request) throws UnsupportedEncodingException, AlipayApiException {
    Map<String,String> params = new HashMap<String,String>();
    Map requestParams = request.getParameterMap();
    for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
        String name = (String) iter.next();
        String[] values = (String[]) requestParams.get(name);
        String valueStr = "";
        for (int i = 0; i < values.length; i++) {
            valueStr = (i == values.length - 1) ? valueStr + values[i]
                    : valueStr + values[i] + ",";
        }
        params.put(name, valueStr);
    }

    //验签
    boolean verify_result = AlipaySignature.rsaCheckV1(params, ALIPAY_PUBLIC_KEY, AlipayConfig.CHARSET, "RSA2");

    if(verify_result) {//验证成功

        //商户订单号
        String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8");
        //支付宝交易号
        String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8");
        //交易状态
        String trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"),"UTF-8");
        //appid
        String app_id = new String(request.getParameter("app_id").getBytes("ISO-8859-1"),"UTF-8");
        //total_amount
        String total_amount = new String(request.getParameter("total_amount").getBytes("ISO-8859-1"),"UTF-8");

        //交易成功处理
        if (trade_status.equals("TRADE_SUCCESS")) {

            PayStatusVo payStatusVo = new PayStatusVo();
            payStatusVo.setOut_trade_no(out_trade_no);
            payStatusVo.setTrade_status(trade_status);
            payStatusVo.setApp_id(app_id);
            payStatusVo.setTrade_no(trade_no);
            payStatusVo.setTotal_amount(total_amount);

           //保存支付状态到各个表(主动查询结果中定义的方法,已经暴露成接口了,可直接调用)
            orderService.saveAliPayStatus(payStatusVo);
        }
    }


}

更改请求第三方支付系统下单时的传参,指定异步通知的地址(上面定义的接口)

//指定notify通知url
alipayRequest.setNotifyUrl("http://tjxt-user-t.itheima.net/xuecheng/orders/receivenotify");

到此,被动等着接收第三方支付系统通知支付结果实现。主动和被动都实现了,支付结果一定可以拿到,本地订单表也会被更新(不会重复更新,上面代码中已经写了:如果本地表状态为已支付,则直接返回)

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

-代号9527

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

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

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

打赏作者

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

抵扣说明:

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

余额充值