Files
Salon/backend/apps/payments/tests/test_payments_flow.py

169 lines
5.2 KiB
Python

import uuid
from datetime import timedelta
from unittest.mock import Mock, patch
import pytest
from django.urls import reverse
from django.utils import timezone
from rest_framework.test import APIClient
from apps.accounts.models import User, UserRole
from apps.bookings.models import Booking, BookingStatus
from apps.payments.models import Payment, PaymentProvider, PaymentStatus
from apps.salons.models import Salon, Service, StaffProfile
@pytest.fixture
def booking_entities():
owner = User.objects.create_user(email="owner@example.com", password="pass", role=UserRole.MANAGER)
customer = User.objects.create_user(email="customer@example.com", password="pass")
staff_user = User.objects.create_user(email="staff@example.com", password="pass", role=UserRole.STAFF)
salon = Salon.objects.create(
owner=owner,
name="Main Salon",
description="",
address="123 King Rd",
city="Riyadh",
phone_number="0512345678",
)
service = Service.objects.create(
salon=salon,
name="Haircut",
description="",
duration_minutes=60,
price_amount=120,
currency="SAR",
)
staff = StaffProfile.objects.create(user=staff_user, salon=salon)
start_time = timezone.now() + timedelta(days=1)
end_time = start_time + timedelta(minutes=60)
booking = Booking.objects.create(
salon=salon,
customer=customer,
service=service,
staff=staff,
start_time=start_time,
end_time=end_time,
status=BookingStatus.PENDING,
price_amount=service.price_amount,
currency=service.currency,
notes="",
)
return customer, booking
def _mock_gateway_response(payment_id="pay_test", status="initiated"):
response = Mock()
response.status_code = 201
response.json.return_value = {
"id": payment_id,
"status": status,
"source": {"transaction_url": "https://moyasar.example/tx"},
}
response.content = b"{}"
return response
@pytest.mark.django_db
@patch("apps.payments.services.gateway.requests.post")
def test_create_payment_idempotency_returns_existing(mock_post, booking_entities, monkeypatch):
customer, booking = booking_entities
client = APIClient()
client.force_authenticate(user=customer)
monkeypatch.setenv("MOYASAR_SECRET_KEY", "sk_test")
monkeypatch.setenv("MOYASAR_PUBLISHABLE_KEY", "pk_test")
mock_post.return_value = _mock_gateway_response()
request_id = str(uuid.uuid4())
payload = {
"booking_id": booking.id,
"provider": PaymentProvider.MOYASAR,
"idempotency_key": request_id,
"source": {"type": "stcpay", "mobile": "0500000000"},
}
response = client.post(reverse("payment-list"), payload, content_type="application/json")
assert response.status_code == 201
response_repeat = client.post(reverse("payment-list"), payload, content_type="application/json")
assert response_repeat.status_code == 200
assert Payment.objects.count() == 1
@pytest.mark.django_db
def test_rejects_creditcard_source(booking_entities):
customer, booking = booking_entities
client = APIClient()
client.force_authenticate(user=customer)
payload = {
"booking_id": booking.id,
"provider": PaymentProvider.MOYASAR,
"idempotency_key": str(uuid.uuid4()),
"source": {"type": "creditcard", "number": "4111111111111111"},
}
response = client.post(reverse("payment-list"), payload, content_type="application/json")
assert response.status_code == 400
assert "source" in response.json()
@pytest.mark.django_db
def test_webhook_paid_updates_status(booking_entities, monkeypatch):
_, booking = booking_entities
monkeypatch.setenv("MOYASAR_WEBHOOK_SECRET", "secret")
payment = Payment.objects.create(
booking=booking,
provider=PaymentProvider.MOYASAR,
status=PaymentStatus.INITIATED,
amount=booking.price_amount,
currency=booking.currency,
external_id="pay_webhook",
metadata={},
)
payload = {
"type": "payment_paid",
"secret_token": "secret",
"data": {"id": "pay_webhook"},
}
client = APIClient()
response = client.post(reverse("payment-webhook"), payload, content_type="application/json")
assert response.status_code == 200
payment.refresh_from_db()
assert payment.status == PaymentStatus.PAID
assert payment.paid_at is not None
@pytest.mark.django_db
def test_webhook_invalid_secret_is_rejected(booking_entities, monkeypatch):
_, booking = booking_entities
monkeypatch.setenv("MOYASAR_WEBHOOK_SECRET", "secret")
Payment.objects.create(
booking=booking,
provider=PaymentProvider.MOYASAR,
status=PaymentStatus.INITIATED,
amount=booking.price_amount,
currency=booking.currency,
external_id="pay_webhook",
metadata={},
)
payload = {
"type": "payment_paid",
"secret_token": "wrong",
"data": {"id": "pay_webhook"},
}
client = APIClient()
response = client.post(reverse("payment-webhook"), payload, content_type="application/json")
assert response.status_code == 401