# 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 " 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 " \ -H "Content-Type: application/json" \ -d '{ "booking_id": 3, "provider": "moyasar", "idempotency_key": "", "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 " 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`.