Files
guilixu-admin/src/store/modules/appSubscription.ts
赵忠林 015cd50c08 feat(subscription): 对齐续费接口为一步生成支付码
- 修正续费请求路径为后端实际的 /renew-pay/{id},避免 404 错误
- 续费接口新增 method 和 envVersion 参数支持余额与微信支付
- 续费请求返回支付结果,微信支付返回小程序码,余额支付直接成功
- 续费不再分步调起支付,前端确认续费逻辑简化为一步调用后端接口
- 后端 renewPay 优化,避免放弃支付导致服务中断及时长丢失
- 后端续费激活逻辑调整,续费保留原订阅有效期累计时长
- 排查并解决微信小程序支付配置缺失导致续费失败的问题
- 代码及注释同步更新,完善续费流程整体一致性与稳定性
2026-07-15 22:19:54 +08:00

376 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 应用订阅 store
* 数据来源websopy 特殊接口db_websopy.app_subscription 表)
* 接口域名https://websopy-api.websoft.top/api
* 后端com.gxwebsoft.app.controller.AppSubscriptionController
*/
import { defineStore } from 'pinia';
import {
pageMySubscriptions,
getSubscriptionDetail,
getSubscriptionDetailByNo,
checkSubscriptionStatus,
checkPurchased,
getBalance,
subscribe,
generatePayQrcode,
paySubscription,
mpPrepay,
mpConfirm,
renewSubscription,
cancelSubscription,
toggleEnable
} from '@/api/app/appSubscription';
import type {
AppSubscription,
AppSubscriptionQueryParam,
SubscribeParam,
SubscribeResult,
GeneratePayQrcodeParam,
GeneratePayQrcodeResult,
CheckStatusResult,
PayResult,
WechatNativePayResult,
MpPrepayResult
} from '@/api/app/appSubscription/model';
import type { PageResult } from '@/api';
export interface AppSubscriptionState {
// 我的订阅列表
subscriptionList: AppSubscription[];
// 总数量
total: number;
// 当前查看的订阅详情
currentSubscription: AppSubscription | null;
// 用户余额
balance: number;
// 加载状态
loading: boolean;
// 最后更新时间
lastUpdateTime: number | null;
// 缓存有效期(毫秒)
cacheExpiry: number;
}
export const useAppSubscriptionStore = defineStore('appSubscription', {
state: (): AppSubscriptionState => ({
subscriptionList: [],
total: 0,
currentSubscription: null,
balance: 0,
loading: false,
lastUpdateTime: null,
// 默认缓存5分钟订阅涉及支付状态不宜过长
cacheExpiry: 5 * 60 * 1000
}),
getters: {
/**
* 已激活订阅
*/
activeSubscriptions: (state): AppSubscription[] => {
return state.subscriptionList.filter((s) => s.status === 'active');
},
/**
* 待支付订阅
*/
pendingSubscriptions: (state): AppSubscription[] => {
return state.subscriptionList.filter((s) => s.status === 'pending');
},
/**
* 已过期订阅
*/
expiredSubscriptions: (state): AppSubscription[] => {
return state.subscriptionList.filter((s) => s.status === 'expired');
},
/**
* 检查缓存是否有效
*/
isCacheValid: (state): boolean => {
if (!state.lastUpdateTime) return false;
const now = Date.now();
return now - state.lastUpdateTime < state.cacheExpiry;
}
},
actions: {
// ============================================================
// 查询类
// ============================================================
/**
* 获取我的订阅列表(分页,带缓存)
* @param params 查询参数page/limit/status
* @param forceRefresh 是否强制刷新(切换 status 过滤时建议传 true
*/
async fetchMySubscriptions(
params: AppSubscriptionQueryParam = { page: 1, limit: 10 },
forceRefresh = false
): Promise<PageResult<AppSubscription>> {
// 缓存有效且不强制刷新,直接返回缓存列表
if (!forceRefresh && this.isCacheValid && this.subscriptionList.length > 0) {
return { list: this.subscriptionList, count: this.total };
}
this.loading = true;
try {
const data = await pageMySubscriptions(params);
this.subscriptionList = data.list || [];
this.total = data.count || 0;
this.lastUpdateTime = Date.now();
return data;
} catch (error) {
console.error('获取我的订阅列表失败:', error);
throw error;
} finally {
this.loading = false;
}
},
/**
* 获取订阅详情按ID
*/
async fetchDetail(id: number): Promise<AppSubscription> {
try {
const data = await getSubscriptionDetail(id);
this.currentSubscription = data;
return data;
} catch (error) {
console.error('获取订阅详情失败:', error);
throw error;
}
},
/**
* 根据订阅编号查询详情(小程序入口用)
*/
async fetchDetailByNo(subscriptionNo: string): Promise<AppSubscription> {
try {
const data = await getSubscriptionDetailByNo(subscriptionNo);
this.currentSubscription = data;
return data;
} catch (error) {
console.error('根据订阅编号查询详情失败:', error);
throw error;
}
},
/**
* 查询支付状态(前端轮询用,强制实时请求,不走缓存)
*/
async checkStatus(subscriptionNo: string): Promise<CheckStatusResult> {
return await checkSubscriptionStatus(subscriptionNo);
},
/**
* 检查是否已购买某应用(强制实时请求,不走缓存)
*/
async checkPurchased(productId: number): Promise<boolean> {
return await checkPurchased(productId);
},
/**
* 获取当前用户余额
*/
async fetchBalance(): Promise<number> {
try {
const data = await getBalance();
this.balance = data.balance;
return data.balance;
} catch (error) {
console.error('获取用户余额失败:', error);
throw error;
}
},
// ============================================================
// 订阅与支付操作
// ============================================================
/**
* 创建订阅
* 免费应用直接激活,付费应用创建待支付记录
*/
async subscribe(data: SubscribeParam): Promise<SubscribeResult> {
try {
const result = await subscribe(data);
// 订阅状态变化,失效缓存
this.invalidateCache();
return result;
} catch (error) {
console.error('创建订阅失败:', error);
throw error;
}
},
/**
* 生成支付小程序码(同时创建订阅记录)
*/
async generatePayQrcode(
data: GeneratePayQrcodeParam
): Promise<GeneratePayQrcodeResult> {
try {
const result = await generatePayQrcode(data);
// 已创建订阅记录,失效缓存
this.invalidateCache();
return result;
} catch (error) {
console.error('生成支付小程序码失败:', error);
throw error;
}
},
/**
* 发起支付
* @param id 订阅ID
* @param method 支付方式balance-余额支付 / wechat-微信支付
* @param envVersion 小程序版本微信支付时使用develop/trial/release
*/
async pay(
id: number,
method: 'balance' | 'wechat' = 'wechat',
envVersion?: string
): Promise<PayResult | WechatNativePayResult> {
try {
const result = await paySubscription(id, method, envVersion);
// 支付成功后余额/订阅状态变化,失效缓存并刷新余额
this.invalidateCache();
if (method === 'balance') {
const payResult = result as PayResult;
if (typeof payResult.balance === 'number') {
this.balance = payResult.balance;
}
}
return result;
} catch (error) {
console.error('发起支付失败:', error);
throw error;
}
},
/**
* 小程序 JSAPI 预支付下单
*/
async mpPrepay(id: number, openid: string): Promise<MpPrepayResult> {
try {
return await mpPrepay(id, { openid });
} catch (error) {
console.error('小程序预支付失败:', error);
throw error;
}
},
/**
* 小程序支付成功确认
*/
async mpConfirm(
subscriptionNo: string,
transactionId?: string
): Promise<PayResult> {
try {
const result = await mpConfirm(subscriptionNo, { transactionId });
// 支付确认后订阅状态变化,失效缓存
this.invalidateCache();
return result;
} catch (error) {
console.error('小程序支付确认失败:', error);
throw error;
}
},
// ============================================================
// 订阅管理
// ============================================================
/**
* 续费:基于已有订阅创建续费订单并生成支付码(一步完成,对齐后端 /renew-pay
* @param id 订阅ID
* @param period 周期month/year
* @param method 支付方式balance/wechat
* @param envVersion 小程序版本(微信支付时使用)
*/
async renew(
id: number,
period: 'month' | 'year' = 'month',
method: 'balance' | 'wechat' = 'wechat',
envVersion?: string
): Promise<PayResult | WechatNativePayResult> {
try {
const result = await renewSubscription(id, period, method, envVersion);
this.invalidateCache();
return result;
} catch (error) {
console.error('续费失败:', error);
throw error;
}
},
/**
* 退订/取消
*/
async cancel(id: number): Promise<string> {
try {
const msg = await cancelSubscription(id);
this.invalidateCache();
return msg;
} catch (error) {
console.error('退订失败:', error);
throw error;
}
},
/**
* 启用/禁用
*/
async toggleEnable(id: number, enabled: boolean): Promise<string> {
try {
const msg = await toggleEnable(id, enabled);
this.invalidateCache();
return msg;
} catch (error) {
console.error('启用/禁用失败:', error);
throw error;
}
},
// ============================================================
// 缓存控制
// ============================================================
/**
* 失效缓存(仅重置时间标记,保留已有数据,下次拉取时更新)
* 用于订阅/支付/管理操作后,确保下次查询获取最新数据
*/
invalidateCache() {
this.lastUpdateTime = null;
},
/**
* 清除缓存(清空数据并重置时间标记)
*/
clearCache() {
this.subscriptionList = [];
this.total = 0;
this.currentSubscription = null;
this.lastUpdateTime = null;
},
/**
* 强制刷新订阅列表
*/
async refresh() {
return await this.fetchMySubscriptions({ page: 1, limit: 10 }, true);
},
/**
* 设置缓存有效期
*/
setCacheExpiry(expiry: number) {
this.cacheExpiry = expiry;
}
}
});