Files
Salon/docs/runbooks/payments_sanity_check.md

4.5 KiB

Payments Sanity Check (Moyasar Mock + Demo Data)

This runbook documents the end-to-end sanity check for the Moyasar payments flow using demo data and a local mock provider. It is intended for developers and agents validating payment creation + webhook reconciliation before merging to main.

Purpose

Verify that the payment creation endpoint and webhook processing work end-to-end in a local environment without hitting Moyasar.

Preconditions

  • Backend dependencies installed in the Python venv.
  • Frontend is not required for this check.
  • backend/ database is migrated and uses SQLite for local dev.

High-level Flow

  1. Start a local mock Moyasar server (HTTP) that emulates /v1/payments responses.
  2. Run migrations and seed demo data.
  3. Start Django with a local payment configuration pointing to the mock server.
  4. Obtain a JWT access token for the demo customer.
  5. Create a payment for an existing booking.
  6. Send a webhook payload to mark it as paid.
  7. Verify the payment status updates.

Steps

1) Start the mock Moyasar server

The mock server responds to POST /v1/payments with a static id and transaction_url.

Create the mock server at /tmp/moyasar_mock.py and run it:

python3 /tmp/moyasar_mock.py

Expected: the process stays running, listening on http://127.0.0.1:8001.

2) Run migrations and seed demo data

source venv/bin/activate
cd backend
python3 manage.py migrate
python3 manage.py seed_demo

Expected: Demo data seeded.

3) Start Django with the mock provider

Run the backend with environment variables pointing to the mock server:

DJANGO_DEBUG=1 \
MOYASAR_SECRET_KEY=sk_test \
MOYASAR_PUBLISHABLE_KEY=pk_test \
MOYASAR_BASE_URL=http://127.0.0.1:8001 \
MOYASAR_WEBHOOK_SECRET=whsec \
python3 manage.py runserver 8000

Expected: server starts at http://127.0.0.1:8000/.

4) Obtain a JWT access token

The demo customer is:

  • customer@example.com
  • Customer123!

Fetch the access token:

curl -s -X POST http://127.0.0.1:8000/api/auth/token/ \
  -H "Content-Type: application/json" \
  -d '{"email":"customer@example.com","password":"Customer123!"}'

Expected: JSON containing access and refresh tokens.

5) Create a payment

Pick a booking (demo data creates bookings; you can list them):

curl -s -H "Authorization: Bearer <ACCESS>" http://127.0.0.1:8000/api/bookings/

Then create a payment (example uses booking id 3):

curl -s -X POST http://127.0.0.1:8000/api/payments/ \
  -H "Authorization: Bearer <ACCESS>" \
  -H "Content-Type: application/json" \
  -d '{
        "booking_id": 3,
        "provider": "moyasar",
        "idempotency_key": "<UUID>",
        "source": {"type": "stcpay", "mobile": "0500000000"}
      }'

Expected: response includes:

  • status: initiated
  • external_id: pay_mock_123
  • redirect_url: https://moyasar.example/tx/mock

6) Send webhook for paid state

curl -s -X POST http://127.0.0.1:8000/api/payments/webhook/ \
  -H "Content-Type: application/json" \
  -d '{"type":"payment_paid","secret_token":"whsec","data":{"id":"pay_mock_123"}}'

Expected: { "detail": "Webhook processed" }

7) Verify payment state

curl -s -H "Authorization: Bearer <ACCESS>" http://127.0.0.1:8000/api/payments/

Expected: payment record shows:

  • status: paid
  • paid_at set
  • metadata.last_webhook populated

Considerations and Edge Cases

  • Webhook secret: MOYASAR_WEBHOOK_SECRET must be set. Requests missing or mismatching secret_token return 401.
  • Idempotency: reuse the same idempotency_key to verify the API returns the existing payment without creating another provider charge.
  • Unsupported sources: creditcard is rejected by the backend. Use stcpay, token, or applepay.
  • Callback URL: required for token payments; otherwise validation fails.
  • Demo data: seed_demo creates a payment with external_id=None (not empty string) to avoid violating unique constraints.
  • Debug mode: DJANGO_DEBUG=1 is required for local runserver if ALLOWED_HOSTS is not set.
  • JWT warnings: short JWT secret keys can trigger warnings in logs; this is acceptable for local sanity checks but should be hardened in production.

What to Look For

  • Payment creation returns external_id from the mock server.
  • Webhook transitions the payment to paid and populates paid_at.
  • metadata.last_webhook persists the payload for audit.

Cleanup

  • Stop the Django server (Ctrl+C).
  • Stop the mock server (Ctrl+C).
  • Optionally delete /tmp/moyasar_mock.py.