> 技术文档 > Web虚拟卡销售店铺实现方案_微信小店 可以卖虚拟充值卡吗

Web虚拟卡销售店铺实现方案_微信小店 可以卖虚拟充值卡吗


文章目录

    • 1. 项目概述
      • 1.1 项目背景
      • 1.2 系统架构
    • 2. 技术选型与环境搭建
      • 2.1 后端技术栈
      • 2.2 前端技术栈
      • 2.3 开发环境配置
    • 3. 数据库设计
      • 3.1 数据库ER图
      • 3.2 数据表设计
    • 4. 后端实现
      • 4.1 Spring Boot项目结构
      • 4.2 核心功能实现
        • 4.2.1 用户认证与授权
        • 4.2.2 虚拟卡管理
        • 4.2.3 订单服务
        • 4.2.4 微信支付集成
    • 5. 前端实现
      • 5.1 用户端前端实现
        • 5.1.1 项目结构
        • 5.1.2 核心页面实现
      • 5.2 管理端前端实现
        • 5.2.1 项目结构
        • 5.2.2 核心页面实现
    • 6. 微信H5支付集成
      • 6.1 微信支付配置
      • 6.2 支付流程实现

1. 项目概述

1.1 项目背景

随着数字经济的发展,虚拟卡(如礼品卡、会员卡、游戏点卡等)的市场需求日益增长。本项目旨在构建一个完整的Web虚拟卡销售平台,包含前端销售系统、后端管理系统和移动端H5支付功能,采用Java作为后端技术栈,Vue.js作为前端框架,并集成微信支付功能。

1.2 系统架构

系统采用前后端分离架构:

  • 前端:Vue.js + Element UI (管理端) + Vant (移动端)
  • 后端:Spring Boot + Spring Security + MyBatis Plus
  • 数据库:MySQL
  • 缓存:Redis
  • 支付:微信支付H5 API

2. 技术选型与环境搭建

2.1 后端技术栈

// pom.xml 主要依赖<dependencies> <!-- Spring Boot Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- 数据库相关 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.8</version> </dependency> <!-- 工具类 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>31.0.1-jre</version> </dependency> <!-- 微信支付SDK --> <dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-apache-httpclient</artifactId> <version>0.4.7</version> </dependency> <!-- 其他 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency></dependencies>

2.2 前端技术栈

# 管理端前端vue create admin-frontendcd admin-frontendvue add element-uinpm install axios vue-router vuex --save# 用户端前端vue create user-frontendcd user-frontendnpm install vant axios vue-router vuex --save

2.3 开发环境配置

  1. JDK 1.8+
  2. Maven 3.6+
  3. Node.js 14+
  4. MySQL 5.7+
  5. Redis 5.0+
  6. IDE推荐:IntelliJ IDEA + VS Code

3. 数据库设计

3.1 数据库ER图

主要实体:

  • 用户(User)
  • 虚拟卡产品(CardProduct)
  • 卡密库存(CardSecret)
  • 订单(Order)
  • 支付记录(Payment)
  • 管理员(Admin)

3.2 数据表设计

