Document payments sanity check and fix demo seed
This commit is contained in:
@@ -126,10 +126,10 @@ class Command(BaseCommand):
|
|||||||
booking=booking,
|
booking=booking,
|
||||||
provider=PaymentProvider.MOYASAR,
|
provider=PaymentProvider.MOYASAR,
|
||||||
defaults={
|
defaults={
|
||||||
"status": PaymentStatus.CREATED,
|
"status": PaymentStatus.INITIATED,
|
||||||
"amount": booking.price_amount,
|
"amount": booking.price_amount,
|
||||||
"currency": booking.currency,
|
"currency": booking.currency,
|
||||||
"external_id": "",
|
"external_id": None,
|
||||||
"metadata": {"note": "Demo payment record"},
|
"metadata": {"note": "Demo payment record"},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
# 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`.
|
||||||
Reference in New Issue
Block a user