跳到主要内容Web 虚拟卡销售平台架构与实现详解 | 极客日志JavaWeChatPay大前端java
Web 虚拟卡销售平台架构与实现详解
综述由AI生成基于 Spring Boot 和 Vue.js 构建的 Web 虚拟卡销售平台,涵盖前后端分离架构、数据库设计、订单库存管理及微信支付集成。核心难点在于高并发下的库存锁定与支付回调安全处理,通过 Redis 缓存优化查询性能,利用 JWT 保障接口安全,实现了从商品展示到订单完成的完整闭环流程。
松间照月2 浏览 项目概述
随着数字经济的发展,虚拟卡(如礼品卡、会员卡、游戏点卡等)的市场需求日益增长。本项目旨在构建一个完整的 Web 虚拟卡销售平台,包含前端销售系统、后端管理系统和移动端 H5 支付功能。技术选型上采用 Java 作为后端核心,Vue.js 作为前端框架,并深度集成微信支付能力。
系统架构
系统采用前后端分离架构,确保高内聚低耦合:
- 前端:管理端使用 Vue.js + Element UI,用户端及移动端使用 Vant 组件库。
- 后端:Spring Boot 为核心,结合 Spring Security 做安全控制,MyBatis Plus 简化数据访问。
- 数据存储:MySQL 负责持久化,Redis 处理缓存与并发锁。
- 支付集成:微信支付 H5 API。
技术选型与环境搭建
后端依赖配置
Maven 依赖是项目的基石,这里列出了核心模块的配置思路:
<dependencies>
<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>
mysql
mysql-connector-java
runtime
com.baomidou
mybatis-plus-boot-starter
3.5.1
com.alibaba
druid-spring-boot-starter
1.2.8
org.apache.commons
commons-lang3
com.google.guava
guava
31.0.1-jre
com.github.wechatpay-apiv3
wechatpay-apache-httpclient
0.4.7
org.projectlombok
lombok
true
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
<scope>
</scope>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
<version>
</version>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
<version>
</version>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
<version>
</version>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
<version>
</version>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
<optional>
</optional>
</dependency>
</dependencies>
前端环境
vue create admin-frontend
cd admin-frontend
vue add element-ui
npm install axios vue-router vuex --save
vue create user-frontend
cd user-frontend
npm install vant axios vue-router vuex --save
开发环境建议配置 JDK 1.8+、Maven 3.6+、Node.js 14+、MySQL 5.7+ 以及 Redis 5.0+。
数据库设计
核心表结构
数据库设计围绕用户、商品、库存、订单和支付记录展开。以下是关键表的 DDL 定义,注意字段注释和索引的合理性。
用户表
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='卡密库存表';
其他关键表包括订单表 order、订单明细表 order_item、支付记录表 payment、管理员表 admin 以及系统日志表 sys_log,均遵循类似的设计规范。
后端实现
项目结构
Spring Boot 项目采用分层架构,清晰划分职责:
src/main/java/com/virtualcard/
├── config/
├── constant/
├── controller/
│ ├── api/
│ └── admin/
├── dao/
├── dto/
├── exception/
├── service/
├── util/
└── VirtualCardApplication.java
核心功能实现
1. 用户认证与授权
安全性是电商系统的底线。我们使用 Spring Security 配合 JWT 实现无状态认证。
@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();
}
}
JWT 工具类负责生成和验证 Token,设置合理的过期时间(例如 24 小时)。
2. 虚拟卡库存管理
库存扣减是并发场景下的难点。这里采用了'预锁定'策略,在创建订单时锁定卡密,防止超卖。
@Service
public 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;
@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;
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);
}
}
3. 订单服务
订单流程涉及状态流转和关联操作。创建订单时同步锁定库存,取消订单时自动释放。
@Service
public 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);
cardService.updateCardSecretsOrderId(cardSecrets.stream().map(CardSecret::getId).collect(Collectors.toList()), 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 || !order.getUserId().equals(userId) || order.getStatus() != OrderStatus.UNPAID.getCode()) {
throw new BusinessException("无法取消订单");
}
order.setStatus(OrderStatus.CANCELLED.getCode());
orderMapper.updateById(order);
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 || 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(); }
}
4. 微信支付集成
微信支付的回调验证至关重要,必须校验签名以防止伪造请求。
@Component
public 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 = 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) {
JSONObject json = JSON.parseObject(responseBody);
Map<String, String> result = new HashMap<>();
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 tradeState = json.getJSONObject("resource").getString("trade_state");
return "SUCCESS".equals(tradeState);
} catch (Exception e) { return false; }
}
}
Controller 层处理回调时,需提取请求头中的签名信息并调用上述工具类验证。
前端实现
用户端 (Vant)
移动端页面强调交互体验,使用 Vant 组件库快速构建。
虚拟卡列表页:支持搜索、分类切换、下拉刷新和分页加载。
<template>
<div>
<header-component title="虚拟卡商城" :show-back="false" />
<van-search v-model="searchKeyword" placeholder="搜索虚拟卡" shape="round" @search="onSearch" />
<van-tabs v-model="activeCategory" @click="onCategoryChange">
<van-tab v-for="category in categories" :key="category.id" :title="category.name" />
</van-tabs>
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list v-model="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
<card-item v-for="card in cardList" :key="card.id" :card="card" @click="goToDetail(card.id)" />
</van-list>
</van-pull-refresh>
</div>
</template>
<script>
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}`); }
}
};
</script>
订单创建页:展示商品信息、数量选择、地址确认及提交按钮。
支付页:根据浏览器环境判断是调起 JSAPI 还是跳转 H5 链接,并轮询检查支付结果。
管理端 (Element UI)
后台管理系统侧重数据录入与统计,使用 Element UI 提供丰富的表格和表单组件。
虚拟卡产品管理:支持批量操作、状态切换、图片上传及详情编辑。表格支持分页和筛选,对话框用于新增或修改产品信息。
微信 H5 支付集成
配置要点
- 商户号申请:登录微信支付商户平台完成资质认证。
- 域名配置:备案域名,配置授权目录和回调域名。
- 密钥证书:设置 APIv2 密钥,申请 APIv3 证书。
- 应用信息:配置 H5 支付场景参数。
流程实现
前端通过 isWeixinBrowser 判断是否在微信内。如果是,调用 WeixinJSBridge.invoke;否则直接跳转返回的 h5_url。后端监听回调接口,验证签名后更新订单状态,确保资金流与业务流一致。
import 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 || '支付失败')); }
});
}
});
}
@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");
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";
}
}
整个流程闭环后,用户获得卡密,商家收到款项,系统记录完整日志,确保交易可追溯。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online