学成在线-生成扫码下单接口的二维码同时创建创建商品订单记录和交易支付记录

news2024/12/25 10:08:54

生成下单接口二维码

界面原型

打开课程支付引导界面,点击支付宝支付按钮商户系统生成下单的二维码接口,用户扫描二维码后商户系统开始请求支付宝下单

在这里插入图片描述

用户扫码开始请求支付宝下单,但是在生成下单接口的二维码前前端需要做一些操作

  • 前端调用学习中心服务的添加课程的接口(收费课程只添加选课记录)–>添加选课记录成功后前端继续调用订单服务生成二维码的接口
    在这里插入图片描述

用户扫码支付流程

在这里插入图片描述

数据模型

订单表:记录订单信息

  • out_business_id: 与订单相关的业务主键,如选课记录Id

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

订单明细表: 记录订单的详细信息

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

支付交易记录表记录每次请求微信或支付宝下单接口时支付的交易明细,关联商品订单,对于支付成功的商品订单会修改订单的状态,且下次不会再为其创建支付记录

  • 传入商品订单号: 当用户支付失败或因为其他原因导致该订单没有支付成功,此时再次调用第三方支付平台的下单接口就会提示订单号已存在,对于没有支付成功的订单重新创建一个新订单是不合理的
  • 传入支付交易记录的流水号: 用户每次发起支付请求都会创建一个新的支付交易记录,此交易记录与商品订单关联,当给订单生成支付记录前需要判断订单的支付状态,如果订单支付成功则不再生成, 避免出现用户对同一个订单重复支付的情况

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

订单号生成方案

生成的订单要具有: 唯一性、安全性、尽量短等特点

  • 一般场景: 时间戳+随机数: 年月日时分秒毫秒+随机数

  • 高并发场景: 年月日时分秒毫秒+随机数+redis自增序列

  • 特殊业务场景:订单号中加上业务标识,如淘宝订单号加上业务标识方便客服处理业务需求,比如第10位是业务类型,第11位是用户类型等

  • 雪花算法: 推特内部使用的分布式环境下的唯一ID生成算法,它基于时间戳生成保证有序递增,加以入计算机硬件等元素,可以满足高并发环境下ID不重复

在base工程的utils包下创建IdWorkerUtils基于雪花算法生成订单号

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());
	}
}

生成订单对应二维码接口

在订单服务中定义生成订单对应的支付二维码接口,同时完成创建商品订单记录和支付交易记录的操作

@Api(value = "订单支付接口", tags = "订单支付接口")
@RestController
@Slf4j
public class OrderController {
    @Autowired
    OrderService orderService;

    @ApiOperation("生成支付二维码")
    @PostMapping("/generatepaycode")
    public PayRecordDto generatePayCode(@RequestBody AddOrderDto addOrderDto) {
        SecurityUtil.XcUser user = SecurityUtil.getUser();
        if (user == null) {
            XueChengPlusException.cast("请登录后继续选课");
        }
        return orderService.createOrder(user.getId(), addOrderDto);
    }
}

请求响应模型类

请求模型类: 包含商品订单的相关信息

@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;
}

响应模型类:包含商品订单对应的支付交易记录信息二维码信息

@Data
@ToString
public class PayRecordDto extends XcPayRecord {
    //二维码
    private String qrcode;
}

创建商品订单记录和交易支付记录

定义OrderService接口及其实现类, 生成商品订单二维码前需要完成创建商品订单(同一个选课只能创建一个订单) --->创建订单的交易支付记录(支付成功的订单不再创建)-->生成订单的二维码的操作

