Payments, Credits & Reconciliation — Technical Reference¶
Part of the Aashray Technical Reference. Pairs with Business Logic — Payments, Credits & Reconciliation, which describes these rules in plain terms with no code detail. Related technical detail for the booking types that get charged here lives in Booking Lifecycle, Stays: Rooms & Flats, Food, Travel, Adhyayan & Utsav, Status & Cron, and the Admin Panel Map.
This file is 100% derived from reading the backend, admin, and app source on main — no operational/staff-knowledge content lives here (that's in the business-logic companion). It covers exactly how charges, the Razorpay order → checkout → webhook → settlement flow, store credit, the transactions ledger, the 24-hour pending-payment window, and the admin settlement / reconciliation / voucher / credit reporting surface are implemented: every route, the data model, admin mechanics, edge cases, and known defects.
The hub of everything here is CardDb (cardno). Every Transactions row belongsTo a CardDb and is linked by bookingid to a RoomBooking / FlatBooking (declared associations) or — resolved dynamically by category — to a ShibirBookingDb, TravelDb, UtsavBooking, or FoodDb. Store credit is a JSON blob on card_db.credits. There is no separate payment/refund table — the transactions row is the ledger entry, and its status column carries the entire lifecycle.
Verification status: audited & anchored per
docs/DOCS-METHODOLOGY.mdon 2026-07-07 againstaashray-backendmain. Every rule carries a[src: …]anchor. All 10 discrepancies were confirmed against code this pass; three stale code-location references (flat-price function, adhyayan formula, utsav line) were corrected.
A. Charges — where real prices come from¶
What it is. The single source of truth for the amount a member owes. The member app assembles a booking and calls a validate endpoint; the response's per-line charge values and totalCharge are exactly what the UI renders. The app has a legacy constants/prices.js file but it is dead code — the live numbers are these backend constants and per-record amounts.
Backend rules & logic. Price sources (config/constants.js + per-record columns):
| Booking type | Charge formula | Source |
|---|---|---|
| Room | roomCharge(roomtype) * nights → NAC ₹700/night, AC ₹1100/night |
NAC_ROOM_PRICE=700, AC_ROOM_PRICE=1100 [src: config/constants.js:25-26]; roomCharge() [src: helpers/roomBooking.helper.js:478-480] |
| Flat | roomCharge('nac') * nights (= ₹700/night); ₹0 if the booker is the flat owner |
checkFlatAvailabilityForMumukshus flat branch [src: helpers/roomBooking.helper.js:755] |
| Adhyayan (shibir) | min(available_seats, people) * shibir.amount (only when shibir status == open; waitlisted seats charge ₹0) |
[src: helpers/adhyayanBooking.helper.js:403-405] |
| Utsav | utsav_package.amount (only when utsav status == open; waitlisted = ₹0) |
[src: helpers/utsavBooking.helper.js:357-359] |
| Food (breakfast/lunch/dinner) | BREAKFAST_PRICE=60, LUNCH_PRICE=120, DINNER_PRICE=120 per plate |
[src: config/constants.js:22-24; helpers/foodBooking.helper.js:52-60,271-284] |
| Travel | always ₹0 — status awaiting confirmation, charged/adjusted later by travel admin |
[src: helpers/travelBooking.helper.js:277] |
The validate controllers (validate() in both mumukshuBooking.controller.js and guestBooking.controller.js) loop over primary_booking + addons, dispatch by booking_type, sum each detail's charge into response.totalCharge, and attach an availableCredits figure per line (see §B) for room, flat, utsav, and food — but not for adhyayan or travel. [src: helpers/roomBooking.helper.js:689,756-757; helpers/utsavBooking.helper.js:360; helpers/foodBooking.helper.js:290; helpers/adhyayanBooking.helper.js:411-416 (adhyayan returns only charge)]
Validation response shape: { roomDetails[], flatDetails[], adhyayanDetails[], utsavDetails[], foodDetails{}, travelDetails{}, totalCharge }. Each accommodation/utsav/food line carries charge and (where applicable) availableCredits; room lines add nights, status, roomType, per-person id/name.
Every route (charges).
| Method | Path | Auth realm | Purpose | Key params/body |
|---|---|---|---|---|
| POST | /api/v1/mumukshu/validate |
Member (validateCard) |
Price + credit preview for a mumukshu/self multi-booking | { primary_booking, addons[] } each { booking_type, details } |
| POST | /api/v1/guest/validate |
Member (validateCard) |
Price + credit preview for a guest multi-booking | { primary_booking, addons[] } |
| POST | /api/v1/unified/validate |
Member (validateCard) |
Deprecated — deprecatedEndpoint throws HTTP 410 |
— [src: routes/client/unifiedBooking.routes.js:7-9,18] |
Discrepancies. (1) Flat is priced at the NAC room rate (roomCharge('nac')), never the AC rate — there is no separate flat tariff constant. [src: helpers/roomBooking.helper.js:755] (2) Adhyayan validate returns no availableCredits even though a member may hold adhyayan credit; the app therefore can never pre-apply credit to an adhyayan line. [src: helpers/adhyayanBooking.helper.js:411-416] (3) Prices are hard-coded constants, not DB-configurable, except adhyayan/utsav which read a per-record amount. [src: config/constants.js:22-26]
B. Credits (store credit)¶
What it is. Non-cash, non-refundable store credit. 1 credit = ₹1. Held on card_db.credits as a JSON object keyed by credit type: { room, food, travel, utsav, adhyayan }. Flat credit is folded into the room bucket (getCreditType() maps flat → room).
Member-app behavior. Credits are shown on the Profile "Available Credits" card (Stay / Travel / Food / Utsav) and inline on the Review screen (original struck through, green "−₹X credit", net). They are applied automatically at checkout; the member never chooses to spend them.
Backend rules & logic (helpers/transactions.helper.js):
- Applying credit (
useCredit) — called fromcreatePendingTransactionat booking time. For the transaction's credit type,creditsUsed = min(amount, availableCredits); the transaction is updated toamount = charge − creditsUsed,discount = creditsUsed,description = "credits used: N", and the card's credit bucket is decremented. If credit fully covers the charge (amount == 0) the transaction is set tocompletedimmediately and the booking is confirmed (pending checkinfor room/flat,confirmedotherwise) — no Razorpay needed. [src: helpers/transactions.helper.js:326-358] - Previewing credit (
usableCredits) — the read-only version used inside validate. It mutates a clonedcard.credits(atempUser) so multi-person previews deduct sequentially without touching the real balance. [src: helpers/transactions.helper.js:386; helpers/roomBooking.helper.js:650,748] - Earning credit (
addCredit) — only viacancelTransaction(see §D). Credit accrues to thegetCreditType(bookingType)bucket; a bucket that hits 0 is deleted from the JSON. [src: helpers/transactions.helper.js:289-293,295,407-409]
Which booking types actually earn / spend credit:
| Type | Credit applied at checkout? | Credit earned on cancel? |
|---|---|---|
| Room | Yes (room) |
Yes (member or admin cancel of a paid booking) |
| Flat | Yes (room bucket) |
Yes (room bucket) |
| Food | Yes (food) |
Yes |
| Utsav | Yes (utsav) |
No on normal cancel (see §D) — only via the admin re-credit path |
| Adhyayan | No (validate omits it) | No |
| Travel | No (charge is ₹0) | No |
Refund model. There is no cash refund anywhere in the system. Cancelling a paid booking converts the paid amount to store credit (status → credited). Cancelling an unpaid booking that had credit pre-applied returns only the previously-used credit (the discount). Cancel dialogs mention nothing about refunds.
Discrepancies. (1) Utsav asymmetry — utsav credit can be spent at checkout and can be previewed, but a normal utsav cancellation grants no credit (utsav is in the "not credited, keep as completed" branch), so a utsav bucket can realistically only appear via the admin double-cancel path. [src: helpers/transactions.helper.js:166-177,196-207] (2) adjustAmount(card, booking, transaction, amount, updatedBy, t) (admin re-price helper) calls addCredit(user, …) but user is not a parameter of adjustAmount — a latent ReferenceError (dead/broken path). [src: helpers/transactions.helper.js:257-287 (addCredit(user,…) at :270)] (3) The admin credits report (fetchCredits) parses only room/food/travel/utsav keys — an adhyayan bucket, if it ever existed, would be invisible. [src: controllers/admin/accountsManagement.controller.js:775-778]
C. Razorpay flow (order → pay/payv2 → checkout → webhook → settlement)¶
What it is. All online money moves through Razorpay. The backend creates an order, the app opens the native Razorpay sheet, and Razorpay calls back a webhook that captures the payment and confirms bookings. Settlement/reconciliation is imported later from Razorpay Excel exports (§F).
Order creation (generateOrderId, transactions.helper.js:414). Builds { amount: rupees*100 (paise), currency: 'INR', receipt: uuidv4(), notes: { app:'aashray', env } }. Only calls the real Razorpay API when ['prod','qa'].includes(NODE_ENV) and amount > 0; otherwise it fabricates a fake order object with a uuid id (so dev/staging never hit Razorpay). [src: helpers/transactions.helper.js:414-439 (gate at :431)] Order creation happens in three places, each followed by updateRazorpayTransactions() which stamps the razorpay_order_id onto the relevant transaction rows:
1. Inline "Pay Now" at booking time — mumukshuBooking / guestBooking controllers, only if req.user.country == 'India' and amount > 0.
2. POST /razorpay/pay (v1 pending payments).
3. POST /razorpay/payv2 (v2 pending payments — the one the app uses).
Webhook capture (verifyPayment, payment.controller.js:32). [src: controllers/client/payment.controller.js:32-269; routes/client/payment.routes.js:12] Every webhook is first archived into razorpay_webhook, then:
- Reads payload.payment.entity.{order_id, id, status}.
- Ignores any status not in {captured, failed, authorized} (returns 200 "Invalid status" — Razorpay must get a 200 or it retries).
- Opens a DB transaction with lock: { update: true } and loads transactions for that razorpay_order_id whose status ∈ {pending, cash pending, failed, authorized}.
- Per transaction, by webhook status:
- authorized → transaction status = authorized (money held, not captured; booking untouched).
- captured → transaction status = completed; resolve the booking via getBookingType/getBooking and set booking status = pending checkin (room/flat) or confirmed (adhyayan/utsav/travel). Food bookings' status is deliberately not changed. For late-checkout fees, a transaction exists with no booking — handled gracefully (if (booking)).
- failed → transaction status = failed.
- Commits, then fires per-type WhatsApp status messages (room/flat/travel/utsav) and a unified confirmation email. If no matching pending transactions are found, it rolls back and returns "No pending bookings found".
Every route (Razorpay).
| Method | Path | Auth realm | Purpose | Key params/body |
|---|---|---|---|---|
| POST | /api/v1/razorpay/verifyPayment |
None (public webhook) | Razorpay server-to-server capture/authorize/fail callback | { payload.payment.entity: { order_id, id, status } } |
| POST | /api/v1/razorpay/pay |
Member (validateCard) |
v1: create order for selected bookingids (blocks any food) |
{ bookingids: [] } |
| POST | /api/v1/razorpay/payv2 |
Member (validateCard) |
v2: create order for [{bookingid, category}] (food allowed only if category matches) |
{ data: [{ bookingid, category }] } |
Member-app behavior. Sheet branded "Vitraag Vigyaan Aashray", INR, theme #F1AC09, prefilled email/phone/name. On success → /paymentConfirmation; on failure/cancel → /paymentFailed. A ₹0 (fully credit-covered) or Pay-Later booking skips Razorpay entirely and goes to /bookingConfirmation.
Discrepancies. (1) verifyPayment performs no Razorpay signature verification and has no auth middleware — anyone who can POST the expected JSON shape can flip transactions to completed and confirm bookings. Significant security gap. [src: controllers/client/payment.controller.js:32-269; routes/client/payment.routes.js:12] (2) STATUS_PAYMENT_CAPTURED is a defined transaction enum value but the webhook maps captured → completed, so captured is only ever a webhook status string, never a stored transaction status. [src: payment.controller.js:110; config/constants.js:54] (3) /pay (v1) hard-rejects any order containing a food line, while /payv2 permits food when the category matches — inconsistent handling between the two surfaces. [src: payment.controller.js:294-305 (v1); payment.controller.js:361-375 (v2)]
D. The transactions lifecycle — every status & transition¶
Data model (transactions.model.js, table transactions). [src: models/transactions.model.js:44-78]
| Field | Type | Notes |
|---|---|---|
id |
INT PK auto | |
cardno |
STRING FK → card_db.cardno |
belongsTo CardDb |
bookingid |
STRING | links to Room/Flat (declared assoc) or Shibir/Travel/Utsav/Food (by category) |
category |
STRING | one of room, flat, adhyayan, utsav, travel, food, breakfast, lunch, dinner — mapped to a booking type via bookingTypeMap |
amount |
DECIMAL | net payable after credit deduction |
discount |
DECIMAL (default 0) | credits used |
razorpay_order_id |
STRING nullable | stamped when an order is created |
description |
STRING nullable | e.g. "credits used: N", "credits added: N" |
status |
ENUM | full lifecycle (below) |
updatedBy |
STRING (default 'USER') | RAZORPAY_CALLBACK for webhook, admin username, or user |
The status set (config/constants.js) [src: config/constants.js:32,36,45,46,51,53,54,55,56,57,58]:
pending(STATUS_PAYMENT_PENDING) — online payment awaited (India users). Subject to the 24h auto-cancel.cash pending(STATUS_CASH_PENDING) — cash owed; created for admin bookings or non-India users. Never expires.authorized— Razorpay authorized but not yet captured.completed(STATUS_PAYMENT_COMPLETED) — paid online (captured) or fully covered by credit.cash completed(STATUS_CASH_COMPLETED) — cash collected/marked by admin.failed(STATUS_PAYMENT_FAILED) — payment attempt failed (retryable via pending payments).cancelled(STATUS_CANCELLED) — user-cancelled, no credit issued.admin cancelled(STATUS_ADMIN_CANCELLED) — admin- or cron-cancelled, no credit.credited(STATUS_CREDITED) — cancellation converted the amount to store credit.captured(STATUS_PAYMENT_CAPTURED) — enum value present but unused as a stored status (see §C discrepancy).
Transitions (cancelTransaction, transactions.helper.js:112). [src: helpers/transactions.helper.js:112-255] On cancel, totalAmount = amount + discount (gross); credit awarded = totalAmount if the txn was completed/cash completed, else discount (previously-applied credit). Then:
- User cancel of travel or utsav → early return, no credit, transaction left completed. [src: helpers/transactions.helper.js:166-177]
- Adhyayan / utsav / migrated bookings (ifMigrated: bookingid < 36 chars or description contains "came from datachef migration") → no credit; if it was completed/cash completed it stays completed (kept for reports), else moves to cancelled/admin cancelled. [src: helpers/transactions.helper.js:196-207; helpers/booking.helper.js:87-97]
- Everything else (room/flat/food) with credit > 0 → addCredit(...), status → credited, description = "credits added: N". [src: helpers/transactions.helper.js:208-212]
- Re-cancelling an already-cancelled txn is only allowed for admin, which force-credits the full amount + discount → credited. Re-cancelling admin cancelled/credited throws 400.
- Finally the row is normalized: discount = 0, amount = totalAmount, new status.
Bus-assignment cleanup: cancelling any transaction also removes the booking from any TravelBusPassengers group and nulls a coordinator_bookingid if it pointed at it.
Discrepancies. The model file itself carries a // TODO: remove cash completed and payment completed status comment — the team considers part of this enum legacy. [src: models/transactions.model.js:61]
E. Pending payments & the 24-hour auto-cancel cron¶
What it is. Outstanding dues surface in the app's "Pending Payments" list (fed by GET /profile/transactions filtered to pending,cash pending,failed). Non-cash pending items expire 24 hours after creation; the cron enforces this by cancelling the booking + transaction.
The cron (cron.js). MAX_APP_PAYMENT_DURATION = 24*60 minutes. The main job runs every 30 minutes (*/30 * * * *): [src: cron.js:38,43]
1. getPendingTransactions(now − 24h) → transactions with status ∈ {pending, failed}, card.country = 'INDIA', and createdAt <= cutoff. Note: cash pending is excluded by design, and non-India users are excluded (their transactions are cash pending anyway). [src: helpers/transactions.helper.js:441-463 (country filter :451)]
2. For each non-food transaction, resolve and collect the booking.
3. cancelBookings sets each booking status = admin cancelled; for adhyayan it promotes the next waitlisted seat (and creates attendance) + resets attendance; for utsav it cancels utsav food and opens the seat; for travel it promotes the next waiting booking.
4. cancelTransactions(systemUser, txns, t, admin=true) runs the §D admin-cancel logic (so a paid pending item that somehow expired would credit).
5. cancelMeals cancels the food bookings.
6. Commits, then sends cancellation emails, "open booking" emails, and per-type WhatsApp.
Other crons in the same file (not payment-related): meal-count WhatsApp at 21:00/22:00/23:00 IST, and a WiFi low-code alert every 30 min. A getUnpaidPastBookings path (cancel bookings whose check-in is already past) exists but is commented out.
Member-app behavior. Live color-coded countdown per item (green > 3h, orange ≤ 3h, red "Expired"); expired items are non-selectable ("Payment expired"); cash pending items show no timer. Pay-Later bookings display a 24h warning modal before confirming.
Discrepancies. (1) Country casing mismatch — createPendingTransaction marks a txn cash pending when card.country != 'India' (mixed case), but the cron's getPendingTransactions filters country = 'INDIA' (upper). If any card stores the country as 'India' (not 'INDIA'), that member's pending transactions would be skipped by the cron and never auto-cancelled. Worth auditing the stored value. [src: helpers/transactions.helper.js:38 vs :451] (2) Self food transactions are created pending yet online payment for food is blocked (§C) — such items can only be cleared by cash or the 24h auto-cancel.
F. Admin — settlements, reconciliation, vouchers & credit reports¶
All routes below sit under /api/v1/admin/accounts, gated by auth + authorizeRoles(superAdmin, accountsAdmin, accountsAdminPra). [src: app.js:188; routes/admin/accountsManagement.routes.js:26-39; config/constants.js:82,96,97] The admin UI pages live in admin/account/* (settlementReport, settlementRecon, settlementBreakdown, voucherCreation, creditsReport, creditTransaction, debitTransaction, pendingTransaction).
Data model.
- razorpay_settlement (RazorpaySettlement): id (settlement id, PK), amount, status, fees, tax, utr, cerated_at [sic — misspelled column]. Populated by uploading Razorpay's settlement report Excel; dedup by id. [src: models/razorpay_settlement.model.js:32]
- razorpay_settlement_recon (RazorpaySettlementRecon): composite PK (payment_id, order_id), plus amount, fees, tax, credit_amount, payment_notes, settlement_id, settled_at, settlement_utr. Populated by uploading Razorpay's per-payment recon Excel (upsert keyed on order+payment). Ties each payment to a settlement. [src: models/razorpay_settlement_recon.model.js]
How the reports work.
- Settlement report upload parses the sheet, formats/validates dates (DD/MM/YYYY HH:mm:ss), skips rows missing a date, and bulkCreates only new settlement ids.
- Recon upload upserts one recon row per (order_id, entity_id), mapping fee (exclusive tax), tax, credit, order_notes, settlement_id, settled_at, settlement_utr (and a settled_by field written but not defined on the model).
- fetchAllSettlements returns settlements with fees/tax overwritten by the sum from recon grouped by settlement_id.
- fetchTransactionsBySettlementId joins app transactions to recon (pre-aggregated per order to avoid fan-out) and UNIONs "Satshrut Transactions" — captured razorpay_webhook payments whose order isn't in the local transactions table (i.e. cross-app payments to the same Razorpay account), reading the amount from the webhook JSON.
- fetchTransactionsByPaymentId drills a single order into per-line detail (booking/room/flat/shibir/utsav/travel/food joins, bookedBy + bookedFor cards), again UNIONing the Satshrut webhook-only fallback (matching a card by the last 10 digits of the payment contact).
- fetchCompletedTransactions (voucher creation) returns completed/cash completed/credited transactions with optional startDate/endDate, category (food expands to all meal categories), adhyayanId, utsavId filters — used to generate receipts/vouchers.
- Credits report — fetchCredits lists every card with a non-empty credits JSON, parsed into roomCredits/foodCredits/travelCredits/utsavCredits; fetchCreditTransactions?cardno=&category= rebuilds a running-balance ledger for one card from credited (+) rows and completed+"credits used:" (−) rows.
- Credit / Debit / Pending reports — fetchAllCreditTransactions (status credited, dated by updatedAt); fetchAllDebitTransactions (excludes credited, includes completed with "credits used: %" — i.e. credit consumption, plus a credits_remaining extracted from the card JSON); fetchPendingTransactions (cash pending + pending).
Every route (admin accounts).
| Method | Path | Auth realm | Purpose | Key params/body |
|---|---|---|---|---|
| GET | /api/v1/admin/accounts/fetchcompleted |
Accounts admin | Completed/credited txns for voucher creation | startDate, endDate, category, adhyayanId, utsavId |
| GET | /api/v1/admin/accounts/fetchpending |
Accounts admin | Pending + cash-pending txns | startDate, endDate |
| GET | /api/v1/admin/accounts/fetchcredits |
Accounts admin | Credited transactions (by updatedAt) |
startDate, endDate |
| GET | /api/v1/admin/accounts/fetchdebits |
Accounts admin | Credit-consumption transactions | startDate, endDate |
| POST | /api/v1/admin/accounts/setrep |
Accounts admin | Upload Razorpay settlement report Excel | multipart file |
| POST | /api/v1/admin/accounts/updateset |
Accounts admin | Upload Razorpay recon Excel (upsert) | multipart file |
| GET | /api/v1/admin/accounts/fetchset |
Accounts admin | List settlements (fees/tax summed from recon) | startDate, endDate |
| GET | /api/v1/admin/accounts/fetchTransactions/:settlementId |
Accounts admin | Orders under one settlement (+Satshrut fallback) | :settlementId |
| GET | /api/v1/admin/accounts/fetchTransactions/payment/:razorpay_order_id |
Accounts admin | Per-line detail for one order (+Satshrut fallback) | :razorpay_order_id |
| GET | /api/v1/admin/accounts/credits |
Accounts admin | All cards holding store credit | — |
| GET | /api/v1/admin/accounts/fetchcreditstransactions |
Accounts admin | Running credit ledger for one card | cardno, category |
Admin operations. Accounts admins do not create transactions here — they collect cash (marking cash pending → cash completed elsewhere), reconcile Razorpay settlements against local transactions, generate vouchers/receipts, and audit member credit balances. (Booking creation/cancellation — which is what mints and burns credit — lives in the booking/admin-booking controllers, see Booking Lifecycle.)
Discrepancies. (1) cerated_at is a genuine misspelled column, relied on across queries. [src: models/razorpay_settlement.model.js:32] (2) settled_by is written on recon upsert but absent from the RazorpaySettlementRecon model definition. [src: controllers/admin/accountsManagement.controller.js:439 (written); models/razorpay_settlement_recon.model.js (absent)] (3) Reconciliation depends on Razorpay Excel exports being manually uploaded — there is no automated settlement ingestion, so fetchAllSettlements fees/tax are only as fresh as the last upload.
Data & relationships (summary)¶
CardDb (cardno)1—NTransactions (cardno);Transactions belongsTo CardDb.Transactions (bookingid)belongsToRoomBooking/FlatBooking(declared); resolved dynamically toShibirBookingDb/TravelDb/UtsavBooking/FoodDbviacategory→bookingTypeMap.card_db.credits— JSON store-credit wallet keyed{ room, food, travel, utsav, adhyayan };flatfolds intoroom.razorpay_webhook— append-only archive of every webhook payload (source of truth for cross-app "Satshrut" reconciliation).razorpay_settlement1—Nrazorpay_settlement_recon(viasettlement_id); recon N—1 to an order (order_id) which maps totransactions.razorpay_order_id.
Edge cases & branches¶
- ₹0 net (credit ≥ charge) → transaction
completed+ booking confirmed at creation; no Razorpay, no order. - Late-checkout fee → a transaction with no corresponding booking; webhook capture handles
if (booking)and simply marks the txn completed. - Non-India user → transaction created
cash pending(never expires); online order never generated (country == 'India'gate); blocked from Razorpay in-app. - Waitlisted adhyayan/utsav →
charge = 0, no transaction cost until promoted. - Migrated bookings (
ifMigrated) → cancellation never credits; completed stays completed. - Webhook race → row-level
lock: { update: true }on the matching transactions; duplicate/late webhooks that find nothing pending simply return 200 with "No pending bookings". - Fake orders in dev/qa-non-prod → uuid
id, no real Razorpay call.
Discrepancies¶
Each item below is a summary of a discrepancy detailed and
[src:]-anchored in its section above (§A–§F); all were re-confirmed againstmainon 2026-07-07.
/razorpay/verifyPaymenthas no signature verification and no auth — a forgeable public endpoint that can confirm bookings and complete transactions.- Country casing —
'India'(booking) vs'INDIA'(cron filter) can strand a member's pending transactions from auto-cancel. - Utsav credit asymmetry — spendable/previewable but not earned on normal cancel.
- Adhyayan credit never applied in validate (and never earned).
- Flat priced at NAC room rate, no dedicated flat tariff.
adjustAmountreferences an undefineduser→ broken admin re-price path.STATUS_PAYMENT_CAPTUREDenum value unused as a stored transaction status; model TODO flagscash completed/completedas legacy./payvs/payv2disagree on food handling.- Settlement schema: misspelled
cerated_at;settled_bywritten but not modeled. - Self food is created
pendingbut cannot be paid online → cleared only by cash or 24h auto-cancel.
How this connects to other domains (technical)¶
- Booking Lifecycle — every booking type funnels into
createPendingTransaction, which both applies credit and decidespendingvscash pending; booking confirmation is a side effect of payment capture or full-credit coverage here. - Status & Cron — the 24h auto-cancel job is the enforcement arm of the
pendingstatus; booking status (pending checkin,confirmed,admin cancelled) is driven by transaction transitions in this doc. - Admin Panel Map — the
/admin/accountssurface (settlements, recon, vouchers, credit/debit/pending reports) is the operational read/reconcile layer over thetransactions,razorpay_settlement, andrazorpay_settlement_recontables described above. - Profile / Account — the member's transaction history and Available Credits card read the same
transactionsledger andcard_db.creditswallet. - Cross-app ("Satshrut") payments — payments to the shared Razorpay account that never created a local
transactionsrow are recovered for reconciliation purely fromrazorpay_webhookJSON.