初始化

This commit is contained in:
谢宇宁
2026-09-15 15:44:13 +07:00
commit a23ba685e9
1065 changed files with 101122 additions and 0 deletions
+170
View File
@@ -0,0 +1,170 @@
import '../../routers/jump_router.dart';
import '../../tools_base/loading/loading_alert_widget.dart';
import '../../tools_base/net/base_resp_bean.dart';
import '../../tools_base/net/net_code.dart';
import '../api_service/buy_service.dart';
/// 下单商品类型,对应 /product/buy 的 productType 字段。
/// 只收单品下单用到的类型;VIP 卡的 productType 由服务端随卡下发(新人卡 5、预售卡 21 等),
/// 走 [PayManager.buyVip] 的 int
enum ProductType {
/// 1 单个作品:金币视频、黄游(SEED_LINK)、社区帖子
media(1),
/// 19 整本/整部:漫画整本、动漫整部、短剧(买单集时靠 contentID 指定哪一集)
mediaAll(19),
/// 25 漫画单集
mediaChapter(25),
/// 101 群聊
group(101);
const ProductType(this.value);
/// 传给接口的原始值
final int value;
}
/// 支付管理:统一下单流程。
/// 内部包掉 loadingshow → cancel)、余额不足(code 8000)跳充值,
/// 外部只透传成功/失败回调,并可带来源页面标识 [source]。
class PayManager {
PayManager._();
static final PayManager _instance = PayManager._();
factory PayManager() => _instance;
/// 下单购买(普通商品:视频/漫画/帖子/群聊/短剧单集等)
/// [productID] 商品ID [productType] 商品类型,见 [ProductType]
/// [source] 来源页面标识
/// [contentID] 子内容ID(短剧买单集时传这一集的 id)——短剧归因就靠它和 productID(剧 id),不另发字段
/// [checkoutContextId] 付费墙下发的结算上下文
/// [requestId] 幂等键,一笔业务拆成多次请求时复用同一个 id;不传则自动生成,见 BuyService._idempotent
/// [jsonTransformation] 要拿模型而不是原始 json 时传,结果在 onSuccess 的 data.data 里
/// [jumpWalletOnInsufficient] 余额不足(code 8000)是否自动跳充值页,默认 true;
/// 已在充值页、或要就地弹金币支付的场景传 false(仅跳过跳转;失败提示由网络层统一弹)
/// [onSuccess] 支付成功回调,各页面自行处理副作用(Get.back / 刷新钱包等)
/// [onFailure] 业务失败回调,可选(失败提示已由网络层统一弹)
Future<void> buy(
String? productID,
ProductType productType, {
String? source,
String? couponId,
int? goldVideoCouponNum,
String? serviceId,
String? contentID,
String? checkoutContextId,
String? requestId,
Function(Map<String, dynamic> json)? jsonTransformation,
bool jumpWalletOnInsufficient = true,
required void Function(BaseRespBean data) onSuccess,
void Function(BaseRespBean? data)? onFailure,
}) {
return _request(
() => BuyService.buyVideo(
productID,
productType.value,
source: source,
couponId: couponId,
goldVideoCouponNum: goldVideoCouponNum,
serviceId: serviceId,
contentID: contentID,
checkoutContextId: checkoutContextId,
requestId: requestId,
jsonTransformation: jsonTransformation,
),
jumpWalletOnInsufficient: jumpWalletOnInsufficient,
//一次性商品「已经买过了」等价于买到手,见 _request
repeatAsSuccess: true,
onSuccess: onSuccess,
onFailure: onFailure,
);
}
/// 金币购买 VIP(含预售尾款)
/// [productType] VIP 卡类型由服务端随卡下发(新人卡 5、预售卡 21 等),不是 [ProductType],保持原样透传
/// [finalPayStatus] 预售业务:true 付尾款 / false 付预定款;null 非预售
/// [requestId] 幂等键,同 [buy]
/// [experimentId]/[experimentVariant]/[sessionId] VIP 卡皮 A/B:与 /mine/topay 对齐回传
/// [mediaId]/[contentId]/[checkoutContextId] 短剧付费墙开卡归因,同 [buy]
/// 其余同 [buy]
Future<void> buyVip(
int? productType,
String? productID,
String? productName,
int? discountedPrice, {
String? source,
String? couponId = "",
bool? finalPayStatus,
String? experimentId,
String? experimentVariant,
String? sessionId,
String? mediaId,
String? contentId,
String? checkoutContextId,
String? requestId,
bool jumpWalletOnInsufficient = true,
required void Function(BaseRespBean data) onSuccess,
void Function(BaseRespBean? data)? onFailure,
}) {
return _request(
() => BuyService.buyVip(
productType,
productID,
productName,
discountedPrice,
source: source,
couponId: couponId,
finalPayStatus: finalPayStatus,
experimentId: experimentId,
experimentVariant: experimentVariant,
sessionId: sessionId,
mediaId: mediaId,
contentId: contentId,
checkoutContextId: checkoutContextId,
requestId: requestId,
),
jumpWalletOnInsufficient: jumpWalletOnInsufficient,
onSuccess: onSuccess,
onFailure: onFailure,
);
}
/// 统一下单流程:loading + 余额不足(code 8000)跳充值页。
/// 有响应的失败提示由 HttpResponseInterceptor 全局弹;网络无响应(超时/断网)不额外处理。
/// [repeatAsSuccess] 8005 重复购买是否当成功。
/// 只有一次性商品(视频/漫画/帖子/群聊/短剧单集)能开——「已经买过了」就是已拥有;
/// VIP 不能开:会员是可叠加续费的,8005 不代表用户拿到了东西,
/// 当成功会误报购买埋点、弹「购买成功」并把充值页关掉
Future<void> _request(
Future<BaseRespBean> Function() request, {
bool jumpWalletOnInsufficient = true,
bool repeatAsSuccess = false,
required void Function(BaseRespBean data) onSuccess,
void Function(BaseRespBean? data)? onFailure,
}) async {
LoadingAlertWidget.show();
final BaseRespBean data;
try {
data = await request();
} finally {
// 先关 loading 再回调,避免 onSuccess 里的 Get.back 误弹到 loading 弹窗。
// 走 finally 是因为请求前的签名(generateRequestOption→_sign)会抛,
// 抛出去就没人关这层全屏 loading 了,用户只能杀进程
LoadingAlertWidget.cancel();
}
// 注意 8005 走进来时 data.data 是错误体不是订单结果,onSuccess 里取字段要判类型
if (data.isSuccess || (repeatAsSuccess && data.code == Code.REPEAT_BUY)) {
onSuccess(data);
return;
}
// 余额不足:拦截器只弹提示不跳转,这里补跳充值页(已在充值页则传 false 跳过)
if (data.code == Code.NOT_ENOUGH_MONEY && jumpWalletOnInsufficient) {
pushToWalletPage(tabPosition: 1);
}
onFailure?.call(data);
}
}