-- 用户表CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT \'用户名\', `password` varchar(100) NOT NULL COMMENT \'密码\', `email` varchar(100) DEFAULT NULL COMMENT \'邮箱\', `phone` varchar(20) DEFAULT NULL COMMENT \'手机号\', `avatar` varchar(255) DEFAULT NULL COMMENT \'头像\', `status` tinyint(1) DEFAULT \'1\' COMMENT \'状态:0-禁用,1-正常\', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT \'创建时间\', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT \'更新时间\', PRIMARY KEY (`id`), UNIQUE KEY `idx_username` (`username`), KEY `idx_phone` (`phone`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=\'用户表\';-- 虚拟卡产品表CREATE TABLE `card_product` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT \'产品名称\', `category_id` bigint(20) NOT NULL COMMENT \'分类ID\', `description` text COMMENT \'产品描述\', `price` decimal(10,2) NOT NULL COMMENT \'售价\', `original_price` decimal(10,2) DEFAULT NULL COMMENT \'原价\', `stock` int(11) NOT NULL DEFAULT \'0\' COMMENT \'库存\', `image_url` varchar(255) DEFAULT NULL COMMENT \'图片URL\', `detail_images` text COMMENT \'详情图片,JSON数组\', `status` tinyint(1) DEFAULT \'1\' COMMENT \'状态:0-下架,1-上架\', `sort_order` int(11) DEFAULT \'0\' COMMENT \'排序权重\', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT \'创建时间\', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT \'更新时间\', PRIMARY KEY (`id`), KEY `idx_category` (`category_id`), KEY `idx_status` (`status`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=\'虚拟卡产品表\';-- 卡密库存表CREATE TABLE `card_secret` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `product_id` bigint(20) NOT NULL COMMENT \'产品ID\', `card_no` varchar(100) NOT NULL COMMENT \'卡号\', `card_password` varchar(100) NOT NULL COMMENT \'卡密\', `status` tinyint(1) DEFAULT \'0\' COMMENT \'状态:0-未售出,1-已售出,2-已锁定\', `order_id` bigint(20) DEFAULT NULL COMMENT \'订单ID\', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT \'创建时间\', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT \'更新时间\', PRIMARY KEY (`id`), UNIQUE KEY `idx_card_no` (`card_no`), KEY `idx_product_id` (`product_id`), KEY `idx_status` (`status`), KEY `idx_order_id` (`order_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=\'卡密库存表\';-- 订单表CREATE TABLE `order` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `order_no` varchar(50) NOT NULL COMMENT \'订单编号\', `user_id` bigint(20) NOT NULL COMMENT \'用户ID\', `total_amount` decimal(10,2) NOT NULL COMMENT \'订单总金额\', `payment_amount` decimal(10,2) NOT NULL COMMENT \'实付金额\', `payment_type` tinyint(1) DEFAULT NULL COMMENT \'支付方式:1-微信,2-支付宝\', `status` tinyint(1) DEFAULT \'0\' COMMENT \'订单状态:0-待支付,1-已支付,2-已发货,3-已完成,4-已取消\', `payment_time` datetime DEFAULT NULL COMMENT \'支付时间\', `complete_time` datetime DEFAULT NULL COMMENT \'完成时间\', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT \'创建时间\', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT \'更新时间\', PRIMARY KEY (`id`), UNIQUE KEY `idx_order_no` (`order_no`), KEY `idx_user_id` (`user_id`), KEY `idx_status` (`status`), KEY `idx_create_time` (`create_time`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=\'订单表\';-- 订单明细表CREATE TABLE `order_item` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `order_id` bigint(20) NOT NULL COMMENT \'订单ID\', `order_no` varchar(50) NOT NULL COMMENT \'订单编号\', `product_id` bigint(20) NOT NULL COMMENT \'产品ID\', `product_name` varchar(100) NOT NULL COMMENT \'产品名称\', `product_image` varchar(255) DEFAULT NULL COMMENT \'产品图片\', `quantity` int(11) NOT NULL COMMENT \'购买数量\', `price` decimal(10,2) NOT NULL COMMENT \'单价\', `total_price` decimal(10,2) NOT NULL COMMENT \'总价\', `card_secret_id` bigint(20) DEFAULT NULL COMMENT \'卡密ID\', `card_no` varchar(100) DEFAULT NULL COMMENT \'卡号\', `card_password` varchar(100) DEFAULT NULL COMMENT \'卡密\', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT \'创建时间\', PRIMARY KEY (`id`), KEY `idx_order_id` (`order_id`), KEY `idx_order_no` (`order_no`), KEY `idx_product_id` (`product_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=\'订单明细表\';-- 支付记录表CREATE TABLE `payment` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `order_id` bigint(20) NOT NULL COMMENT \'订单ID\', `order_no` varchar(50) NOT NULL COMMENT \'订单编号\', `payment_no` varchar(50) NOT NULL COMMENT \'支付流水号\', `payment_type` tinyint(1) NOT NULL COMMENT \'支付方式:1-微信,2-支付宝\', `payment_amount` decimal(10,2) NOT NULL COMMENT \'支付金额\', `payment_status` tinyint(1) DEFAULT \'0\' COMMENT \'支付状态:0-未支付,1-支付成功,2-支付失败\', `payment_time` datetime DEFAULT NULL COMMENT \'支付时间\', `callback_time` datetime DEFAULT NULL COMMENT \'回调时间\', `callback_content` text COMMENT \'回调内容\', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT \'创建时间\', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT \'更新时间\', PRIMARY KEY (`id`), UNIQUE KEY `idx_payment_no` (`payment_no`), KEY `idx_order_id` (`order_id`), KEY `idx_order_no` (`order_no`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=\'支付记录表\';-- 管理员表CREATE TABLE `admin` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT \'用户名\', `password` varchar(100) NOT NULL COMMENT \'密码\', `nickname` varchar(50) DEFAULT NULL COMMENT \'昵称\', `avatar` varchar(255) DEFAULT NULL COMMENT \'头像\', `email` varchar(100) DEFAULT NULL COMMENT \'邮箱\', `phone` varchar(20) DEFAULT NULL COMMENT \'手机号\', `status` tinyint(1) DEFAULT \'1\' COMMENT \'状态:0-禁用,1-正常\', `last_login_time` datetime DEFAULT NULL COMMENT \'最后登录时间\', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT \'创建时间\', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT \'更新时间\', PRIMARY KEY (`id`), UNIQUE KEY `idx_username` (`username`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=\'管理员表\';-- 系统日志表CREATE TABLE `sys_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) DEFAULT NULL COMMENT \'用户ID\', `username` varchar(50) DEFAULT NULL COMMENT \'用户名\', `operation` varchar(50) DEFAULT NULL COMMENT \'用户操作\', `method` varchar(200) DEFAULT NULL COMMENT \'请求方法\', `params` text COMMENT \'请求参数\', `time` bigint(20) DEFAULT NULL COMMENT \'执行时长(毫秒)\', `ip` varchar(64) DEFAULT NULL COMMENT \'IP地址\', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT \'创建时间\', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=\'系统日志表\';

4. 后端实现

4.1 Spring Boot项目结构

src/main/java/com/virtualcard/├── config/ # 配置类│ ├── SecurityConfig.java│ ├── SwaggerConfig.java│ ├── RedisConfig.java│ └── WebMvcConfig.java├── constant/  # 常量类│ ├── OrderStatus.java│ ├── PaymentType.java│ └── RedisKey.java├── controller/ # 控制器│ ├── api/  # 用户API接口│ │ ├── AuthController.java│ │ ├── CardController.java│ │ ├── OrderController.java│ │ └── PaymentController.java│ └── admin/ # 管理端接口│ ├── AdminAuthController.java│ ├── AdminCardController.java│ ├── AdminOrderController.java│ └── AdminUserController.java├── dao/  # 数据访问层│ ├── entity/ # 实体类│ │ ├── User.java│ │ ├── CardProduct.java│ │ ├── CardSecret.java│ │ ├── Order.java│ │ └── Payment.java│ └── mapper/ # MyBatis Mapper接口│ ├── UserMapper.java│ ├── CardProductMapper.java│ ├── CardSecretMapper.java│ ├── OrderMapper.java│ └── PaymentMapper.java├── dto/  # 数据传输对象│ ├── request/  # 请求DTO│ │ ├── LoginReq.java│ │ ├── OrderCreateReq.java│ │ └── PaymentReq.java│ └── response/ # 响应DTO│ ├── ApiResponse.java│ ├── CardProductRes.java│ └── OrderRes.java├── exception/ # 异常处理│ ├── BusinessException.java│ └── GlobalExceptionHandler.java├── service/  # 服务层│ ├── impl/  # 服务实现│ │ ├── AuthServiceImpl.java│ │ ├── CardServiceImpl.java│ │ ├── OrderServiceImpl.java│ │ └── PaymentServiceImpl.java│ └── AuthService.java│ ├── CardService.java│ ├── OrderService.java│ └── PaymentService.java├── util/  # 工具类│ ├── JwtUtil.java│ ├── RedisUtil.java│ ├── SnowFlakeUtil.java│ └── WeChatPayUtil.java└── VirtualCardApplication.java # 启动类

