Question packs removed at the root; all subscriptions 4 assistant questions a day; Bazaar is subscription-only
- subscription.0017/0018, payment.0013: pack plans deleted, QuestionCredit/HeartsPass models and Plan.kind / question_credits / hearts_days / Order.question_credits / hearts_days dropped. The only paid pack order was the company owner's test account (user 26). - Assistant: no credit spending; quota is the daily cap only (free 2, every plan 4, gift subscription 4). - Bazaar: verify_purchase is subscription-only (no inapp guess), start/purchase responses lose productType, verify refuses an order without a subscription. A paid order with no subscription now logs ERROR. - Removed POST /api/payment/orders (internal; no caller). Its permission tests stay. - Pack-based tests rewritten on subscriptions (refund, security, Bazaar token); pack-only test files deleted. - docs: profit table for 4/day on both payment routes plus free users; handbook, operations, CLAUDE.md current. 949 tests green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -115,8 +115,6 @@ def create_order(*, user_id: int, amount: int, currency: str = 'IRT',
|
||||
user_id=user_id,
|
||||
subscription_id=subscription_id,
|
||||
plan_id=plan_id,
|
||||
question_credits=plan.question_credits if plan else None,
|
||||
hearts_days=plan.hearts_days if plan else None,
|
||||
duration_days=plan.duration_days if plan else None,
|
||||
amount=amount,
|
||||
list_price=amount,
|
||||
@@ -227,10 +225,10 @@ def apply_order_payment_result(*, order: Order, gateway: str, gateway_ref_id: st
|
||||
from subscription.services import activate_subscription
|
||||
activate_subscription(order.subscription_id, plan_id=order.plan_id,
|
||||
duration_days=order.duration_days)
|
||||
elif order.plan_id:
|
||||
# بستهی سوال: اعتبار و روزهای قلبِ نامحدود، بیدست زدن به اشتراک.
|
||||
from subscription.services import grant_pack
|
||||
grant_pack(order)
|
||||
else:
|
||||
# هر سفارشِ فروشی از PurchaseView اشتراک دارد (فقط اشتراک فروخته میشود، subscription.0017)؛
|
||||
# سفارشِ پرداختشدهی بیاشتراک یعنی چیزی داده نشد و باید دیده شود.
|
||||
logger.error('سفارشِ %s پرداخت شد ولی اشتراکی ندارد؛ چیزی فعال نشد', order.id)
|
||||
# پورسانتِ معرف فقط روی پرداختِ واقعی (نه ثبتنام، نه مهمان).
|
||||
from affiliate.services import record_paid_order
|
||||
record_paid_order(order)
|
||||
@@ -322,7 +320,7 @@ def sync_bazaar_renewals(user_id: int) -> int:
|
||||
continue
|
||||
try:
|
||||
result = bazaar.verify_purchase(
|
||||
product_sku=last.bazaar_product_sku, purchase_token=token, is_subscription=True)
|
||||
product_sku=last.bazaar_product_sku, purchase_token=token)
|
||||
except bazaar.BazaarAPIError: # خودِ bazaar لاگ کرده؛ ساعتِ بعد
|
||||
continue
|
||||
except Exception: # noqa: BLE001 — صفحهی وضعیت نباید با خطای بازار ۵۰۰ بدهد؛ ولی دیده شود
|
||||
@@ -399,21 +397,18 @@ def refund_order(order: Order, *, staff: str = '') -> dict:
|
||||
|
||||
**پول را برنمیگرداند.** برگرداندنِ پول در پنلِ زرینپال (یا کافهبازار) انجام میشود؛ این تابع
|
||||
فقط حسابوکتابِ خودِ اپ را با آن یکی میکند. چه چیزی پس گرفته میشود (تصمیمِ ۲۰۲۶-۱۰-۰۲):
|
||||
- بسته: اعتبارِ همین سفارش، ولی فقط آنقدر که هنوز خرج نشده (موجودی منفی نمیشود)، و روزهای
|
||||
قلبِ نامحدودِ همین بسته از پایانِ کارتِ قلب.
|
||||
- اشتراک: روزهای همین پلن از پایانِ اشتراک؛ اگر چیزی نماند اشتراک «لغوشده» میشود.
|
||||
- روزهای همین پلن از پایانِ اشتراک؛ اگر چیزی نماند اشتراک «لغوشده» میشود.
|
||||
- پورسانتِ معرف برای همین سفارش به «در انتظار» برمیگردد.
|
||||
خروجی: خلاصه برای پیامِ ادمین.
|
||||
"""
|
||||
from subscription.models import HeartsPass, Plan, QuestionCredit, Subscription
|
||||
from subscription.services import credit_balance
|
||||
from subscription.models import Plan, Subscription
|
||||
|
||||
with db_transaction.atomic():
|
||||
order = Order.objects.select_for_update().get(pk=order.pk)
|
||||
if order.status != 'paid':
|
||||
raise OrderStateError(f'سفارشِ {order.id} «{order.get_status_display()}» است؛ فقط سفارشِ پرداختشده بازپرداخت میشود.')
|
||||
now = timezone.now()
|
||||
summary = {'credits_granted': 0, 'credits_taken': 0, 'hearts_days': 0, 'subscription_days': 0}
|
||||
summary = {'subscription_days': 0}
|
||||
plan = Plan.objects.filter(pk=order.plan_id).first() if order.plan_id else None
|
||||
|
||||
if order.subscription_id:
|
||||
@@ -426,22 +421,6 @@ def refund_order(order: Order, *, staff: str = '') -> dict:
|
||||
sub.version += 1 # PurchaseView با version تشخیص میدهد کسی ردیف را همزمان عوض کرده
|
||||
sub.save(update_fields=['expires_at', 'status', 'version'])
|
||||
summary['subscription_days'] = days
|
||||
else:
|
||||
granted = QuestionCredit.objects.filter(order_id=order.id, reason='purchase').first()
|
||||
if granted is not None:
|
||||
take = min(granted.delta, max(credit_balance(order.user_id), 0))
|
||||
summary['credits_granted'] = granted.delta
|
||||
summary['credits_taken'] = take
|
||||
if take:
|
||||
QuestionCredit.objects.create(user_id=order.user_id, delta=-take, reason='refund',
|
||||
order_id=order.id, note=f'بازپرداختِ سفارشِ {order.id}')
|
||||
hearts_days = order.hearts_days if order.hearts_days is not None else (plan.hearts_days if plan else 0)
|
||||
if hearts_days:
|
||||
hp = HeartsPass.objects.select_for_update().filter(user_id=order.user_id).first()
|
||||
if hp is not None:
|
||||
hp.expires_at = max(hp.expires_at - timezone.timedelta(days=hearts_days), now)
|
||||
hp.save(update_fields=['expires_at'])
|
||||
summary['hearts_days'] = hearts_days
|
||||
|
||||
from affiliate.models import Conversion
|
||||
Conversion.objects.filter(order_id=order.id, status='paid').update(
|
||||
|
||||
Reference in New Issue
Block a user