【Java实战篇】Day13.在线教育网课平台--生成支付二维码与完成支付

news/2024/4/19 3:30:10/文章来源:https://blog.csdn.net/llg___/article/details/130357977

文章目录

  • 一、需求:生成支付二维码
    • 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://www.luyixian.cn/news_show_103344.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VUE3如何定义less全局变量

默认已经安装好了less&#xff0c;这里不过多讲。 &#xff08;1&#xff09;首先我们需要下载一个插件依赖&#xff1a; npm i style-resources-loader --save-dev &#xff08;2&#xff09;VUE3里配置vue.config.js文件内容 代码&#xff1a; const path require("p…

HashMap如何解决哈希冲突

HashMap如何解决哈希冲突 Hash算法和Hash表Hash冲突解决哈希冲突的方法开放地址法链式寻址法再hash法建立公共溢出区 Hash算法和Hash表 Hash算法就是把任意长度的输入通过散列算法编程固定长度的输出。这个输出结果就是一个散列值。 Hash表又称为“散列表”&#xff0c;它是通…

SpringBoot中一个注解优雅实现重试Retry框架

目录: 1、简介2、实现步骤 1、简介 重试&#xff0c;在项目需求中是非常常见的&#xff0c;例如遇到网络波动等&#xff0c;要求某个接口或者是方法可以最多/最少调用几次&#xff1b;实现重试机制&#xff0c;非得用Retry这个重试框架吗&#xff1f;那肯定不是&#xff0c;相信…

Mysql 查询同类数据中某一数字最大的所有数据

方法一、将时间进行排序后再分组 该表表名为customer, park_id表示园区id&#xff0c;joined_at表示用户的加入时间&#xff0c;created_at表示用户的创建时间。 需求&#xff1a;查出每个园区中&#xff0c;最早加入园区的第一位用户 select * from (select * from custome…

数据库课设--基于Python+MySQL的餐厅点餐系统(表的设计)

文章目录 一、系统需求分析二、系统设计1. 功能结构设计2、概念设计2.2.1 bill_food表E-R图2.2.2 bills表E-R图2.2.3 categories E-R图2.2.4 discounts表 E-R图2.2.5 emp表E-R图2.2.6 food 表E-R图2.2.7 member表E-R图2.2.8 member_point_bill表E-R图2.2.9 servers表E-R图2.2.1…

操作系统考试复习—第二章 2.1 2.2程序和进程的描述

第二章 进程的描述与控制 程序&#xff1a;有序的指令集合 程序顺序执行的特征&#xff1a;1.顺序性 2.封闭性 3.可再现性(确定性) 在多道程序环境下&#xff0c;允许多个程序并发执行&#xff0c;此时他们将失去封闭性&#xff0c;并具有间断性和不可再现性的特征。为此引…

基于SGM431的电路设计问题分析

本案例中,采用SGM431芯片设计了一个过压保护电路。 这个电路初次设计,有很多的问题,下面逐一分析 1.当输入24V,测得Vref=1.59V。Vout为1.15V;,mos管关断 2。经过多次测量发现,临界值在10V到10.5之间; 当输入10.5V时,测量Vref=1.69V。vout=1.15V;mos管关断 当输入1…

智慧物联网边缘协同感知(EICS)技术方案: 低功耗无线扫描唤醒技术

物联网的传感器或控制节点通常有体积限制&#xff0c;只能使用钮扣电池、小型电池&#xff0c;甚至使用能量收集源进行运作。在许多工业应用中&#xff0c;需要人工更换电池的成本&#xff0c;特别是在难以接近地方更换所需的成本&#xff0c;使得人们更加重视降低平均电流消耗…

深度学习入门到实践:相关基础概述

绪论 深度学习&#xff08;Deep Learning&#xff09;是近年来发展十分迅速的研究领域&#xff0c;并且在人工智能的很多子领域都取得了巨大的成功。从根源来讲&#xff0c;深度学习是机器学习的一个分支&#xff0c;是指一类问题以及解决这类问题的方法。     深度学习问题…

halcon灰度积分投影/垂直积分投影