4.2 核心功能实现

4.2.1 用户认证与授权
// SecurityConfig.java@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; @Autowired private JwtAccessDeniedHandler jwtAccessDeniedHandler; @Autowired private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(\"/api/auth/**\").permitAll() .antMatchers(\"/api/payment/callback/**\").permitAll() .antMatchers(\"/swagger-ui/**\", \"/swagger-resources/**\", \"/v2/api-docs\").permitAll() .antMatchers(\"/api/**\").authenticated() .antMatchers(\"/admin/**\").hasRole(\"ADMIN\") .anyRequest().authenticated() .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .exceptionHandling() .accessDeniedHandler(jwtAccessDeniedHandler) .authenticationEntryPoint(jwtAuthenticationEntryPoint); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }}// JwtUtil.java@Componentpublic class JwtUtil { private static final String SECRET = \"your_jwt_secret\"; private static final long EXPIRATION = 86400L; // 24小时 public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); claims.put(\"sub\", userDetails.getUsername()); claims.put(\"created\", new Date()); return Jwts.builder() .setClaims(claims) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION * 1000)) .signWith(SignatureAlgorithm.HS512, SECRET) .compact(); } public String getUsernameFromToken(String token) { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody() .getSubject(); } public boolean validateToken(String token, UserDetails userDetails) { final String username = getUsernameFromToken(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token); } private boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); return expiration.before(new Date()); } private Date getExpirationDateFromToken(String token) { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody() .getExpiration(); }}
4.2.2 虚拟卡管理
// CardServiceImpl.java@Servicepublic class CardServiceImpl implements CardService { @Autowired private CardProductMapper cardProductMapper; @Autowired private CardSecretMapper cardSecretMapper; @Autowired private RedisUtil redisUtil; private static final String CARD_PRODUCT_CACHE_KEY = \"card:product:list\"; private static final long CACHE_EXPIRE = 3600; // 1小时 @Override public List<CardProductRes> listAllProducts() { // 先查缓存 String cache = redisUtil.get(CARD_PRODUCT_CACHE_KEY); if (StringUtils.isNotBlank(cache)) { return JSON.parseArray(cache, CardProductRes.class); } // 缓存没有则查数据库 QueryWrapper<CardProduct> queryWrapper = new QueryWrapper<>(); queryWrapper.eq(\"status\", 1).orderByAsc(\"sort_order\"); List<CardProduct> products = cardProductMapper.selectList(queryWrapper); List<CardProductRes> result = products.stream() .map(this::convertToRes) .collect(Collectors.toList()); // 存入缓存 redisUtil.set(CARD_PRODUCT_CACHE_KEY, JSON.toJSONString(result), CACHE_EXPIRE); return result; } @Override @Transactional public List<CardSecret> lockCardSecrets(Long productId, int quantity, Long orderId) { // 查询可用的卡密 List<CardSecret> availableSecrets = cardSecretMapper.selectAvailableSecrets(productId, quantity); if (availableSecrets.size() < quantity) { throw new BusinessException(\"库存不足\"); } // 锁定卡密 List<Long> ids = availableSecrets.stream().map(CardSecret::getId).collect(Collectors.toList()); cardSecretMapper.lockSecrets(ids, orderId); // 更新产品库存 cardProductMapper.decreaseStock(productId, quantity); // 清除缓存 redisUtil.del(CARD_PRODUCT_CACHE_KEY); return availableSecrets; } @Override @Transactional public void unlockCardSecrets(List<Long> cardSecretIds) { if (CollectionUtils.isEmpty(cardSecretIds)) { return; } // 查询卡密对应的产品ID和数量 List<CardSecret> secrets = cardSecretMapper.selectBatchIds(cardSecretIds); if (CollectionUtils.isEmpty(secrets)) { return; } Map<Long, Long> productCountMap = secrets.stream() .collect(Collectors.groupingBy(CardSecret::getProductId, Collectors.counting())); // 解锁卡密 cardSecretMapper.unlockSecrets(cardSecretIds); // 恢复产品库存 for (Map.Entry<Long, Long> entry : productCountMap.entrySet()) { cardProductMapper.increaseStock(entry.getKey(), entry.getValue().intValue()); } // 清除缓存 redisUtil.del(CARD_PRODUCT_CACHE_KEY); } private CardProductRes convertToRes(CardProduct product) { CardProductRes res = new CardProductRes(); BeanUtils.copyProperties(product, res); return res; }}
4.2.3 订单服务
// OrderServiceImpl.java@Servicepublic class OrderServiceImpl implements OrderService { @Autowired private OrderMapper orderMapper; @Autowired private OrderItemMapper orderItemMapper; @Autowired private CardService cardService; @Autowired private PaymentService paymentService; @Autowired private SnowFlakeUtil snowFlakeUtil; @Override @Transactional public OrderRes createOrder(OrderCreateReq req, Long userId) { // 生成订单号 String orderNo = generateOrderNo(); // 锁定卡密 List<CardSecret> cardSecrets = cardService.lockCardSecrets(req.getProductId(), req.getQuantity(), null); // 计算总金额 CardProduct product = cardService.getProductById(req.getProductId()); BigDecimal totalAmount = product.getPrice().multiply(new BigDecimal(req.getQuantity())); // 创建订单 Order order = new Order(); order.setOrderNo(orderNo); order.setUserId(userId); order.setTotalAmount(totalAmount); order.setPaymentAmount(totalAmount); order.setStatus(OrderStatus.UNPAID.getCode()); orderMapper.insert(order); // 创建订单明细 List<OrderItem> orderItems = new ArrayList<>(); for (CardSecret secret : cardSecrets) { OrderItem item = new OrderItem(); item.setOrderId(order.getId()); item.setOrderNo(orderNo); item.setProductId(req.getProductId()); item.setProductName(product.getName()); item.setProductImage(product.getImageUrl()); item.setQuantity(1); item.setPrice(product.getPrice()); item.setTotalPrice(product.getPrice()); item.setCardSecretId(secret.getId()); orderItems.add(item); } orderItemMapper.batchInsert(orderItems); // 更新卡密的订单ID List<Long> cardSecretIds = cardSecrets.stream().map(CardSecret::getId).collect(Collectors.toList()); cardService.updateCardSecretsOrderId(cardSecretIds, order.getId()); // 返回订单信息 OrderRes res = new OrderRes(); BeanUtils.copyProperties(order, res); res.setItems(orderItems.stream().map(this::convertToItemRes).collect(Collectors.toList())); return res; } @Override @Transactional public void cancelOrder(Long orderId, Long userId) { Order order = orderMapper.selectById(orderId); if (order == null) { throw new BusinessException(\"订单不存在\"); } if (!order.getUserId().equals(userId)) { throw new BusinessException(\"无权操作此订单\"); } if (order.getStatus() != OrderStatus.UNPAID.getCode()) { throw new BusinessException(\"订单状态不允许取消\"); } // 更新订单状态 order.setStatus(OrderStatus.CANCELLED.getCode()); orderMapper.updateById(order); // 查询订单明细获取卡密ID List<OrderItem> items = orderItemMapper.selectByOrderId(orderId); List<Long> cardSecretIds = items.stream() .map(OrderItem::getCardSecretId) .filter(Objects::nonNull) .collect(Collectors.toList()); // 解锁卡密 if (!cardSecretIds.isEmpty()) { cardService.unlockCardSecrets(cardSecretIds); } } @Override @Transactional public void payOrderSuccess(String orderNo, String paymentNo, BigDecimal paymentAmount, Date paymentTime) { Order order = orderMapper.selectByOrderNo(orderNo); if (order == null) { throw new BusinessException(\"订单不存在\"); } if (order.getStatus() != OrderStatus.UNPAID.getCode()) { throw new BusinessException(\"订单状态不正确\"); } // 更新订单状态 order.setStatus(OrderStatus.PAID.getCode()); order.setPaymentTime(paymentTime); orderMapper.updateById(order); // 更新卡密状态为已售出 List<OrderItem> items = orderItemMapper.selectByOrderId(order.getId()); List<Long> cardSecretIds = items.stream() .map(OrderItem::getCardSecretId) .filter(Objects::nonNull) .collect(Collectors.toList()); if (!cardSecretIds.isEmpty()) { cardService.sellCardSecrets(cardSecretIds); } // 创建支付记录 Payment payment = new Payment(); payment.setOrderId(order.getId()); payment.setOrderNo(orderNo); payment.setPaymentNo(paymentNo); payment.setPaymentType(PaymentType.WECHAT.getCode()); payment.setPaymentAmount(paymentAmount); payment.setPaymentStatus(1); payment.setPaymentTime(paymentTime); payment.setCallbackTime(new Date()); paymentService.createPayment(payment); } private String generateOrderNo() { return \"ORD\" + snowFlakeUtil.nextId(); } private OrderItemRes convertToItemRes(OrderItem item) { OrderItemRes res = new OrderItemRes(); BeanUtils.copyProperties(item, res); return res; }}
4.2.4 微信支付集成
// WeChatPayUtil.java@Componentpublic class WeChatPayUtil { @Value(\"${wechat.pay.appid}\") private String appId; @Value(\"${wechat.pay.mchid}\") private String mchId; @Value(\"${wechat.pay.apikey}\") private String apiKey; @Value(\"${wechat.pay.serialNo}\") private String serialNo; @Value(\"${wechat.pay.privateKey}\") private String privateKey; @Value(\"${wechat.pay.notifyUrl}\") private String notifyUrl; private CloseableHttpClient httpClient; @PostConstruct public void init() { // 加载商户私钥 PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes())); // 构造HttpClient httpClient = WechatPayHttpClientBuilder.create() .withMerchant(mchId, serialNo, merchantPrivateKey) .withValidator(new WechatPay2Validator(apiKey.getBytes())) .build(); } public Map<String, String> createH5Payment(String orderNo, BigDecimal amount, String description, String clientIp) throws Exception { // 构造请求参数 Map<String, Object> params = new HashMap<>(); params.put(\"appid\", appId); params.put(\"mchid\", mchId); params.put(\"description\", description); params.put(\"out_trade_no\", orderNo); params.put(\"notify_url\", notifyUrl); params.put(\"amount\", new HashMap<String, Object>() {{ put(\"total\", amount.multiply(new BigDecimal(100)).intValue()); put(\"currency\", \"CNY\"); }}); params.put(\"scene_info\", new HashMap<String, Object>() {{ put(\"payer_client_ip\", clientIp); put(\"h5_info\", new HashMap<String, Object>() {{ put(\"type\", \"Wap\"); }}); }}); // 发送请求 HttpPost httpPost = new HttpPost(\"https://api.mch.weixin.qq.com/v3/pay/transactions/h5\"); httpPost.addHeader(\"Accept\", \"application/json\"); httpPost.addHeader(\"Content-type\", \"application/json\"); httpPost.setEntity(new StringEntity(JSON.toJSONString(params), \"UTF-8\")); CloseableHttpResponse response = httpClient.execute(httpPost); try { String responseBody = EntityUtils.toString(response.getEntity()); if (response.getStatusLine().getStatusCode() == 200) { Map<String, String> result = new HashMap<>(); JSONObject json = JSON.parseObject(responseBody); result.put(\"h5_url\", json.getString(\"h5_url\")); result.put(\"prepay_id\", json.getString(\"prepay_id\")); return result; } else { throw new BusinessException(\"微信支付创建失败: \" + responseBody); } } finally { response.close(); } } public boolean verifyNotify(Map<String, String> params, String signature, String serial, String nonce, String timestamp, String body) { try { // 验证签名 String message = timestamp + \"\\n\" + nonce + \"\\n\" + body + \"\\n\"; boolean verifyResult = verifySignature(message.getBytes(\"utf-8\"), serial, signature.getBytes(\"utf-8\"))); if (!verifyResult) { return false; } // 验证订单状态 JSONObject json = JSON.parseObject(body); String orderNo = json.getJSONObject(\"resource\").getString(\"out_trade_no\"); String tradeState = json.getJSONObject(\"resource\").getString(\"trade_state\"); return \"SUCCESS\".equals(tradeState); } catch (Exception e) { return false; } } private boolean verifySignature(byte[] message, String serial, byte[] signature) { try { // 根据证书序列号查询证书 String cert = getWechatPayCert(serial); if (cert == null) { return false; } // 加载证书 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(cert)); KeyFactory keyFactory = KeyFactory.getInstance(\"RSA\"); PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); // 验证签名 Signature sign = Signature.getInstance(\"SHA256withRSA\"); sign.initVerify(publicKey); sign.update(message); return sign.verify(signature); } catch (Exception e) { return false; } } private String getWechatPayCert(String serial) { // 这里应该实现从微信支付平台获取证书的逻辑 // 实际项目中应该缓存证书,避免频繁请求 // 简化实现,返回配置的证书 return \"your_wechat_pay_cert_content\"; }}// PaymentController.java@RestController@RequestMapping(\"/api/payment\")public class PaymentController { @Autowired private OrderService orderService; @Autowired private PaymentService paymentService; @Autowired private WeChatPayUtil weChatPayUtil; @PostMapping(\"/create\") public ApiResponse<Map<String, String>> createPayment(@RequestBody PaymentReq req, HttpServletRequest request) { // 查询订单 Order order = orderService.getOrderByNo(req.getOrderNo()); if (order == null) { return ApiResponse.fail(\"订单不存在\"); } if (order.getStatus() != OrderStatus.UNPAID.getCode()) { return ApiResponse.fail(\"订单状态不正确\"); } // 创建微信支付 try { Map<String, String> result = weChatPayUtil.createH5Payment(  order.getOrderNo(),  order.getPaymentAmount(),  \"虚拟卡购买-\" + order.getOrderNo(),  getClientIp(request)); // 保存支付记录 Payment payment = new Payment(); payment.setOrderId(order.getId()); payment.setOrderNo(order.getOrderNo()); payment.setPaymentNo(result.get(\"prepay_id\")); payment.setPaymentType(PaymentType.WECHAT.getCode()); payment.setPaymentAmount(order.getPaymentAmount()); payment.setPaymentStatus(0); paymentService.createPayment(payment); return ApiResponse.success(result); } catch (Exception e) { return ApiResponse.fail(\"支付创建失败: \" + e.getMessage()); } } @PostMapping(\"/callback/wechat\") public String wechatPayCallback(HttpServletRequest request) { try { // 获取请求头信息 String signature = request.getHeader(\"Wechatpay-Signature\"); String serial = request.getHeader(\"Wechatpay-Serial\"); String nonce = request.getHeader(\"Wechatpay-Nonce\"); String timestamp = request.getHeader(\"Wechatpay-Timestamp\"); // 获取请求体 String body = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8); // 验证回调 if (!weChatPayUtil.verifyNotify(null, signature, serial, nonce, timestamp, body)) { return \"FAIL\"; } // 解析回调内容 JSONObject json = JSON.parseObject(body); JSONObject resource = json.getJSONObject(\"resource\"); String orderNo = resource.getString(\"out_trade_no\"); String transactionId = resource.getString(\"transaction_id\"); BigDecimal amount = resource.getJSONObject(\"amount\")  .getBigDecimal(\"total\")  .divide(new BigDecimal(100)); Date paymentTime = new Date(resource.getLong(\"success_time\") * 1000); // 处理支付成功逻辑 orderService.payOrderSuccess(orderNo, transactionId, amount, paymentTime); return \"SUCCESS\"; } catch (Exception e) { return \"FAIL\"; } } private String getClientIp(HttpServletRequest request) { String ip = request.getHeader(\"X-Forwarded-For\"); if (StringUtils.isNotEmpty(ip) && !\"unKnown\".equalsIgnoreCase(ip)) { int index = ip.indexOf(\",\"); if (index != -1) { return ip.substring(0, index); } else { return ip; } } ip = request.getHeader(\"X-Real-IP\"); if (StringUtils.isNotEmpty(ip) && !\"unKnown\".equalsIgnoreCase(ip)) { return ip; } return request.getRemoteAddr(); }}

