Skip to main content

Overview

MechZie supports two payment methods: Online (Razorpay) and Cash / COD. Every monetary operation is backed by an immutable double-entry ledger — the payments table records Razorpay state, while ledger_entries is the financial source of truth.
  • Online — customer pays via Razorpay checkout (UPI, card, etc.). Platform collects the full amount and credits the mechanic’s wallet.
  • Cash / COD — customer pays the mechanic in cash on-site. The mechanic confirms receipt, and the platform fee is deducted from their future earnings.
All amounts are in paisa (₹1 = 100 paisa).

Architecture

Payment Method Selection

When creating a job, the customer chooses a payment method:
If omitted, defaults to online.

Online Payment Flow

Two paths confirm a payment — the optimistic verify (fast UI) and the webhook (canonical truth). Both are idempotent; if they race, the first one wins.

Step-by-Step (Online)

1

Create a Razorpay Order

After the mechanic marks a job as completed, the customer creates a payment order:
Response (200):
Amount is in paisa. Display as ₹${(amount / 100).toStringAsFixed(0)}.
2

Open Razorpay Checkout

Use the Razorpay Flutter SDK:
Use the Razorpay Key ID (starts with rzp_), not the Key Secret. The Key Secret is server-side only.
3

Verify Payment (Optimistic)

After Razorpay returns success, verify the signature:
On success, this also triggers the platform fee split into the ledger (mechanic payable 90% + platform fee 10%). The webhook is the canonical fallback if this path fails.
If verify and webhook disagree, webhook always wins. The ledger uses idempotency keys to prevent double-processing.

Cash / COD Payment Flow

For COD jobs, there’s no Razorpay involvement. The mechanic collects cash directly from the customer and confirms receipt via the API.

Step-by-Step (Cash / COD)

1

Customer creates COD job

The customer selects “Pay by Cash” when creating a job:
The job proceeds through the normal lifecycle (dispatching → accepted → en_route → arrived → in_progress → completed).
2

Customer pays cash to mechanic

After the mechanic marks the job as completed, the customer’s app shows the amount to pay in cash. This is a UI-only step — no API call needed from the customer.
3

Mechanic confirms cash received

Once the mechanic has collected cash, they confirm via the API:
Response (200):
This endpoint:
  • Transitions the job to paid
  • Records the COD collection in the ledger
  • Deducts the platform fee from the mechanic’s wallet balance

COD Ledger Entries

When a mechanic confirms a COD payment, CommissionService.processCodCommission() creates these ledger entries: The platform fee is deducted from the mechanic’s future earnings (wallet balance goes negative by the fee amount).
COD is not available for cancellation fees. Cancellation fees must always be paid online via Razorpay to prevent customers from avoiding payment.

Platform Fee Split

When a payment is captured (online) or confirmed (COD), CommissionService splits the gross amount. The mechanic always receives 90%. For online payments, the gateway processing fee (PG fee) is a platform expense — it never reduces the mechanic’s payout. The platform fee rate is dynamic — stored in platform_config as platform_fee_bps (default: 1000 = 10%) and changeable without redeployment.

Online vs COD Fee Handling

PG Fee Tracking

The payment-gateway processing cost is recorded separately as a platform expense — not deducted from the mechanic:
  • On payment.captured webhook — if Razorpay reports fee and tax fields, PgFeeService.recordActualPgFee() posts a pg_fee_actual ledger entry: DR PLATFORM_FEE_REVENUE → CR PLATFORM_PG_EXPENSE
  • From settlement reports — if the webhook fee was 0, a corrected entry can be posted with source: 'settlement_report' (distinct idempotency key).
  • pg_fee_bps_estimate (default: 200 = 2%) is available for margin estimation only — never used in actual ledger calculations.

Webhook Processing

The webhook pipeline guarantees exactly-once processing via payment_events.razorpay_event_id: Handled events: Endpoint: POST /api/v1/payments/webhook
  • No authentication (Razorpay sends directly)
  • Verified via HMAC-SHA256 in X-Razorpay-Signature header
  • Always returns 200 to prevent Razorpay retries
Frontend devs don’t need to implement webhook handling — it’s server-side only. But you should handle the case where verify succeeds but the UI should wait for the job status to update to paid.

Checking Payment Status

Response:
For COD jobs, there is no payments table record — the job transitions directly from completed to paid when the mechanic confirms cash. Check the job’s status field instead.

Payment Statuses

Cancellation Fees

Late cancellations incur fees. The fee amount is configurable in platform_config.

Customer cancellation

Cancelling from en_route, arrived, or in_progress → flat ₹50 fee (default). The customer cannot create new jobs until the fee is paid (402 PAYMENT_REQUIRED).

Mechanic cancellation

Consecutive cancellations above a threshold (default: 3 in 30 days) → ₹100 penalty deducted from their wallet automatically.

Check for Unpaid Fees

Use the same create-order → Razorpay checkout → verify flow with type: "cancellation_fee".
Customers with unpaid fees cannot create new jobs (402 PAYMENT_REQUIRED). Always check on app launch and prompt payment if needed. Cancellation fees cannot be paid via COD — online payment is required.

Pricing Model

All amounts in the API are in paisa. Always divide by 100 for display: ₹${(amount / 100).toFixed(0)}.

Client SDKs Required

The Razorpay Key ID will be provided via your app’s environment configuration. Do not hardcode it. COD payments require no additional SDKs.