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

277 lines
7.6 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.

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余额支付返回 PayResultpaid/balance
* - method=wechat微信支付返回小程序码 WechatNativePayResultminiappQrcode
* 后端会基于原 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));
}