5. 前端实现

5.1 用户端前端实现

5.1.1 项目结构
src/├── api/  # API请求│ ├── auth.js  # 认证相关API│ ├── card.js  # 虚拟卡相关API│ ├── order.js # 订单相关API│ └── payment.js # 支付相关API├── assets/  # 静态资源│ ├── css/  # 全局样式│ └── images/  # 图片资源├── components/  # 公共组件│ ├── CardItem.vue # 卡产品项组件│ ├── Header.vue # 头部组件│ ├── Footer.vue # 底部组件│ └── Loading.vue # 加载组件├── router/  # 路由配置│ └── index.js # 路由定义├── store/ # Vuex状态管理│ ├── modules/ # 模块化状态│ │ ├── auth.js # 认证模块│ │ ├── card.js # 虚拟卡模块│ │ └── order.js # 订单模块│ └── index.js # 主入口├── utils/ # 工具函数│ ├── request.js # axios封装│ ├── auth.js  # 认证工具│ └── wechat.js # 微信相关工具├── views/ # 页面组件│ ├── auth/ # 认证相关页面│ │ ├── Login.vue # 登录页│ │ └── Register.vue # 注册页│ ├── card/ # 虚拟卡相关页面│ │ ├── List.vue # 卡列表页│ │ └── Detail.vue # 卡详情页│ ├── order/ # 订单相关页面│ │ ├── Create.vue # 订单创建页│ │ ├── Detail.vue # 订单详情页│ │ └── List.vue # 订单列表页│ ├── payment/ # 支付相关页面│ │ └── Pay.vue # 支付页│ ├── Home.vue # 首页│ └── User.vue # 用户中心页├── App.vue  # 根组件└── main.js  # 应用入口
5.1.2 核心页面实现

