139 lines
8.1 KiB
Markdown
139 lines
8.1 KiB
Markdown
# Payments Integration (Moyasar, Webhooks, Idempotency)
|
||
|
||
This ExecPlan is a living document. The sections `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds.
|
||
|
||
The requirements for ExecPlans live in `PLANS.md` at the repository root. This document must be maintained in accordance with that file.
|
||
|
||
## Purpose / Big Picture
|
||
|
||
After this change, the backend can create Moyasar payments, track their state transitions, and reconcile them via webhooks in an idempotent and auditable way. A user can create a booking payment and see it progress from initiated to paid or failed. You can see it working by creating a payment, receiving a webhook callback that marks it as paid, and observing the payment record transition with a recorded provider reference and idempotency key.
|
||
|
||
## Progress
|
||
|
||
- [x] (2026-02-28 14:35Z) Created ExecPlan for payments integration (Moyasar + webhooks + idempotency).
|
||
- [x] (2026-02-28 15:05Z) Inspected payments models/endpoints and aligned naming with Moyasar scaffolding.
|
||
- [x] (2026-02-28 15:20Z) Defined payment state model extensions and idempotency tracking fields.
|
||
- [x] (2026-02-28 15:40Z) Implemented payment creation service and API endpoint with provider gateway.
|
||
- [x] (2026-02-28 15:55Z) Implemented webhook endpoint with secret verification and status mapping.
|
||
- [x] (2026-02-28 16:10Z) Added tests for creation, idempotency, and webhook reconciliation.
|
||
- [x] (2026-02-28 16:20Z) Updated `docs/risks.md` to close payment integration gaps once tested.
|
||
|
||
## Surprises & Discoveries
|
||
|
||
- Observation: The payments gateway needed an HTTP client dependency, so `requests` was added to backend requirements.
|
||
Evidence: `ModuleNotFoundError: No module named 'requests'` when running migrations after adding gateway calls.
|
||
|
||
## Decision Log
|
||
|
||
- Decision: Model payment state transitions as explicit status changes with audit-friendly timestamps.
|
||
Rationale: Payment flows must be auditable and deterministic under retries.
|
||
Date/Author: 2026-02-28, Codex
|
||
- Decision: Require idempotency keys on payment creation requests.
|
||
Rationale: Prevents duplicate charges when clients retry.
|
||
Date/Author: 2026-02-28, Codex
|
||
- Decision: Use a dedicated webhook endpoint with signature verification.
|
||
Rationale: Ensures authenticity of provider callbacks and protects state integrity.
|
||
Date/Author: 2026-02-28, Codex
|
||
- Decision: Store provider payloads and webhook payloads on the payment record for auditability.
|
||
Rationale: Helps trace payment transitions without introducing a separate event table yet.
|
||
Date/Author: 2026-02-28, Codex
|
||
|
||
## Outcomes & Retrospective
|
||
|
||
Payment creation, idempotency handling, and webhook reconciliation are implemented for Moyasar. Tests cover creation, idempotency, and webhook status transitions, reducing the largest Phase 1 reliability gap. Refund/capture operations remain future work if required.
|
||
|
||
## Context and Orientation
|
||
|
||
Payments live in `backend/apps/payments/` with current models and API endpoints. The system currently stores payment records but does not integrate with Moyasar or reconcile webhooks. Booking flows live in `backend/apps/bookings/` and should link to payments. The project standards require business logic in services and predictable error responses.
|
||
|
||
## Plan of Work
|
||
|
||
First, review existing payment models and endpoints to avoid breaking field names. Identify whether `Payment` includes a reference to `Booking`, a `provider_reference`, and a status field. If any are missing, add them along with timestamps for `initiated_at`, `paid_at`, and `failed_at`. Create a migration for the new fields. Ensure status choices include at least `initiated`, `pending`, `paid`, `failed`, and `refunded` if refunds are in scope.
|
||
|
||
Next, introduce idempotency tracking. Add a `idempotency_key` field to the payment model (unique, indexed) and validate that payment creation requests require it. If a request repeats with the same key, return the existing payment without creating a new provider charge.
|
||
|
||
Then, implement the Moyasar payment creation service in `backend/apps/payments/services.py`. The service should build the provider request using amount, currency, description, and return URLs, and persist the `provider_reference` (payment id returned by Moyasar). Store the full provider response in a JSON field for audit if available.
|
||
|
||
Add a dedicated API endpoint for payment creation in `backend/apps/payments/views.py` and `backend/apps/payments/urls.py`. It should:
|
||
|
||
- Require authentication.
|
||
- Validate booking ownership and amount.
|
||
- Require `idempotency_key`.
|
||
- Call the service to create the provider payment.
|
||
- Return the payment record plus any provider redirect URL if applicable.
|
||
|
||
Then, implement the webhook endpoint (`/api/payments/webhook/`) with signature verification using Moyasar’s secret. It should parse the event, locate the payment by `provider_reference`, apply an idempotent state transition, and record timestamps. Unknown events should be logged but return 200 to avoid retries if possible.
|
||
|
||
Finally, add tests in `backend/apps/payments/tests/`:
|
||
|
||
- Creating a payment succeeds and stores provider reference.
|
||
- Creating with the same idempotency key returns the original record.
|
||
- Webhook for `paid` updates status and timestamp.
|
||
- Webhook with invalid signature is rejected.
|
||
- Webhook is idempotent (replay does not change state or duplicate logs).
|
||
|
||
Update `docs/risks.md` to mark payment integration gaps as addressed.
|
||
|
||
## Concrete Steps
|
||
|
||
Run these commands from the repository root (`/home/m7md/kshkool/Salon`).
|
||
|
||
1. Inspect payments models and endpoints.
|
||
- Read `backend/apps/payments/models.py`, `backend/apps/payments/views.py`, and `backend/apps/payments/serializers.py`.
|
||
|
||
2. Add fields for provider reference, status timestamps, and idempotency.
|
||
- Update `backend/apps/payments/models.py` and create a migration.
|
||
- Run:
|
||
python3 backend/manage.py makemigrations payments
|
||
|
||
3. Implement services and endpoints.
|
||
- Add `backend/apps/payments/services.py`.
|
||
- Update serializers and views accordingly.
|
||
|
||
4. Add webhook endpoint and signature verification.
|
||
- Update `backend/apps/payments/urls.py` and `backend/apps/payments/views.py`.
|
||
|
||
5. Add tests.
|
||
- Create `backend/apps/payments/tests/test_payments_flow.py`.
|
||
|
||
6. Run tests.
|
||
- Backend:
|
||
source venv/bin/activate
|
||
cd backend
|
||
python3 -m pytest
|
||
|
||
## Validation and Acceptance
|
||
|
||
- Creating a payment with a new idempotency key returns HTTP 201 and a provider reference.
|
||
- Creating the same payment with the same idempotency key returns HTTP 200/201 with the original payment (no new provider request).
|
||
- A valid webhook updates the payment status to `paid` and sets `paid_at`.
|
||
- An invalid webhook signature returns HTTP 400/401 and does not mutate data.
|
||
- `python3 -m pytest` passes with the new payments tests.
|
||
|
||
## Idempotence and Recovery
|
||
|
||
Payment creation is safe to retry with idempotency keys. Webhook processing is idempotent and can be replayed safely. If a payment status change is applied incorrectly, it can be corrected manually via admin and will be documented in audit fields.
|
||
|
||
## Artifacts and Notes
|
||
|
||
Example idempotency pattern:
|
||
|
||
existing = Payment.objects.filter(idempotency_key=key).first()
|
||
if existing:
|
||
return existing
|
||
|
||
Example overlap-safe webhook logic:
|
||
|
||
if payment.status == PaymentStatus.PAID:
|
||
return
|
||
payment.mark_paid()
|
||
|
||
## Interfaces and Dependencies
|
||
|
||
- `backend/apps/payments/models.py` must include fields: `provider_reference`, `idempotency_key`, `status`, `initiated_at`, `paid_at`, `failed_at`, and (optionally) `provider_payload` (JSON).
|
||
- `backend/apps/payments/services.py` must define `create_payment_for_booking(booking, idempotency_key, request_data)` and `verify_webhook_signature(request)`.
|
||
- `backend/apps/payments/views.py` must expose `PaymentCreateAPIView` and `payment_webhook` with signature verification.
|
||
|
||
Plan Maintenance Note: Created on 2026-02-28 to implement Moyasar payments with idempotency and webhook reconciliation as the next Phase 1 reliability milestone.
|
||
Plan Maintenance Note (Update): Marked steps complete and recorded dependency and audit decisions after implementing payments and tests on 2026-02-28.
|