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