虚拟卡列表页 (Card/List.vue)

 
import { Search, Tab, Tabs, List, PullRefresh } from \'vant\';import HeaderComponent from \'@/components/Header.vue\';import CardItem from \'@/components/CardItem.vue\';import { getCardProducts, getCardCategories } from \'@/api/card\';export default { components: { [Search.name]: Search, [Tab.name]: Tab, [Tabs.name]: Tabs, [List.name]: List, [PullRefresh.name]: PullRefresh, HeaderComponent, CardItem }, data() { return { searchKeyword: \'\', activeCategory: 0, categories: [], cardList: [], loading: false, finished: false, refreshing: false, page: 1, pageSize: 10 }; }, created() { this.loadCategories(); }, methods: { async loadCategories() { try { const res = await getCardCategories(); this.categories = [{ id: 0, name: \'全部\' }, ...res.data]; } catch (error) { console.error(\'加载分类失败\', error); } }, async onLoad() { if (this.refreshing) { this.cardList = []; this.refreshing = false; } try { const params = { page: this.page, pageSize: this.pageSize, categoryId: this.activeCategory === 0 ? null : this.activeCategory, keyword: this.searchKeyword }; const res = await getCardProducts(params); this.cardList = [...this.cardList, ...res.data.list]; this.loading = false; if (res.data.list.length < this.pageSize) { this.finished = true; } else { this.page++; } } catch (error) { this.loading = false; this.finished = true; console.error(\'加载卡片列表失败\', error); } }, onRefresh() { this.page = 1; this.finished = false; this.loading = true; this.onLoad(); }, onSearch() { this.page = 1; this.cardList = []; this.finished = false; this.loading = true; this.onLoad(); }, onCategoryChange() { this.page = 1; this.cardList = []; this.finished = false; this.loading = true; this.onLoad(); }, goToDetail(id) { this.$router.push(`/card/detail/${id}`); } }};.card-list { padding-bottom: 50px;}.search-box { padding: 10px;}

