""" خریدِ بسته‌ی سوال از راهِ کافه‌بازار (بازبینیِ ۲۰۲۶-۱۰-۰۲). سفارشِ بازار فقط از `POST /api/payment/orders` (کلیدِ داخلی) ساخته می‌شود. آن endpoint `planId` نمی‌گرفت، پس سفارشِ بسته بی‌`plan_id` ساخته می‌شد و بعد از تأییدِ بازار «paid» می‌شد بی‌آن‌که `grant_pack` چیزی بدهد: پول گرفته می‌شد و اعتباری نمی‌آمد. پاسخِ تأیید هم وضعیتِ پیش از پرداخت («pending») را برمی‌گرداند. بازار با `bazaar.verify_purchase`ِ ساختگی آزموده می‌شود؛ خریدِ واقعیِ بازار هنوز هیچ‌جا آزموده نشده. """ from unittest.mock import patch from django.core.cache import cache from django.test import TestCase, override_settings from rest_framework.test import APIClient from accounts.authentication import issue_access_token from accounts.models import User from subscription.models import Plan from subscription.services import credit_balance from .bazaar import BazaarPurchaseResult from .models import Order @override_settings(INTERNAL_SERVICE_KEY='internal-test-key') class BazaarPackTests(TestCase): def setUp(self): cache.clear() self.user = User.objects.create(phone='09120007101', is_guest=False) self.client = APIClient() self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {issue_access_token(self.user.id)}') self.pack = Plan.objects.get(kind='pack', question_credits=20) def tearDown(self): cache.clear() def create_bazaar_order(self, **extra): response = APIClient().post('/api/payment/orders', { 'userId': str(self.user.id), 'amount': self.pack.price, 'provider': 'bazaar', 'bazaarProductSku': 'pack20', **extra, }, format='json', HTTP_X_INTERNAL_KEY='internal-test-key') self.assertEqual(response.status_code, 200, response.content) return response.json()['orderId'] def verify(self, order_id, valid=True): with patch('payment.bazaar.verify_purchase', return_value=BazaarPurchaseResult(is_valid=valid, purchase_state=0 if valid else None)): return self.client.post('/api/payment/bazaar/verify', { 'orderId': order_id, 'productSku': 'pack20', 'purchaseToken': 'token-1'}, format='json') def test_a_pack_bought_through_bazaar_gives_its_credits(self): order_id = self.create_bazaar_order(planId=str(self.pack.id)) order = Order.objects.get(pk=order_id) self.assertEqual((order.plan_id, order.plan_title, order.list_price), (self.pack.id, self.pack.title, self.pack.price)) self.verify(order_id) self.assertEqual(credit_balance(self.user.id), 20) def test_verify_reports_the_status_the_order_actually_has(self): order_id = self.create_bazaar_order(planId=str(self.pack.id)) response = self.verify(order_id) self.assertEqual(Order.objects.get(pk=order_id).status, 'paid') self.assertEqual(response.json()['status'], 'paid') def test_unknown_plan_is_rejected(self): response = APIClient().post('/api/payment/orders', { 'userId': str(self.user.id), 'amount': 1, 'provider': 'bazaar', 'planId': '999999', }, format='json', HTTP_X_INTERNAL_KEY='internal-test-key') self.assertEqual(response.status_code, 400) @override_settings(INTERNAL_SERVICE_KEY='internal-test-key') class BazaarSkuComesFromTheOrderTests(TestCase): """تأییدِ بازار SKU را فقط از سفارش می‌گیرد، نه از کلاینت (۲۰۲۶-۱۰-۰۳).""" def setUp(self): cache.clear() self.user = User.objects.create(phone='09120007102', is_guest=False) self.client = APIClient() self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {issue_access_token(self.user.id)}') def test_a_bazaar_order_needs_a_sku(self): response = APIClient().post('/api/payment/orders', { 'userId': str(self.user.id), 'amount': 1000, 'provider': 'bazaar', }, format='json', HTTP_X_INTERNAL_KEY='internal-test-key') self.assertEqual(response.status_code, 400) def test_verify_never_uses_the_clients_sku(self): order = Order.objects.create(user_id=self.user.id, amount=259000, provider='bazaar', idempotency_key='no-sku-1') with patch('payment.bazaar.verify_purchase') as verify: response = self.client.post('/api/payment/bazaar/verify', { 'orderId': str(order.id), 'productSku': 'cheapest', 'purchaseToken': 't'}, format='json') self.assertEqual(response.status_code, 400) verify.assert_not_called() order.refresh_from_db() self.assertEqual(order.status, 'pending') class BazaarPurchaseFromTheAppTests(TestCase): """ خریدِ بازار از مسیرِ خودِ کاربر: `POST /api/subscription/purchase` با `paymentMethod: "bazaar"` (۲۰۲۶-۱۰-۰۳). پیش از آن تنها راهِ ساختِ سفارشِ بازار کلیدِ داخلی بود و `paymentMethod` هر مقداری را می‌پذیرفت و نادیده می‌گرفت. """ def setUp(self): cache.clear() self.user = User.objects.create(phone='09120007103', is_guest=False) self.client = APIClient() self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {issue_access_token(self.user.id)}') self.pack = Plan.objects.get(kind='pack', question_credits=20) self.pack.bazaar_sku = 'tadrisino_pack_20' self.pack.save() def tearDown(self): cache.clear() def buy(self, plan, method='bazaar', **headers): return self.client.post('/api/subscription/purchase', {'planId': str(plan.id), 'paymentMethod': method}, format='json', **headers) def test_bazaar_order_carries_the_plans_sku_and_no_payment_url(self): response = self.buy(self.pack) self.assertEqual(response.status_code, 200, response.content) body = response.json() self.assertIsNone(body['paymentUrl']) self.assertEqual(body['bazaarSku'], 'tadrisino_pack_20') order = Order.objects.get(pk=body['orderId']) self.assertEqual((order.provider, order.bazaar_product_sku, order.plan_id), ('bazaar', 'tadrisino_pack_20', self.pack.id)) def test_whole_flow_gives_the_pack_credits(self): order_id = self.buy(self.pack).json()['orderId'] with patch('payment.bazaar.verify_purchase', return_value=BazaarPurchaseResult(is_valid=True, purchase_state=0)) as verify: response = self.client.post('/api/payment/bazaar/verify', {'orderId': order_id, 'purchaseToken': 'tok-app-1'}, format='json') self.assertEqual(response.json()['status'], 'paid') self.assertEqual(verify.call_args.kwargs['product_sku'], 'tadrisino_pack_20') self.assertEqual(credit_balance(self.user.id), 20) def test_pack_without_a_sku_is_refused_not_sent_to_zarinpal(self): other = Plan.objects.get(kind='pack', question_credits=60) response = self.buy(other) self.assertEqual(response.status_code, 400) self.assertFalse(Order.objects.filter(user_id=self.user.id).exists()) def test_unknown_payment_method_is_a_400(self): self.assertEqual(self.buy(self.pack, method='paypal').status_code, 400) def test_zarinpal_is_still_the_default(self): response = self.client.post('/api/subscription/purchase', {'planId': str(self.pack.id)}, format='json') self.assertEqual(response.status_code, 200) self.assertIn('/pay', response.json()['paymentUrl']) self.assertEqual(Order.objects.get(pk=response.json()['orderId']).provider, 'gateway') def test_same_idempotency_key_cannot_switch_payment_method(self): self.assertEqual(self.buy(self.pack, HTTP_IDEMPOTENCY_KEY='k-1').status_code, 200) self.assertEqual(self.buy(self.pack, method='zarinpal', HTTP_IDEMPOTENCY_KEY='k-1').status_code, 409) def test_discount_code_is_refused_on_a_bazaar_order(self): from discount.models import DiscountCode DiscountCode.objects.create(code='HALF', type='percent', value=50, max_uses=10) order_id = self.buy(self.pack).json()['orderId'] response = self.client.post('/api/discount/redeem', {'code': 'HALF', 'orderId': order_id}, format='json') self.assertEqual(response.status_code, 400) self.assertEqual(Order.objects.get(pk=order_id).amount, self.pack.price) def test_plans_list_exposes_the_sku(self): plans = self.client.get('/api/subscription/plans').json()['plans'] self.assertEqual(next(p for p in plans if p['id'] == str(self.pack.id))['bazaarSku'], 'tadrisino_pack_20')