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

article/2025/7/13 21:06:54

文章目录

  • 一、需求:生成支付二维码
    • 1、需求分析
    • 2、表设计
    • 3、接口定义
    • 4、接口实现
    • 5、完善controller
  • 二、需求:查询支付结果
    • 1、需求分析
    • 2、表设计与模型类
    • 3、接口定义
    • 4、接口实现
      • 步骤一:查询支付结果
      • 步骤二:保存支付结果(更新订单相关表)
    • 5、完善Controller
  • 三、接收支付通知

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

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")@ResponseBodypublic 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 {@AutowiredXcOrdersMapper ordersMapper;@AutowiredXcOrdersGoodsMapper ordersGoodsMapper;@AutowiredXcPayRecordMapper payRecordMapper;@Transactional@Overridepublic 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());//选课记录idint 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);//获得初始化的AlipayClientAlipayTradeWapPayRequest 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); //获得初始化的AlipayClientAlipayTradeQueryRequest 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();//转mapMap 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* 服务端返回给客户端内容* */@Beanpublic LocalDateTimeSerializer localDateTimeSerializer() {return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));}/** 反序列化内容*   String -> LocalDateTime* 客户端传入服务端数据* */@Beanpublic LocalDateTimeDeserializer localDateTimeDeserializer() {return new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));}//long转string避免精度损失@Beanpublic 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;}// 配置@Beanpublic 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");//appidString app_id = new String(request.getParameter("app_id").getBytes("ISO-8859-1"),"UTF-8");//total_amountString 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");

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


http://chatgpt.dhexx.cn/article/5N6fhR1f.shtml

相关文章

简单的订单系统