订单创建页 (Order/Create.vue)

 
虚拟商品
import { ContactCard, Card, Tag, Cell, CellGroup, Radio, RadioGroup, Stepper, SubmitBar } from \'vant\';import HeaderComponent from \'@/components/Header.vue\';import { getCardDetail } from \'@/api/card\';import { createOrder } from \'@/api/order\';export default { components: { [ContactCard.name]: ContactCard, [Card.name]: Card, [Tag.name]: Tag, [Cell.name]: Cell, [CellGroup.name]: CellGroup, [Radio.name]: Radio, [RadioGroup.name]: RadioGroup, [Stepper.name]: Stepper, [SubmitBar.name]: SubmitBar, HeaderComponent }, data() { return { cardId: null, card: { id: null, name: \'\', price: 0, stock: 0, imageUrl: \'\' }, quantity: 1, paymentType: 1, address: { name: \'张三\', phone: \'13800138000\', address: \'北京市朝阳区\' }, isVirtual: true }; }, computed: { totalPrice() { return this.card.price * this.quantity; } }, created() { this.cardId = this.$route.params.id; this.loadCardDetail(); }, methods: { async loadCardDetail() { try { const res = await getCardDetail(this.cardId); this.card = res.data; } catch (error) { this.$toast.fail(\'加载卡片详情失败\'); console.error(error); } }, editAddress() { this.$router.push(\'/address/edit\'); }, async createOrder() { try { this.$toast.loading({ message: \'创建订单中...\', forbidClick: true }); const params = { productId: this.cardId, quantity: this.quantity }; const res = await createOrder(params); this.$toast.clear(); // 跳转到支付页面 this.$router.push({ path: \'/payment/pay\', query: { orderNo: res.data.orderNo, amount: this.totalPrice } }); } catch (error) { this.$toast.clear(); this.$toast.fail(error.message || \'创建订单失败\'); console.error(error); } } }};.order-create { padding-bottom: 100px;}.address-section { margin-bottom: 10px;}.card-info { margin-bottom: 10px;}.total-price { font-weight: bold; color: #ee0a24;}

微信支付页 (Payment/Pay.vue)

 
¥{{ amount.toFixed(2) }}
Web虚拟卡销售店铺实现方案_微信小店 可以卖虚拟充值卡吗
立即支付
正在调起支付...

支付成功

支付失败

{{ errorMsg }}

