4.8 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
- Start a local mock Moyasar server (HTTP) that emulates
/v1/paymentsresponses. - Run migrations and seed demo data.
- Start Django with a local payment configuration pointing to the mock server.
- Obtain a JWT access token for the demo customer.
- Create a payment for an existing booking.
- Send a webhook payload to mark it as paid.
- 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
Password token login at /api/auth/token/ is deprecated for phone-first auth. For this runbook, mint a local JWT in Django shell.
The demo customer is:
customer@example.comCustomer123!
Generate an access token:
python3 manage.py shell -c "from django.contrib.auth import get_user_model; from rest_framework_simplejwt.tokens import RefreshToken; u=get_user_model().objects.get(email='customer@example.com'); print(str(RefreshToken.for_user(u).access_token))"
Expected: a JWT string printed to stdout. Use it as <ACCESS>.
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: initiatedexternal_id: pay_mock_123redirect_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: paidpaid_atsetmetadata.last_webhookpopulated
Considerations and Edge Cases
- Webhook secret:
MOYASAR_WEBHOOK_SECRETmust be set. Requests missing or mismatchingsecret_tokenreturn401. - Idempotency: reuse the same
idempotency_keyto verify the API returns the existing payment without creating another provider charge. - Unsupported sources:
creditcardis rejected by the backend. Usestcpay,token, orapplepay. - Callback URL: required for
tokenpayments; otherwise validation fails. - Demo data:
seed_democreates a payment withexternal_id=None(not empty string) to avoid violating unique constraints. - Debug mode:
DJANGO_DEBUG=1is required for localrunserverifALLOWED_HOSTSis 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_idfrom the mock server. - Webhook transitions the payment to
paidand populatespaid_at. metadata.last_webhookpersists the payload for audit.
Cleanup
- Stop the Django server (
Ctrl+C). - Stop the mock server (
Ctrl+C). - Optionally delete
/tmp/moyasar_mock.py.