fix: apply telegram group member monthly price

This commit is contained in:
2569718930@qq.com
2026-06-02 21:35:35 +08:00
parent 876417c5ec
commit 137b0d8b3a
7 changed files with 101 additions and 9 deletions
@@ -23,6 +23,10 @@ export function runTests() {
path.join(projectRoot, "components", "account", "usePaymentFlow.ts"),
"utf8",
);
const useBilling = fs.readFileSync(
path.join(projectRoot, "components", "account", "useBilling.ts"),
"utf8",
);
const types = fs.readFileSync(
path.join(projectRoot, "components", "account", "types.ts"),
"utf8",
@@ -59,6 +63,19 @@ export function runTests() {
!usePaymentFlow.includes("monthlyPlanList"),
"payment hooks must not filter checkout plans down to monthly only",
);
assert(
useAccountPayment.includes("applyTelegramGroupPricingToPlanList") &&
useAccountPayment.includes("backend?.telegram_pricing") &&
useAccountPayment.includes('=== "pro_monthly"') &&
useAccountPayment.includes("amount_usdc: telegramAmountUsdc"),
"account payment plan cards must display the 5 USDC Telegram group member monthly price after /bind verification",
);
assert(
useBilling.includes("telegramGroupPriceApplies") &&
useBilling.includes("backend?.telegram_pricing?.is_group_member") &&
useBilling.includes("!telegramGroupPriceApplies"),
"billing must not let referral first-month pricing override the lower Telegram group member monthly price",
);
assert(
types.includes("ReferralSummary") &&
types.includes("referral?: ReferralSummary | null") &&
+2 -2
View File
@@ -271,8 +271,8 @@ export function createAccountCopy(isEn: boolean): Record<string, string> {
verifyUnknown: isEn ? "Unknown error" : "未知错误",
// ── Telegram bind messages ────────────────────────────────────────
telegramVerifySuccess: isEn
? "Telegram group membership verified. Checkout follows the selected Pro plan."
: "Telegram 群成员验证成功,结算金额以当前选择的 Pro 套餐为准。",
? "Telegram group membership verified. Member monthly price {amount} USDC is active."
: "Telegram 群成员验证成功,群友月付价 {amount} USDC 已生效。",
telegramBindClickHint: isEn
? "Open the Telegram Bot, click Start, and confirm binding. Then refresh this page to request group entry."
: "已打开 Telegram Bot,请在 Bot 内点击 Start 并确认绑定;完成后刷新本页再申请入群。",
@@ -13,14 +13,37 @@ import type {
AuthMeResponse,
BoundWallet,
PaymentConfig,
PaymentPlan,
ProviderMode,
InjectedProviderOption,
TelegramPricing,
} from "./types";
import { usePaymentState } from "./usePaymentState";
import { useWalletBind } from "./useWalletBind";
import { usePaymentFlow } from "./usePaymentFlow";
import { useBilling } from "./useBilling";
// ============================================================
function telegramMemberAmountUsdc(pricing?: TelegramPricing | null) {
if (!pricing?.is_group_member) return "";
const amount = String(pricing.amount_usdc || "").trim();
const numeric = Number(amount);
return Number.isFinite(numeric) && numeric > 0 ? amount : "";
}
function applyTelegramGroupPricingToPlanList(
plans: PaymentPlan[],
pricing?: TelegramPricing | null,
): PaymentPlan[] {
const telegramAmountUsdc = telegramMemberAmountUsdc(pricing);
if (!telegramAmountUsdc) return plans;
return plans.map((plan) =>
String(plan.plan_code || "").toLowerCase() === "pro_monthly"
? { ...plan, amount_usdc: telegramAmountUsdc }
: plan,
);
}
// ============================================================
export interface UseAccountPaymentParams {
isEn: boolean;
@@ -387,7 +410,10 @@ export function useAccountPayment(params: UseAccountPaymentParams) {
]);
// ── Selected plan (derived, shared across sub-hooks) ───
const effectivePlanList = paymentConfig?.plans || [];
const effectivePlanList = applyTelegramGroupPricingToPlanList(
paymentConfig?.plans || [],
backend?.telegram_pricing,
);
const selectedPlan = effectivePlanList.find((p) => p.plan_code === selectedPlanCode) || effectivePlanList[0];
// ── useWalletBind ──────────────────────────────────────
@@ -122,6 +122,10 @@ export function useBilling(params: UseBillingParams) {
const listAmount =
Number.isFinite(listAmountRaw) && listAmountRaw > 0 ? listAmountRaw : 29.9;
const selectedPlanCode = String(selectedPlan?.plan_code || "").toLowerCase();
const telegramGroupPriceApplies = Boolean(
selectedPlanCode === "pro_monthly" &&
backend?.telegram_pricing?.is_group_member,
);
const referral = backend?.referral;
const referralPending = Boolean(
referral?.applied_code ||
@@ -137,6 +141,7 @@ export function useBilling(params: UseBillingParams) {
const referralApplies =
selectedPlanCode === "pro_monthly" &&
referralPending &&
!telegramGroupPriceApplies &&
backend?.subscription_active !== true;
const planAmount = referralApplies
? Number.isFinite(discountedMonthlyRaw) && discountedMonthlyRaw > 0
@@ -187,6 +192,7 @@ export function useBilling(params: UseBillingParams) {
paymentConfig?.points_redemption,
backend?.referral,
backend?.subscription_active,
backend?.telegram_pricing?.is_group_member,
selectedPlan?.plan_code,
selectedPlan?.amount_usdc,
totalPoints,
+1 -1
View File
@@ -98,7 +98,7 @@ class TelegramGroupPricing:
os.getenv("TELEGRAM_CHAT_ID"),
)
self.group_chat_ids = dedicated_group_chat_ids or fallback_group_chat_ids
self.member_price = _decimal_env("POLYWEATHER_GROUP_MEMBER_PRICE_USDC", "10")
self.member_price = _decimal_env("POLYWEATHER_GROUP_MEMBER_PRICE_USDC", "5")
self.public_price = _decimal_env("POLYWEATHER_PUBLIC_PRICE_USDC", "10")
self.timeout_sec = max(
2,
+3 -1
View File
@@ -396,7 +396,7 @@ class PaymentContractCheckoutService:
)
self.telegram_payment_pricing_enabled = _env_bool(
"POLYWEATHER_PAYMENT_TELEGRAM_PRICING_ENABLED",
False,
True,
)
self.points_enabled = _env_bool("POLYWEATHER_PAYMENT_POINTS_ENABLED", True)
self.points_per_usdc = max(
@@ -1696,6 +1696,8 @@ class PaymentContractCheckoutService:
except Exception:
telegram_id = None
price_payload = pricing.resolve_price_for_telegram_id(telegram_id)
if not bool(price_payload.get("is_group_member")):
return out
amount_dec = _parse_decimal(
price_payload.get("amount_usdc"), out["amount_usdc_decimal"]
)
+45 -4
View File
@@ -78,6 +78,30 @@ def test_group_pricing_treats_member_status_as_group_member(monkeypatch):
assert result["telegram_status"] == "member"
def test_group_member_price_defaults_to_five_usdc(monkeypatch):
monkeypatch.setenv("TELEGRAM_BOT_TOKEN", "bot-token")
monkeypatch.setenv("POLYWEATHER_TELEGRAM_GROUP_ID", "-100123")
monkeypatch.delenv("POLYWEATHER_GROUP_MEMBER_PRICE_USDC", raising=False)
def fake_get(url, params, timeout):
class Response:
status_code = 200
@staticmethod
def json():
return {"ok": True, "result": {"status": "member"}}
return Response()
monkeypatch.setattr("src.auth.telegram_group_pricing.requests.get", fake_get)
pricing = TelegramGroupPricing()
result = pricing.resolve_price_for_telegram_id(12345)
assert result["is_group_member"] is True
assert result["amount_usdc"] == "5"
def test_group_pricing_treats_left_status_as_public_price(monkeypatch):
monkeypatch.setenv("TELEGRAM_BOT_TOKEN", "bot-token")
monkeypatch.setenv("POLYWEATHER_TELEGRAM_GROUP_ID", "-100123")
@@ -151,7 +175,24 @@ def test_payment_plan_uses_linked_telegram_group_price(monkeypatch, tmp_path):
assert priced["telegram_pricing"]["pricing_source"] == "telegram_group_member"
def test_payment_plan_uses_public_price_without_telegram_link(monkeypatch, tmp_path):
def test_payment_telegram_pricing_enabled_by_default_when_configured(monkeypatch, tmp_path):
monkeypatch.setenv("POLYWEATHER_PAYMENT_ENABLED", "true")
monkeypatch.setenv("SUPABASE_URL", "https://example.supabase.co")
monkeypatch.setenv("SUPABASE_SERVICE_ROLE_KEY", "service-role")
monkeypatch.setenv("POLYWEATHER_PAYMENT_RPC_URL", "https://rpc-1.example")
monkeypatch.setenv("POLYWEATHER_DB_PATH", str(tmp_path / "payments.db"))
monkeypatch.delenv("POLYWEATHER_PAYMENT_TELEGRAM_PRICING_ENABLED", raising=False)
monkeypatch.setenv(
"POLYWEATHER_PAYMENT_ACCEPTED_TOKENS_JSON",
'[{"code":"usdc_e","address":"0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174","decimals":6,"receiver_contract":"0xeD2f13Aa5fF033c58FB436E178451Cd07f693f32","is_default":true}]',
)
service = PaymentContractCheckoutService()
assert service.telegram_payment_pricing_enabled is True
def test_payment_plan_keeps_catalog_price_without_telegram_link(monkeypatch, tmp_path):
monkeypatch.setenv("POLYWEATHER_PAYMENT_ENABLED", "true")
monkeypatch.setenv("SUPABASE_URL", "https://example.supabase.co")
monkeypatch.setenv("SUPABASE_SERVICE_ROLE_KEY", "service-role")
@@ -170,6 +211,6 @@ def test_payment_plan_uses_public_price_without_telegram_link(monkeypatch, tmp_p
plan = service._select_plan("pro_monthly")
priced = service._apply_telegram_group_pricing("user-1", plan)
assert priced["amount_usdc"] == "10"
assert priced["amount_usdc_decimal"] == Decimal("10")
assert priced["telegram_pricing"]["pricing_source"] == "telegram_public"
assert priced["amount_usdc"] == "29.9"
assert priced["amount_usdc_decimal"] == Decimal("29.9")
assert "telegram_pricing" not in priced