Order Payments & Refunds
The Orders module tracks payments as separate records linked to orders, supporting multiple payment providers, partial payments, COD collection, and refunds.
Payment Model
Each payment is a OrderPayment record:
| Field | Type | Description |
|---|---|---|
id |
UUID | Primary key |
order_id |
UUID | Parent order |
provider_module |
string | Payment provider module alias |
external_payment_id |
string | Gateway transaction ID |
method |
string | Payment method (razorpay, stripe, cod, bank_transfer) |
status |
string | pending, completed, failed, refunded |
amount |
decimal | Payment amount |
currency |
string | Currency code |
payment_link_url |
string | Generated payment link URL |
payment_link_expires_at |
datetime | Link expiry |
metadata |
jsonb | Gateway-specific metadata |
paid_at |
datetime | When payment was confirmed |
Recording Payments
POST /api/v1/orders/{order}/payments
{
"method": "razorpay",
"amount": 2999.00,
"external_payment_id": "pay_abc123",
"metadata": {
"gateway_order_id": "order_xyz",
"payment_method": "upi"
}
}
The service automatically reconciles the order's payment_status:
- If total paid >= order total →
paid - If total paid > 0 but < total →
partial - If no payments →
pending
Payment Links
Generate shareable payment links via integrated payment providers:
POST /api/v1/orders/{order}/payments/link
{
"provider_module": "payment-razorpay"
}
This calls the payment provider module through the Module API Bus:
$bus->call($providerModule, 'payment.createLink', [
'amount' => $order->balance_due,
'currency' => $order->currency,
'reference' => $order->order_number,
'customer_email' => $order->customer->email,
'customer_phone' => $order->customer->phone,
]);
COD Collection
Record cash collection on delivery:
POST /api/v1/orders/{order}/payments
{
"method": "cod",
"amount": 2999.00,
"collected_by": "delivery_agent_id"
}
Refunds
POST /api/v1/orders/{order}/refund
{
"payment_id": "uuid",
"amount": 999.00,
"reason": "Item defective"
}
Refunds create an OrderRefund record and publish an order.refund_processed event. If the original payment was through an external provider, the refund is forwarded via the bus.
Refund Model
| Field | Type | Description |
|---|---|---|
id |
UUID | Primary key |
order_id |
UUID | Parent order |
payment_id |
UUID | Original payment (nullable) |
provider_module |
string | Refund processor |
external_refund_id |
string | Gateway refund ID |
amount |
decimal | Refund amount |
reason |
string | Refund reason |
status |
string | pending, completed, failed |
processed_at |
datetime | When refund was processed |
Order Accessors
The Order model provides computed payment attributes:
$order->total_paid; // Sum of completed payments
$order->balance_due; // total - total_paid + refunded
Events
| Event | When |
|---|---|
order.payment_updated |
Payment recorded, status reconciled |
order.refund_processed |
Refund completed |