import { Cell, CellGroup, Radio, RadioGroup, Button, Dialog, Loading, Icon } from \'vant\';import HeaderComponent from \'@/components/Header.vue\';import { createPayment } from \'@/api/payment\';import { getOrderDetail } from \'@/api/order\';import { isWeixinBrowser, wechatPay } from \'@/utils/wechat\';export default { components: { [Cell.name]: Cell, [CellGroup.name]: CellGroup, [Radio.name]: Radio, [RadioGroup.name]: RadioGroup, [Button.name]: Button, [Dialog.name]: Dialog, [Loading.name]: Loading, [Icon.name]: Icon, HeaderComponent }, data() { return { orderNo: this.$route.query.orderNo, amount: parseFloat(this.$route.query.amount), paymentMethod: \'wechat\', loading: false, showPaymentDialog: false, paymentStatus: \'pending\', // pending, success, failed errorMsg: \'\', timer: null, isWeixin: isWeixinBrowser() }; }, beforeDestroy() { if (this.timer) { clearInterval(this.timer); } }, methods: { async handlePayment() { if (this.paymentMethod !== \'wechat\') { this.$toast(\'请选择微信支付\'); return; } this.loading = true; try { // 创建支付 const res = await createPayment({ orderNo: this.orderNo, paymentType: 1 // 微信支付 }); this.loading = false; if (this.isWeixin) { // 微信浏览器内使用JSAPI支付 await this.wechatJsApiPay(res.data); } else { // 非微信浏览器使用H5支付 this.showPaymentDialog = true; window.location.href = res.data.h5Url; // 启动轮询检查支付状态 this.startPaymentCheck(); } } catch (error) { this.loading = false; this.$toast.fail(error.message || \'支付创建失败\'); console.error(error); } }, async wechatJsApiPay(paymentData) { try { await wechatPay(paymentData); // 支付成功,跳转到结果页 this.$router.push({ path: \'/payment/result\', query: { orderNo: this.orderNo, status: \'success\' } }); } catch (error) { this.$toast.fail(error.message || \'支付失败\'); console.error(error); } }, startPaymentCheck() { this.timer = setInterval(async () => { try { const res = await getOrderDetail(this.orderNo); if (res.data.status === 1) { // 已支付 this.paymentStatus = \'success\'; clearInterval(this.timer); // 3秒后自动跳转 setTimeout(() => { this.showPaymentDialog = false; this.$router.push({ path: \'/payment/result\', query: { orderNo: this.orderNo, status: \'success\' } }); }, 3000); } } catch (error) { console.error(\'检查支付状态失败\', error); } }, 3000); }, beforeClose(action, done) { if (action === \'confirm\') { if (this.paymentStatus === \'pending\') { this.$toast(\'支付处理中,请稍候\'); done(false); } else { done(); this.$router.push({ path: \'/payment/result\', query: { orderNo: this.orderNo, status: this.paymentStatus } }); } } else { done(); } } }};.payment-page { padding-bottom: 100px;}.payment-info { margin-bottom: 10px;}.price { color: #ee0a24; font-weight: bold;}.pay-icon { width: 24px; height: 24px; margin-right: 10px;}.payment-btn { margin: 20px 15px;}.payment-dialog { padding: 20px; text-align: center;}.payment-success,.payment-failed { padding: 20px 0;}.payment-success p,.payment-failed p { margin-top: 10px; font-size: 16px;}.error-msg { color: #ee0a24; font-size: 14px;}

5.2 管理端前端实现

5.2.1 项目结构

管理端前端结构与用户端类似,但使用Element UI作为UI框架,主要包含以下功能模块:

  • 管理员登录
  • 虚拟卡产品管理
  • 卡密库存管理
  • 订单管理
  • 用户管理
  • 数据统计
5.2.2 核心页面实现

