feat(appSubscription): 新增应用订阅模块及商城欢迎横幅Logo支持

- 新增 useAppSubscriptionStore,封装 websopy 特殊接口的应用订阅功能
- 实现订阅列表分页查询、详情获取、支付状态查询、订阅管理等接口调用
- 适配不同支付方式,支持余额支付、微信支付及小程序支付预支付流程
- 在商城 Dashboard 欢迎横幅左侧新增商城 Logo 显示,读取商城设置的 shopLogo 字段
- 使用图像压缩接口处理 Logo 大小和质量,优化加载体验
- 优化 Dashboard 待处理事项展示,恢复退款和优惠券使用项
- 统一订阅接口调用地址为独立域名,复用请求工具共享认证和错误处理
This commit is contained in:
2026-07-15 12:49:05 +08:00
parent e2dd8dbc18
commit 9e65007e65
5 changed files with 909 additions and 6 deletions

View File

@@ -0,0 +1,269 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type {
AppSubscription,
AppSubscriptionQueryParam,
SubscribeParam,
SubscribeResult,
GeneratePayQrcodeParam,
GeneratePayQrcodeResult,
CheckStatusResult,
PayResult,
WechatNativePayResult,
MpPrepayParam,
MpPrepayResult,
MpConfirmParam,
BalanceResult
} from './model';
/**
* websopy 特殊接口域名(与项目主 API 域名不同)
* 后端 Controller: com.gxwebsoft.app.controller.AppSubscriptionController
* @RequestMapping("/api/app/subscription"),后端无 context-path
* 因此完整 URL = https://websopy-api.websoft.top + /api/app/subscription/xxx
*/
const WEBSOPY_API_BASE = 'https://websopy-api.websoft.top/api';
const BASE = `${WEBSOPY_API_BASE}/app/subscription`;
/**
* 我的订阅列表(分页)
* GET /app/subscription/my/page
*/
export async function pageMySubscriptions(
params: AppSubscriptionQueryParam
): Promise<PageResult<AppSubscription>> {
const res = await request.get<ApiResult<PageResult<AppSubscription>>>(
`${BASE}/my/page`,
{ params }
);
if (res.data.code === 0) {
return (
res.data.data || { list: [], count: 0 }
);
}
return Promise.reject(new Error(res.data.message));
}
/**
* 订阅详情按ID
* GET /app/subscription/detail/{id}
*/
export async function getSubscriptionDetail(
id: number
): Promise<AppSubscription> {
const res = await request.get<ApiResult<AppSubscription>>(
`${BASE}/detail/${id}`
);
if (res.data.code === 0) {
return res.data.data as AppSubscription;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据订阅编号查询详情(小程序入口用)
* GET /app/subscription/detail-by-no/{subscriptionNo}
*/
export async function getSubscriptionDetailByNo(
subscriptionNo: string
): Promise<AppSubscription> {
const res = await request.get<ApiResult<AppSubscription>>(
`${BASE}/detail-by-no/${subscriptionNo}`
);
if (res.data.code === 0) {
return res.data.data as AppSubscription;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询支付状态(前端轮询用)
* GET /app/subscription/check-status/{subscriptionNo}
*/
export async function checkSubscriptionStatus(
subscriptionNo: string
): Promise<CheckStatusResult> {
const res = await request.get<ApiResult<CheckStatusResult>>(
`${BASE}/check-status/${subscriptionNo}`
);
if (res.data.code === 0) {
return res.data.data as CheckStatusResult;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 检查是否已购买某应用
* GET /app/subscription/check-purchased/{productId}
* 返回 boolean
*/
export async function checkPurchased(
productId: number
): Promise<boolean> {
const res = await request.get<ApiResult<boolean>>(
`${BASE}/check-purchased/${productId}`
);
if (res.data.code === 0) {
return res.data.data === true;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 获取当前用户余额
* GET /app/subscription/balance
*/
export async function getBalance(): Promise<BalanceResult> {
const res = await request.get<ApiResult<BalanceResult>>(`${BASE}/balance`);
if (res.data.code === 0) {
return (
res.data.data || { balance: 0 }
);
}
return Promise.reject(new Error(res.data.message));
}
/**
* 创建订阅
* POST /app/subscription/subscribe
* 免费应用直接激活,付费应用创建待支付记录
*/
export async function subscribe(
data: SubscribeParam
): Promise<SubscribeResult> {
const res = await request.post<ApiResult<SubscribeResult>>(
`${BASE}/subscribe`,
data
);
if (res.data.code === 0) {
return res.data.data as SubscribeResult;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 生成支付小程序码(同时创建订阅记录)
* POST /app/subscription/generate-pay-qrcode
*/
export async function generatePayQrcode(
data: GeneratePayQrcodeParam
): Promise<GeneratePayQrcodeResult> {
const res = await request.post<ApiResult<GeneratePayQrcodeResult>>(
`${BASE}/generate-pay-qrcode`,
data
);
if (res.data.code === 0) {
return res.data.data as GeneratePayQrcodeResult;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 发起支付
* POST /app/subscription/pay/{id}?method=balance|wechat&envVersion=xxx
* - method=balance余额支付返回 PayResult
* - method=wechat微信 Native 支付,返回小程序码 WechatNativePayResult
*/
export async function paySubscription(
id: number,
method: 'balance' | 'wechat' = 'wechat',
envVersion?: string
): Promise<PayResult | WechatNativePayResult> {
const res = await request.post<ApiResult<PayResult | WechatNativePayResult>>(
`${BASE}/pay/${id}`,
undefined,
{ params: { method, envVersion } }
);
if (res.data.code === 0) {
return res.data.data as PayResult | WechatNativePayResult;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 小程序 JSAPI 预支付下单
* POST /app/subscription/mp-prepay/{id}
* Body: { openid }
*/
export async function mpPrepay(
id: number,
data: MpPrepayParam
): Promise<MpPrepayResult> {
const res = await request.post<ApiResult<MpPrepayResult>>(
`${BASE}/mp-prepay/${id}`,
data
);
if (res.data.code === 0) {
return res.data.data as MpPrepayResult;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 小程序支付成功确认
* POST /app/subscription/mp-confirm/{subscriptionNo}
* Body: { transactionId? }
*/
export async function mpConfirm(
subscriptionNo: string,
data?: MpConfirmParam
): Promise<PayResult> {
const res = await request.post<ApiResult<PayResult>>(
`${BASE}/mp-confirm/${subscriptionNo}`,
data
);
if (res.data.code === 0) {
return (
res.data.data || { paid: true, subscriptionNo }
);
}
return Promise.reject(new Error(res.data.message));
}
/**
* 续费
* POST /app/subscription/renew/{id}?period=month|year
*/
export async function renewSubscription(
id: number,
period: 'month' | 'year' = 'month'
): Promise<string> {
const res = await request.post<ApiResult<string>>(`${BASE}/renew/${id}`, undefined, {
params: { period }
});
if (res.data.code === 0) {
return res.data.message || '续费成功';
}
return Promise.reject(new Error(res.data.message));
}
/**
* 退订/取消
* POST /app/subscription/cancel/{id}
*/
export async function cancelSubscription(id: number): Promise<string> {
const res = await request.post<ApiResult<string>>(`${BASE}/cancel/${id}`);
if (res.data.code === 0) {
return res.data.message || '退订成功';
}
return Promise.reject(new Error(res.data.message));
}
/**
* 启用/禁用
* POST /app/subscription/toggle-enable/{id}?enabled=true|false
*/
export async function toggleEnable(
id: number,
enabled: boolean
): Promise<string> {
const res = await request.post<ApiResult<string>>(
`${BASE}/toggle-enable/${id}`,
undefined,
{ params: { enabled } }
);
if (res.data.code === 0) {
return res.data.message || (enabled ? '已启用' : '已禁用');
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -0,0 +1,219 @@
import type { PageParam } from '@/api';
/**
* 订阅状态: pending-待支付 active-已激活 expired-已过期 cancelled-已取消
*/
export type SubscriptionStatus =
| 'pending'
| 'active'
| 'expired'
| 'cancelled';
/**
* 价格类型: free-免费 one_time-买断 subscription-订阅
*/
export type PriceType = 'free' | 'one_time' | 'subscription';
/**
* 订阅周期: month-月 quarter-季 year-年
*/
export type SubscriptionPeriod = 'month' | 'quarter' | 'year';
/**
* 支付方式: 0-余额 1-微信 2-支付宝 12-免费
*/
export type PayType = 0 | 1 | 2 | 12;
/**
* 应用订阅实体(对应表 app_subscription
* 字段对照后端 com.gxwebsoft.app.entity.AppSubscription
*/
export interface AppSubscription {
// 主键ID
id?: number;
// 订阅编号(业务唯一)
subscriptionNo?: string;
// 购买用户ID
userId?: number;
// 应用产品ID
productId?: number;
// 租户ID
tenantId?: number;
// 订阅状态
status?: SubscriptionStatus;
// 价格类型
priceType?: PriceType;
// 原价(单位:元)
originalPrice?: number;
// 实付金额(单位:元)
payPrice?: number;
// 支付方式
payType?: PayType;
// 支付状态: 0-未支付 1-已支付
payStatus?: number;
// 支付时间
payTime?: string;
// 第三方交易号
transactionId?: string;
// 订阅周期
subscriptionPeriod?: SubscriptionPeriod;
// 生效时间
startTime?: string;
// 到期时间(订阅型)
expireTime?: string;
// 是否自动续费 0-否 1-是
autoRenew?: number;
// 分配的域名
instanceDomain?: string;
// 实例管理后台URL
instanceAdminUrl?: string;
// 实例配置JSON
instanceConfig?: string;
// 关联的支付订单号
orderNo?: string;
// 关联的支付订单ID
orderId?: number;
// 备注
remark?: string;
// 排序
sortNumber?: number;
// 创建时间
createTime?: string;
// 更新时间
updateTime?: string;
// ===== 关联查询字段(非数据库字段) =====
productName?: string;
productIcon?: string;
productLogo?: string;
productAppType?: number;
productDescription?: string;
developerName?: string;
}
/**
* 我的订阅分页查询参数
*/
export interface AppSubscriptionQueryParam extends PageParam {
// 订阅状态过滤
status?: SubscriptionStatus;
}
/**
* 创建订阅参数
*/
export interface SubscribeParam {
// 应用产品ID
productId: number;
// 订阅周期,默认 month
subscriptionPeriod?: SubscriptionPeriod;
}
/**
* 生成支付小程序码参数
*/
export interface GeneratePayQrcodeParam {
// 应用产品ID
productId: number;
// 订阅周期,默认 month
subscriptionPeriod?: SubscriptionPeriod;
// 小程序版本develop / trial / release默认 trial
envVersion?: string;
}
/**
* 创建订阅返回结果
*/
export interface SubscribeResult {
subscriptionId: number;
subscriptionNo: string;
status: SubscriptionStatus;
message: string;
payPrice?: number;
orderNo?: string;
}
/**
* 生成支付小程序码返回结果
*/
export interface GeneratePayQrcodeResult {
subscriptionNo: string;
subscriptionId: number;
qrcodeBase64: string;
productName: string;
payPrice: number;
}
/**
* 查询支付状态返回结果
*/
export interface CheckStatusResult {
paid: boolean;
payStatus: number;
status: SubscriptionStatus;
payTime?: string;
transactionId?: string;
id: number;
subscriptionNo: string;
productId: number;
productName?: string;
productLogo?: string;
priceType?: PriceType;
payPrice?: number;
subscriptionPeriod?: SubscriptionPeriod;
}
/**
* 余额支付返回结果
*/
export interface PayResult {
paid: boolean;
balance?: number;
subscriptionNo: string;
}
/**
* 微信 Native 支付Web 端)返回结果
*/
export interface WechatNativePayResult {
subscriptionId: number;
subscriptionNo: string;
miniappQrcode: string;
miniappPagePath: string;
payPrice: number;
}
/**
* 小程序 JSAPI 预支付返回结果
*/
export interface MpPrepayResult {
subscriptionId: number;
subscriptionNo: string;
outTradeNo: string;
timeStamp: string;
nonceStr: string;
package: string;
signType: string;
paySign: string;
payPrice: number;
}
/**
* 获取余额返回结果
*/
export interface BalanceResult {
balance: number;
}
/**
* 小程序预支付参数
*/
export interface MpPrepayParam {
openid: string;
}
/**
* 小程序支付确认参数
*/
export interface MpConfirmParam {
transactionId?: string;
}