public interface OrderService {
    /**
     * 创建商品订单二维码,同时完成创建商品订单记录和支付交易记录的操作
     * @param userId        用户id
     * @param addOrderDto   订单信息
     * @return  支付交易记录
     */
    PayRecordDto createOrder(String userId, AddOrderDto addOrderDto);
}
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    XcOrdersMapper xcOrdersMapper;
    @Autowired
    XcPayRecordMapper xcPayRecordMapper;
    @Autowired
    XcOrdersGoodsMapper xcOrdersGoodsMapper;

    @Value("${pay.qrcodeurl}")
    String qrcodeurl;
    @Override
    public PayRecordDto createOrder(String userId, AddOrderDto addOrderDto) {
        // 1. 创建商品订单记录
        XcOrders orders = saveOrders(userId, addOrderDto);
        // 2. 创建订单对应的支付交易记录
        XcPayRecord payRecord = createPayRecord(orders);
        // 3. 生成二维码
        String qrCode = null;
        try {
            // 3.1 用本系统支付交易号填充占位符
            qrcodeurl = String.format(qrcodeurl, payRecord.getPayNo());
            // 3.2 生成二维码
            qrCode = new QRCodeUtil().createQRCode(qrcodeurl, 200, 200);
        } catch (IOException e) {
            XueChengPlusException.cast("生成二维码出错");
        }
        PayRecordDto payRecordDto = new PayRecordDto();
        BeanUtils.copyProperties(payRecord, payRecordDto);
        payRecordDto.setQrcode(qrCode);
        return payRecordDto;
    }
}

创建商品订单记录和订单中包含的具体产品记录: 订单记录的订单信息来源于对应的选课记录,所以在订单表需要存入选课记录的ID

  • 幂等性判断: 同一个选课记录只能创建一个订单
@Autowired
XcOrdersMapper xcOrdersMapper;

@Autowired
XcOrdersGoodsMapper xcOrdersGoodsMapper;

/**
 * 保存订单信息到订单表和订单明细表
 * @param userId      用户id
 * @param addOrderDto 选课信息
 * @return
 */
@Transactional
public XcOrders saveOrders(String userId, AddOrderDto addOrderDto) {
    // 1. 幂等性判断,同一个选课记录只能创建一个订单
    XcOrders order = getOrderByBusinessId(addOrderDto.getOutBusinessId());
    if (order != null) {
        return order;
    }
    // 2. 插入订单表
    order = new XcOrders();
    BeanUtils.copyProperties(addOrderDto, order);
    order.setId(IdWorkerUtils.getInstance().nextId());
    order.setCreateDate(LocalDateTime.now());
    order.setUserId(userId);
    order.setStatus("600001");//未支付
    order.setUserId(userId);
    order.setOrderType(addOrderDto.getOrderType());// 订单类型,如60201表示购买课程
    order.setOrderName(addOrderDto.getOrderName());
    order.setOrderDetail(addOrderDto.getOrderDetail());
    order.setOrderDescrip(addOrderDto.getOrderDescrip());
    order.setOutBusinessId(addOrderDto.getOutBusinessId());// 如购买课程业务对应选课记录id
    int insert = xcOrdersMapper.insert(order);
    if (insert <= 0) {
        XueChengPlusException.cast("插入订单记录失败");
    }
    // 3. 插入订单明细表,包含所属商品订单记录的Id
    Long orderId = order.getId();
    // 将前端传入的订单明细的json字符串转成List集合,集合元素类型是XcOrdersGoods订单产品类型
    String orderDetail = addOrderDto.getOrderDetail();
    List<XcOrdersGoods> xcOrdersGoodsList = JSON.parseArray(orderDetail, XcOrdersGoods.class);
    xcOrdersGoodsList.forEach(goods -> {
        goods.setOrderId(orderId);
        int insert1 = xcOrdersGoodsMapper.insert(goods);
        if (insert1 <= 0) {
            XueChengPlusException.cast("插入订单明细失败");
        }
    });
    return order;
}


//根据业务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 (orders == null) {
        XueChengPlusException.cast("订单不存在");
    }
    // 对于支付成功的订单不再创建交易记录避免重复支付
    if ("600002".equals(orders.getStatus()) {
        XueChengPlusException.cast("订单已支付");
    }
    // 添加支付记录
    XcPayRecord payRecord = new XcPayRecord();
    payRecord.setPayNo(IdWorkerUtils.getInstance().nextId());// 本系统生成的支付交易记录号将来传给支付宝
    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 = xcPayRecordMapper.insert(payRecord);
    if (insert <= 0) {
        XueChengPlusException.cast("插入支付交易记录失败");
    }
    return payRecord;
}

生成支付二维码: 在Nacos中dev环境下的orders-service-dev.yaml中配置二维码的URL要求支付的时候也需要带上订单号

