feat(tip): support dynamic gift products
This commit is contained in:
@@ -1,38 +1,73 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { PaymentRepository } from "@/data/repositories/payment_repository";
|
||||
import { TipPaymentPlansResponseSchema } from "@/data/schemas/payment";
|
||||
import {
|
||||
GiftProductsResponseSchema,
|
||||
TipMessageResponseSchema,
|
||||
} from "@/data/schemas/payment";
|
||||
import type { PaymentApi } from "@/data/services/api";
|
||||
import { Result } from "@/utils/result";
|
||||
|
||||
describe("PaymentRepository", () => {
|
||||
it("adapts tip plans without caching them as subscription plans", async () => {
|
||||
const getTipPlans = vi.fn().mockResolvedValue(
|
||||
TipPaymentPlansResponseSchema.parse({
|
||||
plans: [
|
||||
{
|
||||
planId: "tip_coffee_usd_9_99",
|
||||
planName: "Medium Coffee",
|
||||
amountCents: 999,
|
||||
currency: "USD",
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
it("loads a character gift catalog without adapting product metadata", async () => {
|
||||
const catalog = GiftProductsResponseSchema.parse({
|
||||
characterId: "elio",
|
||||
categories: [
|
||||
{
|
||||
category: "coffee",
|
||||
name: "Coffee",
|
||||
productCount: 1,
|
||||
imageUrl: null,
|
||||
},
|
||||
],
|
||||
plans: [
|
||||
{
|
||||
planId: "tip_coffee_usd_9_99",
|
||||
planName: "Golden Reserve",
|
||||
orderType: "tip",
|
||||
tipType: "coffee_medium",
|
||||
category: "coffee",
|
||||
characterId: "elio",
|
||||
description: "Buy Elio a coffee",
|
||||
imageUrl: null,
|
||||
amountCents: 999,
|
||||
currency: "USD",
|
||||
autoRenew: false,
|
||||
isFirstRechargeOffer: false,
|
||||
firstRechargeDiscountPercent: 0,
|
||||
promotionType: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const getGiftProducts = vi.fn().mockResolvedValue(catalog);
|
||||
const repository = new PaymentRepository({
|
||||
getTipPlans,
|
||||
getGiftProducts,
|
||||
} as unknown as PaymentApi);
|
||||
|
||||
const result = await repository.getTipPlans();
|
||||
const result = await repository.getGiftProducts("elio");
|
||||
|
||||
expect(getTipPlans).toHaveBeenCalledOnce();
|
||||
expect(Result.isOk(result) && result.data.plans[0]).toMatchObject({
|
||||
expect(getGiftProducts).toHaveBeenCalledWith("elio");
|
||||
expect(Result.isOk(result) && result.data).toBe(catalog);
|
||||
});
|
||||
|
||||
it("validates and forwards a Tip message order id", async () => {
|
||||
const tipMessage = TipMessageResponseSchema.parse({
|
||||
orderId: "pay_xxx",
|
||||
characterId: "elio",
|
||||
planId: "tip_coffee_usd_9_99",
|
||||
planName: "Medium Coffee",
|
||||
orderType: "tip",
|
||||
amountCents: 999,
|
||||
currency: "USD",
|
||||
isFirstRechargeOffer: false,
|
||||
productName: "Golden Reserve",
|
||||
tipCount: 1,
|
||||
poolIndex: 1,
|
||||
message: "Thank you.",
|
||||
});
|
||||
const getTipMessage = vi.fn().mockResolvedValue(tipMessage);
|
||||
const repository = new PaymentRepository({
|
||||
getTipMessage,
|
||||
} as unknown as PaymentApi);
|
||||
|
||||
const result = await repository.getTipMessage("pay_xxx");
|
||||
|
||||
expect(getTipMessage).toHaveBeenCalledWith({ orderId: "pay_xxx" });
|
||||
expect(Result.isOk(result) && result.data.message).toBe("Thank you.");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
*/
|
||||
import type {
|
||||
CreatePaymentOrderResponse,
|
||||
GiftProductsResponse,
|
||||
PayChannel,
|
||||
PaymentOrderStatusResponse,
|
||||
PaymentPlansResponse,
|
||||
TipMessageResponse,
|
||||
} from "@/data/schemas/payment";
|
||||
import type { Result } from "@/utils/result";
|
||||
|
||||
@@ -16,8 +18,8 @@ export interface IPaymentRepository {
|
||||
/** 获取本地缓存套餐列表。 */
|
||||
getCachedPlans(): Promise<Result<PaymentPlansResponse | null>>;
|
||||
|
||||
/** 获取咖啡打赏套餐列表。 */
|
||||
getTipPlans(): Promise<Result<PaymentPlansResponse>>;
|
||||
/** 获取当前角色的完整礼物目录。 */
|
||||
getGiftProducts(characterId: string): Promise<Result<GiftProductsResponse>>;
|
||||
|
||||
/** 清除本地缓存套餐列表。 */
|
||||
clearCachedPlans(): Promise<Result<void>>;
|
||||
@@ -32,4 +34,7 @@ export interface IPaymentRepository {
|
||||
|
||||
/** 查询支付订单状态。 */
|
||||
getOrderStatus(orderId: string): Promise<Result<PaymentOrderStatusResponse>>;
|
||||
|
||||
/** 获取已支付礼物订单的角色感谢文案。 */
|
||||
getTipMessage(orderId: string): Promise<Result<TipMessageResponse>>;
|
||||
}
|
||||
|
||||
@@ -7,10 +7,13 @@ import type { IPaymentRepository } from "@/data/repositories/interfaces";
|
||||
import {
|
||||
CreatePaymentOrderRequestSchema,
|
||||
CreatePaymentOrderResponse,
|
||||
GiftProductsResponse,
|
||||
PayChannel,
|
||||
PaymentOrderStatusResponse,
|
||||
PaymentPlansResponse,
|
||||
PaymentPlansResponseSchema,
|
||||
TipMessageRequestSchema,
|
||||
TipMessageResponse,
|
||||
} from "@/data/schemas/payment";
|
||||
import { PaymentApi, paymentApi } from "@/data/services/api";
|
||||
import { PaymentPlansStorage } from "@/data/storage/payment";
|
||||
@@ -38,25 +41,11 @@ export class PaymentRepository implements IPaymentRepository {
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取咖啡打赏套餐列表,不写入通用套餐缓存。 */
|
||||
async getTipPlans(): Promise<Result<PaymentPlansResponse>> {
|
||||
return Result.wrap(async () => {
|
||||
const response = await this.api.getTipPlans();
|
||||
return PaymentPlansResponseSchema.parse({
|
||||
plans: response.plans.map((plan) => ({
|
||||
...plan,
|
||||
orderType: "tip",
|
||||
vipDays: null,
|
||||
dolAmount: null,
|
||||
creditBalance: 0,
|
||||
originalAmountCents: null,
|
||||
dailyPriceCents: null,
|
||||
isFirstRechargeOffer: false,
|
||||
firstRechargeDiscountPercent: null,
|
||||
promotionType: null,
|
||||
})),
|
||||
});
|
||||
});
|
||||
/** 获取当前角色的完整礼物目录,不写入订阅套餐缓存。 */
|
||||
async getGiftProducts(
|
||||
characterId: string,
|
||||
): Promise<Result<GiftProductsResponse>> {
|
||||
return Result.wrap(() => this.api.getGiftProducts(characterId));
|
||||
}
|
||||
|
||||
/** 清除本地缓存套餐列表。 */
|
||||
@@ -89,6 +78,12 @@ export class PaymentRepository implements IPaymentRepository {
|
||||
): Promise<Result<PaymentOrderStatusResponse>> {
|
||||
return Result.wrap(() => this.api.getOrderStatus(orderId));
|
||||
}
|
||||
|
||||
/** 获取已支付礼物订单的角色感谢文案。 */
|
||||
async getTipMessage(orderId: string): Promise<Result<TipMessageResponse>> {
|
||||
const request = TipMessageRequestSchema.parse({ orderId });
|
||||
return Result.wrap(() => this.api.getTipMessage(request));
|
||||
}
|
||||
}
|
||||
|
||||
/** 全局懒单例。 */
|
||||
|
||||
Reference in New Issue
Block a user