虚拟卡产品管理页 (Card/List.vue)

 
查询 重置 新增产品 批量删除 {{ getCategoryName(scope.row.categoryId) }} ¥{{ scope.row.price.toFixed(2) }} {{ scope.row.status ? \'上架\' : \'下架\' }} 编辑 {{ scope.row.status ? \'下架\' : \'上架\' }} 取 消 确 定
import { getCardProducts, addCardProduct, updateCardProduct, deleteCardProduct, updateCardProductStatus } from \'@/api/card\';import { getCardCategories } from \'@/api/category\';export default { data() { return { searchForm: { name: \'\', categoryId: null, status: null }, tableData: [], selectedItems: [], categories: [], loading: false, pagination: { current: 1, size: 10, total: 0 }, dialogVisible: false, dialogTitle: \'新增产品\', dialogForm: { id: null, name: \'\', categoryId: null, price: 0, originalPrice: 0, imageUrl: \'\', detailImages: [], description: \'\', sortOrder: 0, status: 1 }, detailImageList: [], rules: { name: [{ required: true, message: \'请输入产品名称\', trigger: \'blur\' }], categoryId: [{ required: true, message: \'请选择产品分类\', trigger: \'change\' }], price: [{ required: true, message: \'请输入产品价格\', trigger: \'blur\' }] } }; }, created() { this.loadCategories(); this.loadTableData(); }, methods: { async loadCategories() { try { const res = await getCardCategories(); this.categories = res.data; } catch (error) { console.error(\'加载分类失败\', error); } }, async loadTableData() { this.loading = true; try { const params = { ...this.searchForm, page: this.pagination.current, pageSize: this.pagination.size }; const res = await getCardProducts(params); this.tableData = res.data.list; this.pagination.total = res.data.total; } catch (error) { console.error(\'加载产品列表失败\', error); } finally { this.loading = false; } }, getCategoryName(categoryId) { const category = this.categories.find(item => item.id === categoryId); return category ? category.name : \'--\'; }, handleSearch() { this.pagination.current = 1; this.loadTableData(); }, resetSearch() { this.searchForm = { name: \'\', categoryId: null, status: null }; this.pagination.current = 1; this.loadTableData(); }, handleSelectionChange(val) { this.selectedItems = val; }, handleSizeChange(val) { this.pagination.size = val; this.loadTableData(); }, handleCurrentChange(val) { this.pagination.current = val; this.loadTableData(); }, handleAdd() { this.dialogTitle = \'新增产品\'; this.dialogForm = { id: null, name: \'\', categoryId: null, price: 0, originalPrice: 0, imageUrl: \'\', detailImages: [], description: \'\', sortOrder: 0, status: 1 }; this.detailImageList = []; this.dialogVisible = true; }, handleEdit(row) { this.dialogTitle = \'编辑产品\'; this.dialogForm = { ...row, detailImages: row.detailImages ? JSON.parse(row.detailImages) : [] }; this.detailImageList = this.dialogForm.detailImages.map(url => ({ url, name: url.substring(url.lastIndexOf(\'/\') + 1) })); this.dialogVisible = true; }, async handleStatusChange(row) { try { await updateCardProductStatus(row.id, row.status ? 0 : 1); this.$message.success(\'状态更新成功\'); this.loadTableData(); } catch (error) { this.$message.error(\'状态更新失败\'); console.error(error); } }, handleBatchDelete() { this.$confirm(\'确定要删除选中的产品吗?\', \'提示\', { confirmButtonText: \'确定\', cancelButtonText: \'取消\', type: \'warning\' }).then(async () => { try { const ids = this.selectedItems.map(item => item.id); await deleteCardProduct(ids); this.$message.success(\'删除成功\'); this.loadTableData(); } catch (error) { this.$message.error(\'删除失败\'); console.error(error); } }).catch(() => {}); }, handleImageSuccess(res, file) { this.dialogForm.imageUrl = res.data.url; }, handleDetailImageSuccess(res, file) { this.dialogForm.detailImages.push(res.data.url); }, handleDetailImageRemove(file, fileList) { const url = file.url || file.response.data.url; this.dialogForm.detailImages = this.dialogForm.detailImages.filter(item => item !== url); }, beforeImageUpload(file) { const isImage = file.type.startsWith(\'image/\'); const isLt2M = file.size / 1024 / 1024 { if (!valid) { return; } try { const formData = { ...this.dialogForm, detailImages: JSON.stringify(this.dialogForm.detailImages) }; if (this.dialogForm.id) { await updateCardProduct(formData); this.$message.success(\'更新成功\'); } else { await addCardProduct(formData); this.$message.success(\'添加成功\'); } this.dialogVisible = false; this.loadTableData(); } catch (error) { this.$message.error(error.message || \'操作失败\'); console.error(error); } }); } }};.search-card { margin-bottom: 20px;}.search-form { display: flex; flex-wrap: wrap;}.operation-card { margin-bottom: 20px;}.pagination { margin-top: 20px; text-align: right;}.avatar-uploader { border: 1px dashed #d9d9d9; border-radius: 6px; cursor: pointer; position: relative; overflow: hidden; width: 150px; height: 150px;}.avatar-uploader:hover { border-color: #409EFF;}.avatar-uploader-icon { font-size: 28px; color: #8c939d; width: 150px; height: 150px; line-height: 150px; text-align: center;}.avatar { width: 150px; height: 150px; display: block;}

6. 微信H5支付集成

6.1 微信支付配置

  1. 申请微信支付商户号

    • 登录微信支付商户平台(https://pay.weixin.qq.com)
    • 完成商户号申请和资质认证
  2. 配置支付域名

    • 在商户平台配置支付域名(需备案)
    • 配置授权目录和回调域名
  3. 获取API密钥和证书

    • 设置APIv2密钥(32位)
    • 申请API证书(用于V3接口)
  4. 配置应用信息

    • 在商户平台配置H5支付信息
    • 设置支付场景和域名

6.2 支付流程实现

  1. 前端发起支付请求
// src/utils/wechat.jsimport axios from \'axios\';export function isWeixinBrowser() { return /micromessenger/i.test(navigator.userAgent);}export async function wechatPay(paymentData) { return new Promise((resolve, reject) => { if (typeof WeixinJSBridge === \'undefined\') { if (document.addEventListener) { document.addEventListener(\'WeixinJSBridgeReady\', onBridgeReady, false); } else if (document.attachEvent) { document.attachEvent(\'WeixinJSBridgeReady\', onBridgeReady); document.attachEvent(\'onWeixinJSBridgeReady\', onBridgeReady); } reject(new Error(\'请在微信中打开页面\')); } else { onBridgeReady(); } function onBridgeReady() { WeixinJSBridge.invoke( \'getBrandWCPayRequest\', { appId: paymentData.appId, timeStamp: paymentData.timeStamp, nonceStr: paymentData.nonceStr, package: paymentData.package, signType: paymentData.signType, paySign: paymentData.paySign }, function(res) { if (res.err_msg === \'get_brand_wcpay_request:ok\') { resolve(); } else { reject(new Error(res.err_msg || \'支付失败\')); } } ); } });}
  1. 后端处理支付回调
// PaymentController.java@RestController@RequestMapping(\"/api/payment\")public class PaymentController { // ... 其他代码 ... @PostMapping(\"/callback/wechat\") public String wechatPayCallback(HttpServletRequest request) { try { // 获取请求头信息 String signature = request.getHeader(\"Wechatpay-Signature\"); String serial = request.getHeader(\"Wechatpay-Serial\"); String nonce = request.getHeader(\"Wechatpay-Nonce\"); String timestamp = request.getHeader(\"Wechatpay-Timestamp\"); // 获取请求体 String body = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8); // 验证回调 if (!weChatPayUtil.verifyNotify(null, signature, serial, nonce, timestamp, body)) { log.error(\"微信支付回调验证失败\"); return \"FAIL\"; } // 解析回调内容 JSONObject json = JSON.parseObject(body); JSONObject resource = json.getJSONObject(\"resource\"); String orderNo = resource.getString(\"out_trade_no\"); String transactionId = resource.getString(\"transaction_id\"); BigDecimal amount = resource.getJSONObject(\"amount\")  .getBigDecimal(\"total\")  .divide(new BigDecimal(100)); Date paymentTime = new Date(resource.getLong(\"success_time\") * 1000); // 处理支付成功逻辑 orderService.payOrderSuccess(orderNo, transactionId, amount, paymentTime); log.info(\"微信支付回调处理成功, orderNo: {}\", orderNo); return \"SUCCESS\"; } catch (Exception e) { log.error(\"微信支付回调处理失败\", e); return \"FAIL\"; } }}

其他略…

茶叶网上商城