fix(payments): make checkout attempts idempotent and reconcilable
This commit is contained in:
@@ -42,9 +42,14 @@ def test_discount_code_admin_crud_and_restore(client, user_factory, auth_headers
|
||||
assert DiscountCode.objects.filter(pk=code.pk).exists()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("gateway_result,expected", [({"errors": {"code": -9}}, 502), (None, 502)])
|
||||
def test_payment_create_gateway_failures_leave_no_active_payment(client, user_factory, auth_headers, event_factory,
|
||||
mocker, settings, gateway_result, expected):
|
||||
@pytest.mark.parametrize(
|
||||
"gateway_result,expected_status,expected_payment_status",
|
||||
[({"errors": {"code": -9}}, "failed", Payment.OrderStatusChoices.FAILED), (None, "pending", Payment.OrderStatusChoices.PENDING)],
|
||||
)
|
||||
def test_payment_create_gateway_failure_is_persisted_conservatively(
|
||||
client, user_factory, auth_headers, event_factory, mocker, settings,
|
||||
gateway_result, expected_status, expected_payment_status,
|
||||
):
|
||||
settings.ZARINPAL_MERCHANT_ID = "test"
|
||||
settings.ZARINPAL_REQUEST_URL = "https://gateway.example/request"
|
||||
user = user_factory()
|
||||
@@ -56,8 +61,9 @@ def test_payment_create_gateway_failures_leave_no_active_payment(client, user_fa
|
||||
gateway.return_value.json.return_value = gateway_result
|
||||
response = _send(client, "post", "/api/payments/create", auth_headers(user),
|
||||
{"event_id": event.pk, "description": "Ticket"})
|
||||
assert response.status_code == expected
|
||||
assert not Payment.objects.filter(user=user, event=event).exists()
|
||||
assert response.status_code == 200
|
||||
assert response.json()["status"] == expected_status
|
||||
assert Payment.objects.filter(user=user, event=event, status=expected_payment_status).count() == 1
|
||||
assert Registration.objects.filter(user=user, event=event).exists()
|
||||
|
||||
|
||||
@@ -74,14 +80,16 @@ def test_payment_callback_verification_failures_notify_once(client, user_factory
|
||||
base_amount=25000, authority="AUTH-FAIL", status=Payment.OrderStatusChoices.PENDING)
|
||||
gateway = mocker.patch("apps.payments.api.views.requests.post")
|
||||
notify = mocker.patch("apps.payments.api.views.notify_user")
|
||||
retry = mocker.patch("apps.payments.api.views.reconcile_payment.apply_async")
|
||||
if gateway_result is None:
|
||||
gateway.side_effect = RuntimeError("offline")
|
||||
else:
|
||||
gateway.return_value.json.return_value = gateway_result
|
||||
response = client.get("/api/payments/callback", {"Authority": "AUTH-FAIL", "Status": "OK"})
|
||||
assert response.status_code == 302 and "status=failed" in response.url
|
||||
assert response.status_code == 302 and "status=pending" in response.url
|
||||
payment.refresh_from_db()
|
||||
registration.refresh_from_db()
|
||||
assert payment.status == Payment.OrderStatusChoices.FAILED
|
||||
assert payment.status == Payment.OrderStatusChoices.PENDING
|
||||
assert registration.status == Registration.StatusChoices.PENDING
|
||||
notify.assert_called_once()
|
||||
notify.assert_not_called()
|
||||
retry.assert_called_once_with(args=(payment.id,), countdown=60)
|
||||
|
||||
Reference in New Issue
Block a user