目录 一、数据库方面 二、jdbc配置文件 三、JDBC工具类 三、Users类 四、功能实现类 五、运行结果 一、数据库方面 USE foodmenu; DROP TABLE IF EXISTS menu7; CREATE TABLE menu7(num INT PRIMARY KEY,TypeDishes VARCHAR(255))CHARSETutf8; INSERT INTO menu7(num,Type…

天翎低代码平台实现的订单管理系统

编者按&#xff1a; 本文 主要介绍了基于天翎低代码平台实现的 订单管理系统以及 优势 &#xff0c;并进一步展现了低代码平台是如何为企业实现订单管理科学化 和 规范业务管理过程。 关键词&#xff1a;低代码平台、订单管理系统 1.订单管理系统是什么&#xff1f; 订单管理系…

初学订单-支付流程(思路)

主要说的是 生成订单的一系列操作 生成订单号---确认支付---生成支付链接--支付流程 支付流程 ---1.获取支付链接 1.1 三方接口&#xff0c;发送数据 ----1.2 返回数据解析&#xff08;包含支付订单id&#xff09;将链接也返回前端 ----2.进行支付 2.1 扫码支付 2.2 支付成…

订单系统的代码实现

面向接口的编程&#xff1a; 面向接口编程(Interface Oriented Programming:OIP)是一种编程思想&#xff0c;接口作为实体抽象出来的一种表现形式&#xff0c;用于抽离内部实现进行外部沟通&#xff0c;最终实现内部变动而不影响外部与其他实现交互&#xff0c;可以理解成按照这…

【码学堂】教师如何在码学堂上组织教学活动?

码学堂简介 码学堂是由贵州师范学院数学与大数据学院研发的智慧教学平台&#xff0c;学生可以自主练习&#xff0c;教师可以组织练习、考试、竞赛、共享题库、共享教学资源&#xff0c;支持判断题、单项选择题、多项选择题、填空题、程序函数题、程序填空题、编程题、主观题8种…

如何在码学堂组织练习、考试、竞赛?

组织练习、考试、竞赛时就是将多个题目组成题目集&#xff0c;然后加入学生组完成。题目集是由多个题目构成的集合&#xff0c;可以理解为组卷、出卷&#xff0c;码学堂上“练习/作业”、“考试”或“竞赛”操作方式一致&#xff0c;故下面以考试为例来说明操作方法。 1 设置题…

如何开发出一款直播APP项目实践篇 -【原理篇】

【 主要模块】 主播端&#xff1a; 把主播实时录制的视频&#xff0c;经过&#xff08;采集、美颜处理、编码&#xff09;推送到服务器服务器&#xff1a; 处理&#xff08;转码、录制、截图、鉴黄&#xff09;后分发给用户播放端播放器&#xff1a; 获取服务器地址&#xff0…

短视频小视频直播app开发定制解决方案

一、直播APP的市场前景 随着智能移动手机端的普及,人们对于线上的娱乐的要求越发感兴趣,很多互联网电商平台也将直播APP作为销售的主战场之一。将线上与线下的方式相结合才能更好的促进企业的发展。当然对于直播APP的开发也是我们需要了解的。相关数据表明,目前直播APP对于…

直播APP开发过程

直播是2016年火爆的产业&#xff0c;看起来很炫&#xff0c;玩起来很方便、很贴近生活&#xff0c;开发一款直播App不仅耗时还非常昂贵&#xff0c;那么&#xff0c;开发一款直播App到底分几步走&#xff1f; 第一步&#xff1a;分解直播App的功能&#xff0c;我们以X客为例 1…

直播app开发必备五步流程

直播app开发搭建是最近几年比较火的技术&#xff0c;本文从技术角度分析一套直播app开发必备的几个流程。 从主播录制视频开始到最后直播间播放&#xff0c;涉及到的流程包括&#xff1a; 音视频采集—>编码和封装—>推流到流媒体服务器—>流媒体服务器播流分发—&g…

金融直播APP方案开发

分享一下英唐众创开发的金融直播APP解决方案。随着视频直播风靡全球&#xff0c;视频直播已成为众多传统行业和互联网行业争夺的“香饽饽”。金融行业当然也不例外&#xff0c;在当今“互联网”的大时代下&#xff0c;金融行业作为走在前沿的产业&#xff0c;不但开辟出互联网金…

如何开发出一款仿映客直播APP项目实践篇 -【原理篇】

前言&#xff1a;每个成功者多是站在巨人的肩膀上&#xff01;在做直播开发时 碰到了很多问题&#xff0c;在收集了许多人博客的基础上做出来了成功的直播项目并做了整理&#xff0c;并在最后奉上我的全部代码。 其中采用博客的博主开篇在此感谢&#xff0c;本着开源分享的精神…

cmd的炫酷玩法教程

在我们看电影的时候&#xff0c;经常看到黑客在电脑是一顿猛如虎的操作。然后电脑上就出现一系列花里胡哨的画面&#xff0c;其实那种画面我们用cmd的一行代码就能搞定。 第一步 按WinR&#xff0c;输入cmd&#xff0c;打开小黑框。 第二部 如果什么属性都不设置&#xff…

一行代码让你伪装成黑客惊艳世人

今天给大家带来一行代码让你伪装成黑客惊艳世人&#xff0c;保证让你成为学校机房最亮的崽 新建一个文本文档&#xff0c;输入tree c: CtrlS保存 重命名修改后缀名为.bat 这就OK了&#xff0c;不知道这个代码你有没有学废了&#xff01;

小bat大装逼(▼へ▼メ)

直接上代码 echo off cls color echo come!!! color 1a color 2b color 3c color 4d color 5e color 6f color 70 tree d: dir /s %0把代码粘贴到一个【文件名.bat】文件中&#xff0c;例如 复制粘贴完成&#xff0c;别忘记【Ctrls】进行保存操作啊。 然后打开就行了。很疯狂…

使用cmd命令行装逼,让命令行滚动起来

使用cmd命令行装逼&#xff0c;让命令行滚动起来 一、滚动cmd二、清理垃圾总结 一、滚动cmd color a扫描当前所有目录 dir /s二、清理垃圾 创建txt文件 echo offdel/f/s/q %systemdrive%\*.tmp del/f/s/q %systemdrive%\*._mp del/f/s/q %systemdrive%\*.logdel/f/s/q %sys…

(六)C语言入门,代码编程,三子棋游戏【300行】【原创】

文章目录 十二篇文章汇总&#xff0c;独家吐大血整理 编译环境 游戏界面 test6.c game.c game.h​​​​​​​ ​​​​​​​ 编译环境 VS2019 游戏界面 test6.c #include <stdio.h>//std standard input output #include <string.h> #include <game…

C语言编程流程

2.5 C语言编程流程 C语言的编程流程 a)解决什么问题 b)怎么解决 c)编写程序 d)分析结果 2. 解决什么问题 &#xff1f; 比如说&#xff1a;我们如何打开、关闭一个计算器呢&#xff1f; 3 那么我们怎么解决呢&#xff1f; 我们可以通过命令行的方式打开、关闭计算器 在DO…

c语言万年历查询程序代码,C语言实现万年历程序的代码分享

C语言实现万年历程序的代码分享 发布时间&#xff1a;2020-04-27 09:55:52 来源&#xff1a;亿速云 阅读&#xff1a;795 作者&#xff1a;小新 今天小编给大家分享的是C语言实现万年历程序的代码&#xff0c;相信很多人都不太了解&#xff0c;为了让大家更加了解C语言实现万年…

C语言基础部分代码

这是我大一的时候新入门C语言整个语法的时候&#xff0c;课堂中所练习的一些基础问题的源码&#xff0c;现分享给新入门C语言的小白&#xff0c;以便于学习了解C语言。 目录 1.将一个正整数分解质因数 2.判断一个数是否为水仙花数 3.找出1000以内的所有完数 4.求一个数是否为素…