- 修正续费请求路径为后端实际的 /renew-pay/{id},避免 404 错误
- 续费接口新增 method 和 envVersion 参数支持余额与微信支付
- 续费请求返回支付结果,微信支付返回小程序码,余额支付直接成功
- 续费不再分步调起支付,前端确认续费逻辑简化为一步调用后端接口
- 后端 renewPay 优化,避免放弃支付导致服务中断及时长丢失
- 后端续费激活逻辑调整,续费保留原订阅有效期累计时长
- 排查并解决微信小程序支付配置缺失导致续费失败的问题
- 代码及注释同步更新,完善续费流程整体一致性与稳定性
277 lines
7.6 KiB
TypeScript
277 lines
7.6 KiB
TypeScript
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));
|
||
}
|
||
|
||
/**
|
||
* 续费:基于已有订阅创建续费订单并生成支付码(一步完成,对齐后端 /renew-pay)
|
||
* POST /app/subscription/renew-pay/{id}?period=month&method=wechat&envVersion=trial
|
||
* - method=balance:余额支付,返回 PayResult(paid/balance)
|
||
* - method=wechat:微信支付,返回小程序码 WechatNativePayResult(miniappQrcode)
|
||
* 后端会基于原 expireTime 或 now(取较大者)延长到期时间
|
||
*/
|
||
export async function renewSubscription(
|
||
id: number,
|
||
period: 'month' | 'year' = 'month',
|
||
method: 'balance' | 'wechat' = 'wechat',
|
||
envVersion?: string
|
||
): Promise<PayResult | WechatNativePayResult> {
|
||
const res = await request.post<ApiResult<PayResult | WechatNativePayResult>>(
|
||
`${BASE}/renew-pay/${id}`,
|
||
undefined,
|
||
{ params: { period, method, envVersion } }
|
||
);
|
||
if (res.data.code === 0) {
|
||
return res.data.data as PayResult | WechatNativePayResult;
|
||
}
|
||
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));
|
||
}
|