pay:
  qrcodeurl: http://127.0.0.1:63030/orders/requestpay?payNo=%s
@Value("${pay.qrcodeurl}")
String qrcodeurl;

// 生成二维码
String qrCode = null;
try {
    // 用本系统支付交易号填充占位符
    qrcodeurl = String.format(qrcodeurl, payRecord.getPayNo());
    // 生成二维码
    qrCode = new QRCodeUtil().createQRCode(qrcodeurl, 200, 200);
} catch (IOException e) {
    XueChengPlusException.cast("生成二维码出错");
}

重启所有服务,测试二维码是否可以正常生成,同时查看数据库中的选课记录表、订单表(包含选课记录Id)、订单明细表(包含商品订单Id)、支付交易记录表(包含商品订单Id)对应记录

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1513017.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue项目因内存溢出启动报错

前端能正常启动&#xff0c;但只要一改动就报错启动出错。 解决办法&#xff1a; 安装依赖 npm install cross-env increase-memory-limit 然后再做两件事&#xff1a;在node 在package.json 里的 script 里进行配置 LIMIT是你想分配的内存大小&#xff0c;这里的8192单位…

手势学习

1. 点击手势 Composable fun ClickableSample() {val number remember {mutableStateOf(0)}Text(text number.value.toString(),textAlign TextAlign.Center,modifier Modifier.wrapContentSize().clickable {number.value}.background(Color.LightGray).padding(horizonta…

UE5.2 SmartObject使用实践

SmartObject是UE5新出的一项针对AI的功能&#xff0c;可为开发者提供如公园长椅、货摊等交互对象的统一外观封装&#xff0c;如UE的CitySample&#xff08;黑客帝国Demo&#xff09;中就运用到了SmartObject。 但SmartObject实践起来较为繁琐&#xff0c;主要依赖于AI及行为树…

python爬虫(9)之requests模块

1、获取动态加载的数据 1、在开发者工具中查看动态数据 找到csdn的门户的开发者工具后到这一页面。 2、加载代码 import requests headers {User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36…

iOS17.4获取UDID安装mobileconfig描述文件失败 提示“安全延迟进行中”问题 | 失窃设备保护

iOS17.4这两天已经正式发布&#xff0c; 在iOS 17.4版本中新增了一个名为"失窃设备保护"的功能&#xff0c;并提供了一个"需要安全延迟"的选项。 iOS17.4获取UDID安装mobileconfig描述文件失败 提示“安全延迟进行中”问题 | 失窃设备保护 当用户选择启用…

#LLM入门|Prompt#2.8_搭建一个带评估的端到端问答系统

在这一章节中&#xff0c;我们将会构建一个集成评估环节的完整问答系统。这个系统将会融合我们在前几节课中所学到的知识&#xff0c;并且加入了评估步骤。以下是该系统的核心操作流程&#xff1a; 对用户的输入进行检验&#xff0c;验证其是否可以通过审核 API 的标准。若输入…

植物病虫害:YOLO玉米病虫害识别数据集

玉米病虫害识别数据集&#xff1a;玉米枯萎病&#xff0c;玉米灰斑病&#xff0c;玉米锈病叶&#xff0c;粘虫幼虫&#xff0c;玉米条斑病&#xff0c;黄二化螟&#xff0c;黄二化螟幼虫7类&#xff0c;yolo标注完整&#xff0c;3900多张图像&#xff0c;全部原始数据&#xff…

青少年软件编程(图形化)等级考试试卷(三级)

青少年软件编程&#xff08;图形化&#xff09;等级考试试卷&#xff08;三级&#xff09; 分数&#xff1a;100 题数&#xff1a;38 总体情况姓名开始时间结束时间用时(分钟)得分得分率是否通过考试状态xXx2023-03-192023-03-1959.5769.50.7Y已评卷 一、单选题(共25题&#x…

数据结构之树(Topk问题, 链式二叉树)

一.topk问题 取N个数中最大(小)的前k个值,N远大于k 这道题可以用堆的方法来解决,首先取这N个数的前k个值,用它们建堆 时间复杂度O(k) 之后将剩余的N-k个数据依次与堆顶数据进行比较,如果比堆顶数据大,则将堆顶数据覆盖后向下调整 时间复杂度(N-k)*log(N) 总共的时间复杂度…

2024年独立站C端只靠SEO还有机会吗?(川圣SEO)蜘蛛池

baidu搜索&#xff1a;如何联系八爪鱼SEO&#xff1f; baidu搜索&#xff1a;如何联系八爪鱼SEO&#xff1f; baidu搜索&#xff1a;如何联系八爪鱼SEO&#xff1f; 2024年独立站C端只靠SEO还有机会吗&#xff1f;#蜘蛛池SEO 都2024年了&#xff0c;你的外贸独立站推广还需要…

Xilinx 7系列FPGA的配置流程

目录 1.4配置流程 1.4.1 设备上电 ​编辑1.4.2 清除配置寄存器 1.4.3 采样模式引脚 1.4.4 同步 ​编辑1.4.5 检测设备ID ​编辑1.4.6 加载配置数据 1.4.7 CRC校验 1.4.8 启动序列 1.4配置流程 对于所有配置模式&#xff0c;7系列的基本配置流程都是相同的&…

高速USB3.0接口控制器芯片--T630

T630芯片是方寸微电子自主研发的USB3.0超高速控制器&#xff0c;具有功能丰富、性能强劲、扩展性强等特点&#xff0c;可广泛应用于视频采集卡、视频会议摄像头、监控摄像头、数字摄录机、工业照相机、测量和测试设备、医疗成像设备、打印机、扫描仪、指纹采集终端等众多电子产…

记事小本本

记事小本本 实现效果 相关代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</titl…

TRANSCEIVER 中的LPM和DFE模块

接收端都使用了通信中常用的LPM和DFE 本质上是高通滤波器&#xff0c;一个是单项被动&#xff0c;而常用的DFE用 了反馈。参见这篇不错的文章。 赛灵思7系列FPGA GT收发器中的RX均衡器_lpm和dfe-CSDN博客

C#,T检验(T -Test)的算法与源代码

1 T-Test 学生t检验(英语:Students t-test)是指虚无假设成立时的任一检定统计有学生t-分布的统计假说检定,属于母数统计。学生t检验常作为检验一群来自正态分配母体的独立样本之期望值的是否为某一实数,或是二群来自正态分配母体的独立样本之期望值的差是否为某一实数。举…

Android自定义view从入门到高级

简介 什么是自定义view&#xff1f;我认为只要不是编译器直接提供可以使用的view&#xff0c;都可以认为是自定义view。自定义view主要分为两大类&#xff0c;第一类自定义view可以通过系统提供的各种view组合&#xff0c;样式变化实现的view。第二类是通过继承view或者ViewGro…

​高山 MPV 四驱旗舰版:新能源时代的豪华出行新选择

随着新能源技术的不断进步和消费者对高端出行体验的追求&#xff0c;MPV&#xff08;Multi-Purpose Vehicle&#xff0c;多用途车&#xff09;市场迎来了新的发展机遇。 长城汽车旗下的魏牌&#xff0c;凭借其在新能源领域的深厚技术积累&#xff0c;推出了全新的高山MPV四驱旗…

用通俗易懂的方式讲解:大模型 Rerank 模型部署及使用技巧总结

Rerank 在 RAG&#xff08;Retrieval-Augmented Generation&#xff09;过程中扮演了一个非常重要的角色&#xff0c;普通的 RAG 可能会检索到大量的文档&#xff0c;但这些文档可能并不是所有的都跟问题相关&#xff0c;而 Rerank 可以对文档进行重新排序和筛选&#xff0c;让…

【Python 5】----Pytest接口自动化(实现基础的测试框架)

安装准备 安装好pytest的环境及allure环境 1. 安装pytest pip insatll pytest2.安装allure (需要确保安装了jdk环境&#xff09;安装allure命令行&#xff1a; 访问allure官网&#xff0c;下载allure2.13.5的安装包&#xff0c;将其bin路径 添加进环境变量path中在cmd里面…

flutter选择国家或地区的电话号码区号

1.国家区号列表&#xff08;带字母索引侧边栏&#xff09; import package:generated/l10n.dart; import package:widget/login/area_index_bar_widget.dart; import package:flutter/material.dart; import package:flutter_screenutil/flutter_screenutil.dart;class LoginA…