Money review: Bazaar rejection no longer burns the purchase token, read-only order admin, pack grant from order snapshot

- Bazaar verify: a rejected token is not stored on the order or the failed transaction, so the
  same purchase can still pay the right order; a refunded order refuses a new token (409).
- OrderAdmin: every field read-only, no add/delete; Transaction admin read-only.
- grant_pack uses the order's snapshot when the plan was deleted, logs ERROR when nothing is granted.
- Pay link: authority is stored only if the order amount did not change during PaymentRequest.
- PlanAdmin accepts only IRT (the app always renders toman).
- reconcile_pending_order logs unexpected exceptions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 09:26:26 +03:30
parent eb4e97753b
commit fedd10ff22
7 changed files with 268 additions and 22 deletions

View File

@@ -1,10 +1,26 @@
from django import forms
from django.contrib import admin
from .models import Plan, Subscription
class PlanAdminForm(forms.ModelForm):
class Meta:
model = Plan
fields = '__all__'
def clean_currency(self):
# فرانت هر قیمتی را «تومان» نشان می‌دهد (src/lib/format.ts) و currency بی‌تغییر به زرین‌پال می‌رود؛
# پلنی با «IRR» در اپ ۴۵٬۰۰۰ تومان دیده می‌شد و درگاه ۴۵٬۰۰۰ ریال (یک‌دهم) می‌گرفت (بازتولید شد).
value = self.cleaned_data['currency']
if value != 'IRT':
raise forms.ValidationError('فقط IRT (تومان)؛ اپ قیمت را تومان نشان می‌دهد.')
return value
@admin.register(Plan)
class PlanAdmin(admin.ModelAdmin):
form = PlanAdminForm
list_display = ('id', 'title', 'price', 'currency', 'duration_days', 'ai_daily_quota', 'discount_percent', 'popular',
'bazaar_sku')
list_editable = ('ai_daily_quota',)