简介:关于灰度投影积分可以用到的场合很多,例如分割字符,分割尺子上的刻度等,适用于有规律的变化这些内容的检测。本文复现了论文《基于深度学习和灰度纹理特征的铁路接触网绝缘子状态检测》中灰度积分投影实现了对绝缘子缺陷位置的检测。见(图1)灰度积分垂直方向投影获得…

AI智能智能课程第四讲 -数据库领域专家

使用chatGPT让你成为数据库领域专家 作业 现在要测试电商的下单功能&#xff1a;测试员张三在公司的电商平台上下了几个单&#xff0c;现在需要验证&#xff1a;张三这个客户下单的所有订单信息&#xff0c;包含订单编号&#xff0c;商品名称&#xff0c;商品价格&#xff0c;…

分支和循环语句(2)

文章目录 3.2 for循环3.2.1 for语句的语法3.2.2 for循环中的break和continue3.2.3 for语句的循环控制变量3.2.4 一些for循环的变种3.2.5 一道笔试题 3.3 do while循环3.3.1 do语句的语法3.3.2 do语句的特点3.3.3 do while循环中的break和continue 3.4 练习3.4.1 计算 n的阶乘3.…

Compiler- 尾调用

我们还是用例子来引入本次要探讨的问题--尾调用 #include <stdio.h>int fib(int a) {return a < 2 ? 1 : fib(a - 1) fib(a - 2); }int main() {int n,result;scanf("%d",&n);result fib(n);printf("result is %d.\n",result);return 0; …

创建路由React router(使用react-router dom V6版本)

React路由 隔了很长一段时间&#xff0c;重新捡起来React学习。 发现React的路由从原来的 Switch改成了Routes。nice&#xff0c;nice&#xff0c;nice&#xff01;&#xff01;&#xff01;&#xff01; 刚开始接触确实还是有一点生疏的。之前的关于【传参】【js跳转】【跳转模…

矿井下无人值守变电所电力监控系统的探讨与产品选型

摘要&#xff1a;为了探讨井下无人值守变电所的电力监控系统技术&#xff0c;以西山煤电马兰矿为背景&#xff0c;详细阐述了井下无人值守变电所电力监控系统技术的各项基本参数&#xff0c;如额定工作电压及整机输入视在功率、交换机或监控分站的传输口、高压配电装置的传输口…

下载VMWare

1、首先登录到vmware官网 官网&#xff1a;https://www.vmware.com/ 2、点击Resource 3、找到Product Downloads 4、找到我们要下载的产品&#xff0c;点击download product 5、选择自己要下载的版本和对应的系统 6、点击去下载 7、点击download now

国云筑基“翼”气风发,天翼云以科技创新绘就数字中国蓝图

科技云报道原创。 全球新一轮技术革命方兴未艾&#xff0c;特别是以数字技术为核心的信息技术革命&#xff0c;正在实现群体突破和加快广泛深度应用。 从2017年的“促进数字经济加快成长”&#xff0c;到2019年的“壮大数字经济”&#xff0c;到2020年的“全面推进‘互联网&am…

SpringBoot的配置和日志

1.配置文件的作用和意义 配置文件中配置整个项目中所有重要的数据&#xff0c;比如&#xff1a; 1.数据库的连接信息&#xff08;包含用户名和密码的设置&#xff09;&#xff1b; 2.项目的启动端口&#xff1b; 3.第三方系统的调用秘钥等信息&#xff1b; 4.用于发现和定位问…

Unity之OpenXR+XR Interaction Toolkit实现 抓取物体

前言 我们今天来说一下如何使用XR Interaction Toolkit来实现和3D物体的交互之&#xff1a;抓取&#xff0c;简单说就是通过VR手柄拿起来一个物体。 二.准备工作 有了前两篇的配置介绍,我们就不在详细说明这些了&#xff0c;大家自行复习 Unity之OpenXRXR Interaction Toolk…

BPF技术学习与整理

目录 eBPF是什么&#xff1f; eBPF是做什么的&#xff1f;可以解决什么问题&#xff1f; eBPF可以带来的解决方案是什么&#xff1f; eBPF的技术点 eBPF hookeBPF MapeBPF Helper FunctioneBPF有什么限制吗&#xff1f; 前言 21年因为项目需求而要开发一个工具&#xff0c;可以…