fix Bazaar purchase validation URLs and add the new pishkhan-token method
Read against Bazaar's own docs (wp-json of developers.cafebazaar.ir): both validation URLs were wrong, so no real purchase could ever verify. In-app is now api/validate/<pkg>/inapp/<sku>/purchases/<token>/, and subscriptions add the missing /purchases/ and judge validUntil, since that response has no purchaseState. Only 404 means "not purchased"; 401/403/5xx now raise 503 so our own key or quota trouble cannot mark a paid order failed. The client token is URL-quoted so it cannot steer the request to another API path. BAZAAR_PISHKHAN_API_SECRET (the documented new method, a static header token with no OAuth) takes precedence when set; /api/health gains integrations.bazaar. Not yet exercised by a real purchase. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -122,3 +122,68 @@ class BazaarOAuthCallbackTests(TestCase):
|
||||
|
||||
self.assertTrue(bazaar.verify_purchase(product_sku='s', purchase_token='t').is_valid)
|
||||
self.assertEqual(mock_post.call_args.kwargs['data']['refresh_token'], 'db-ref')
|
||||
|
||||
|
||||
@override_settings(
|
||||
BAZAAR_PISHKHAN_API_SECRET='pishkhan-secret', BAZAAR_REFRESH_TOKEN='',
|
||||
BAZAAR_PACKAGE_NAME='ir.example.app',
|
||||
BAZAAR_API_BASE='https://pardakht.cafebazaar.ir/devapi/v2',
|
||||
)
|
||||
class BazaarValidateAgainstDocsTests(TestCase):
|
||||
"""آدرسها و معنای پاسخها طبقِ سندِ بازار (خواندهشده ۲۰۲۶-۰۹-۲۶)."""
|
||||
|
||||
def setUp(self):
|
||||
cache.clear()
|
||||
|
||||
def _resp(self, mock_get, status, body):
|
||||
mock_get.return_value.status_code = status
|
||||
mock_get.return_value.json.return_value = body
|
||||
|
||||
@patch('payment.bazaar.requests.post')
|
||||
@patch('payment.bazaar.requests.get')
|
||||
def test_inapp_url_and_new_method_header(self, mock_get, mock_post):
|
||||
self._resp(mock_get, 200, {'purchaseState': 0, 'consumptionState': 1})
|
||||
self.assertTrue(bazaar.verify_purchase(product_sku='monthly', purchase_token='tok').is_valid)
|
||||
self.assertEqual(
|
||||
mock_get.call_args.args[0],
|
||||
'https://pardakht.cafebazaar.ir/devapi/v2/api/validate/ir.example.app/inapp/monthly/purchases/tok/',
|
||||
)
|
||||
self.assertEqual(mock_get.call_args.kwargs['headers'],
|
||||
{'CAFEBAZAAR-PISHKHAN-API-SECRET': 'pishkhan-secret'})
|
||||
mock_post.assert_not_called() # روشِ جدید OAuth نمیخواهد
|
||||
|
||||
@patch('payment.bazaar.requests.get')
|
||||
def test_refunded_is_invalid(self, mock_get):
|
||||
self._resp(mock_get, 200, {'purchaseState': 1})
|
||||
self.assertFalse(bazaar.verify_purchase(product_sku='s', purchase_token='t').is_valid)
|
||||
|
||||
@patch('payment.bazaar.requests.get')
|
||||
def test_not_found_is_invalid(self, mock_get):
|
||||
self._resp(mock_get, 404, {'error': 'not_found'})
|
||||
self.assertFalse(bazaar.verify_purchase(product_sku='s', purchase_token='forged').is_valid)
|
||||
|
||||
@patch('payment.bazaar.requests.get')
|
||||
def test_our_credential_error_is_503_not_invalid(self, mock_get):
|
||||
# کلیدِ غلط یا سقفِ روزانه نباید خریدِ واقعیِ کاربر را failed کند.
|
||||
for status in (401, 403, 500):
|
||||
self._resp(mock_get, status, {'error': 'invalid_credentials'})
|
||||
with self.assertRaises(bazaar.BazaarAPIError):
|
||||
bazaar.verify_purchase(product_sku='s', purchase_token='t')
|
||||
|
||||
@patch('payment.bazaar.requests.get')
|
||||
def test_client_token_cannot_escape_the_path(self, mock_get):
|
||||
self._resp(mock_get, 404, {'error': 'not_found'})
|
||||
bazaar.verify_purchase(product_sku='s', purchase_token='../../applications/x')
|
||||
self.assertIn('/purchases/..%2F..%2Fapplications%2Fx/', mock_get.call_args.args[0])
|
||||
|
||||
@patch('payment.bazaar.requests.get')
|
||||
def test_subscription_url_and_expiry(self, mock_get):
|
||||
now_ms = timezone.now().timestamp() * 1000
|
||||
self._resp(mock_get, 200, {'validUntilTimestampMsec': now_ms + 86_400_000})
|
||||
self.assertTrue(bazaar.verify_purchase(product_sku='sub', purchase_token='t', is_subscription=True).is_valid)
|
||||
self.assertEqual(
|
||||
mock_get.call_args.args[0],
|
||||
'https://pardakht.cafebazaar.ir/devapi/v2/api/applications/ir.example.app/subscriptions/sub/purchases/t',
|
||||
)
|
||||
self._resp(mock_get, 200, {'validUntilTimestampMsec': now_ms - 1000})
|
||||
self.assertFalse(bazaar.verify_purchase(product_sku='sub', purchase_token='t', is_subscription=True).is_valid)
|
||||
|
||||
Reference in New Issue
Block a user