Bazaar: subscriptions (10343/10344), dynamic price (discount codes on Bazaar), auto-renewal sync
- Purchase with paymentMethod=bazaar now works for subscription plans with a bazaar_sku (not only packs);
response carries bazaarProductType. Migration subscription.0014 puts 10343/10344 on the 90/180-day plans.
- Verify asks Bazaar for the right product type (from the order) and stores validUntil (payment.0012).
- POST /api/payment/bazaar/orders/{id}/start: SKU, type, and an HS256 dynamic-price JWT when a discount
code lowered the price (BAZAAR_DYNAMIC_PRICE_KEY); discount on Bazaar orders allowed only with that key
and only before start.
- Auto-renewal: sync_bazaar_renewals re-asks Bazaar when the paid period is near its end (status page and
the paywall via has_subscription_access); each new period becomes a paid order.
- Default BAZAAR_PACKAGE_NAME = com.tadrisino.math.app; health shows bazaarDynamicPrice.
979 tests green. No real Bazaar purchase or renewal tested.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -278,22 +278,38 @@ callbackِ زرینپال، برگشت از پرداخت، `BAZAAR_REDIRECT_U
|
||||
«پرداختها و فاکتورها» میبیند. کد در `payment/services.py` (`refund_order`، `cancel_pending_order`)
|
||||
است و تستهایش در `payment/tests_refund.py`.
|
||||
|
||||
## فروشِ بسته در کافهبازار
|
||||
## فروش در کافهبازار (اشتراک و بسته)
|
||||
|
||||
از ۲۰۲۶-۱۰-۰۳. اپِ اندروید (پوستهی WebViewِ همین سایت، `android/` در ریپوی فرانت) بستهها را با
|
||||
پرداختِ درونبرنامهایِ بازار میفروشد؛ در مرورگرِ معمولی همهچیز مثلِ قبل از زرینپال است.
|
||||
اپِ اندروید (پوستهی WebViewِ همین سایت، `android/` در ریپوی فرانت) با پرداختِ درونبرنامهایِ بازار میفروشد. در
|
||||
برگهی پرداخت دو کاشی همیشه کنارِ هماند: «کارتِ بانکی» (زرینپال) و «کافهبازار». در مرورگر، یا وقتی پلن SKU ندارد،
|
||||
کاشیِ بازار غیرفعال است و دلیلش را میگوید.
|
||||
|
||||
1. در پیشخانِ بازار برای هر بسته یک **محصولِ مصرفشدنی** با همان قیمت بسازید.
|
||||
2. شناسهی آن (SKU) را در `/admin/subscription/plan/` در فیلدِ «Bazaar sku» همان بسته بنویسید. سرور SKU
|
||||
را فقط از همینجا میگیرد، نه از اپ؛ بستهی بیSKU داخلِ اپ «بهزودی در بازار» نشان داده میشود و
|
||||
درخواستش ۴۰۰ است. یک SKU روی دو بسته نمینشیند.
|
||||
3. کلیدِ RSAِ عمومیِ اپ (از پیشخان) در `android/app/build.gradle.kts` میرود، نه در `.env`.
|
||||
| چه | کجا | الان |
|
||||
|---|---|---|
|
||||
| محصول در پیشخانِ بازار | اشتراک = محصولِ «اشتراکی» (بازار خودکار تمدید میکند)، بسته = «مصرفشدنی» | سهماهه `10343`، ششماهه `10344`؛ `10342` هم هست و معلوم نیست چیست |
|
||||
| SKU روی پلن | `/admin/subscription/plan/` ← «Bazaar sku» (سرور SKU را فقط از اینجا میگیرد) | دو اشتراکِ بالا (migrationِ `subscription.0014`) |
|
||||
| قیمت | قیمتِ پیشخان باید با قیمتِ ادمین یکی باشد | **سنجیده نشده** |
|
||||
| کلیدِ «تخفیف پویا» | `BAZAAR_DYNAMIC_PRICE_KEY` در `.env` سرور | با آن کدِ تخفیف روی خریدِ بازار هم کار میکند |
|
||||
| کلیدِ RSAِ عمومی | `android/app/build.gradle.kts` | گذاشته شد |
|
||||
|
||||
مسیر: `POST /api/subscription/purchase` با `paymentMethod: "bazaar"` (فقط بسته) سفارش میسازد و
|
||||
`bazaarSku` برمیگرداند؛ اپ با Poolakey میخرد (`payload` = شمارهی سفارش)؛ `POST /api/payment/bazaar/verify`
|
||||
توکن را از خودِ بازار میسنجد و اعتبار میدهد. کدِ تخفیف روی سفارشِ بازار رد میشود (بازار قیمتِ
|
||||
ثابتِ محصول را میگیرد). تستها: `payment/tests_bazaar_pack.py`. **هنوز هیچ خریدِ واقعیای از بازار
|
||||
انجام نشده.**
|
||||
مسیر:
|
||||
|
||||
1. `POST /api/subscription/purchase` با `paymentMethod: "bazaar"` → سفارش، `bazaarSku`، `bazaarProductType`.
|
||||
2. (اختیاری) `POST /api/discount/redeem` روی همان سفارش. فقط با کلیدِ «تخفیف پویا»، وگرنه ۴۰۰.
|
||||
3. `POST /api/payment/bazaar/orders/{id}/start` → SKU، نوع، و اگر تخفیف خورده `dynamicPriceToken` (JWTِ HS256 با مبلغ
|
||||
به ریال). بیتخفیف توکن نمیرود چون بازارِ قدیمیتر از ۱۳.۳ با آن خطا میدهد. از این لحظه کدِ تخفیف نمینشیند.
|
||||
4. اپ با Poolakey میخرد (`subscribeProduct` یا `purchaseProduct`، `payload` = شمارهی سفارش).
|
||||
5. `POST /api/payment/bazaar/verify` توکن را از خودِ بازار میسنجد (نوعِ محصول از سفارش) و اشتراک را فعال یا
|
||||
اعتبار را اضافه میکند. فقط بسته بعد از آن consume میشود.
|
||||
|
||||
**تمدیدِ خودکار:** بازار به ما خبر نمیدهد. وقتی دورهی پرداختشدهی کاربر (`Order.bazaar_valid_until`) کمتر از یک روز
|
||||
مانده یا گذشته، با باز شدنِ وضعیتِ اشتراک (`GET /api/subscription/status`) همان توکن از بازار پرسیده میشود (هر توکن
|
||||
ساعتی یک بار)، و همینطور وقتی کاربرِ بیاشتراکِ فعال خانه یا فصلِ پولی را باز میکند (`has_subscription_access`). اگر `validUntil` جلو رفته، برای هر دوره یک سفارشِ «پرداختشده» ساخته میشود و روزها اضافه میشوند
|
||||
(`payment.services.sync_bazaar_renewals`). مبلغِ آن سفارش قیمتِ فعلیِ ادمین است، چون بازار مبلغِ تمدید را نمیگوید.
|
||||
اگر بازار در دسترس نباشد، کاربرِ تمدیدشده تا تلاشِ ساعتِ بعد پشتِ دیوار میماند. لغوِ تمدید از خودِ بازار است.
|
||||
|
||||
تستها: `payment/tests_bazaar_subscription.py`، `payment/tests_bazaar_pack.py`، `payment/tests_bazaar_oauth.py`.
|
||||
**هیچ خریدِ واقعیای از بازار انجام نشده**؛ نه پذیرفتنِ JWT از سوی بازار و نه تمدیدِ واقعی.
|
||||
|
||||
## ورودِ توسعهدهندهها (بدونِ سرویسِ پیامک)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user