Skip to content

Digital-Bills Module

One-line: After an order, Prism sends the customer a branded web receipt (via WhatsApp or SMS, immediately or delayed 15–240 min) containing the itemized bill + loyalty info + upsell/banner widgets + feedback link + profile-completion widget — and tracks the full engagement funnel (bills sent → clicks → unique clicks → opening %) in digital_bill_mis.

Validation legend

Symbol Meaning
Verified in code this review
📄 From PM Excel / business docs
💻 Code detail confirmed
♻️ Duplicated / overlapping implementation
⚰️ Legacy / temporary
⚠️ Bug, risk, or footgun

1. Overview

Digital Bills is a paid sub-product (📄 per-bill delivery charge, revenue model in Product-Overview §2). It replaces the paper receipt with a branded, interactive microsite. Configuration is a setup wizard persisted in MySQL link_config + link_widget_mapping; delivery + tracking run through MongoDB queues and MIS.

Two config surfaces: - Bill config ✅ — mode (1=SMS, 2=WhatsApp), templates, sender ID, brand color, footer, linkValidity, delivery delay time, and bill_platform (which order sources trigger a bill). - Widgets ✅ — ordered (sequence) list of widget blocks rendered inside the bill (itemized receipt, loyalty, upsell, feedback link, profile-completion, static banner).

⚠️ Known bug E1.8 — "Digital bills report logic" (P1) 📄 (Roadmap). The reporting funnel (digital_bill_mis) and the daily digitalBillMail report are flagged for correctness review — notably the derived opening % and how enabled-but-unsent businesses are attributed.


2. Business Flow

Example journey

  1. Setup (one-time) 📄 — Admin enables Digital Bills, picks channel/template/sender, sets delivery delay and platforms, arranges widgets. Go-Live checklist step 5 (Business-Flow §1).
  2. Order completes → order lands in crm_queue.
  3. processDigitalBills (or the order pipeline) matches business config, builds the bill payload, creates a short link (https://uen.io/{code}), enqueues SMS/WhatsApp with scheduled_at = now + time.
  4. Wallet is debited for the send (SMS or WhatsApp cost + GST).
  5. Customer opens the branded bill → invoiceClick fires → funnel counters increment.
  6. Customer interacts with widgets (widgetInsight), taps feedback link (feedbackClick), downloads PDF (invoiceDownload).

Edge cases ✅

  • No config — child config missing → falls back to parent (parentId=0); if neither, bill is skipped.
  • Masked / invalid mobile — Indian 10-digit [6-9] only; sequential/repeated numbers rejected → no bill.
  • Duplicate order — existing order_id in digital_bill_customers for that child → skipped (idempotency).
  • Insufficient wallet — send blocked (surfaced in digitalBillMail as reason "wallet balance < 0.30").
  • Platform gate — order source not in bill_platform array → skipped (null/empty = all platforms).
  • Special parents — parent 27234 skips "Pick Up" orders ⚠️ (hardcoded).

3. Technical Flow

Source → API → Validation → Logic → DB → Events → Queue → Notification → Response
  • Source ✅ — POS/order events in crm_queue (source=2 petpooja handled by cron; other sources via order pipeline). Config comes from the wizard APIs.
  • API ✅ — Config routes (/links/*, auth) + insight routes (/invoice/*, injestion_middleware).
  • Validation ✅ — mobile format; platform gate; duplicate order_id; wallet balance.
  • Logic ✅ — commonFunctions/digitalBill.js builds the bill (customer, order, items, loyalty points, feedback-form JWT), computes scheduled_at, chooses SMS vs WhatsApp, renders template variables.
  • DB ✅ — writes digital_bill_customers, digital_bill_mis, wallet_transactions; reads link_config, link_widget_mapping, business_configs, wallet_config, feedback_configs.
  • Events / Queue ✅ — inserts into sms_queue_YYYYMMDD (mode 1) or whatsapp_queue_YYYYMMDD (mode 2). Drained by the SMS/WhatsApp send crons (Queues).
  • Notification ✅ — customer receives the link; opens/clicks tracked by invoiceInsightsController.
  • Response ✅ — link created via POST https://api.uengage.in/links/createLink{shortened_url, bill_id}.

4. Architecture Diagram

Flowchart

flowchart TD
    subgraph Config["Setup wizard (MySQL)"]
        LC[(link_config)]
        LWM[(link_widget_mapping)]
        LWC[(link_widget_copy)]
    end
    DBC[digitalBillController] --> LC
    DBC --> LWM
    DBC --> LWC

    subgraph Gen["Bill generation"]
        Q[(crm_queue source=2)]
        CRON[processDigitalBills 09:57 IST]
        FN[commonFunctions/digitalBill.js]
        LINK[(api.uengage.in/links/createLink)]
    end
    Q --> CRON --> FN
    FN --> LINK
    FN -->|mode 1| SQ[(sms_queue_YYYYMMDD)]
    FN -->|mode 2| WQ[(whatsapp_queue_YYYYMMDD)]
    FN --> DBCUST[(digital_bill_customers)]
    FN --> DBMIS[(digital_bill_mis)]
    FN --> WT[(wallet_transactions)]

    subgraph Track["Engagement tracking"]
        II[invoiceInsightsController]
    end
    CUSTOMER((Customer)) -->|open| II
    CUSTOMER -->|widget tap| II
    CUSTOMER -->|feedback| II
    CUSTOMER -->|download| II
    II --> DBCUST
    II --> DBMIS
    II --> FBMIS[(feedback_mis)]

    subgraph Report["Daily report"]
        MAIL[digitalBillMail 11:32 IST]
    end
    DBMIS --> MAIL --> EMAIL[[Ops email + XLSX]]

Sequence — generate + track

sequenceDiagram
    autonumber
    participant CRON as processDigitalBills
    participant CFG as link_config / business_configs
    participant FN as digitalBill.js
    participant LINK as links/createLink
    participant Q as sms_queue / whatsapp_queue
    participant CUST as Customer
    participant II as invoiceInsightsController
    participant MIS as digital_bill_mis

    CRON->>CFG: match config (child→parent fallback)
    CRON->>FN: digitalBill(order)
    FN->>FN: validate mobile, platform gate, dedup order_id
    FN->>LINK: POST create link
    LINK-->>FN: {shortened_url, bill_id}
    FN->>Q: enqueue (scheduled_at = now + time)
    FN->>MIS: inc total_bill_sent, total_sms/whatsapp_sent
    Note over Q: SMS/WhatsApp cron delivers link
    CUST->>II: POST /invoice/open {bill_id}
    II->>MIS: inc total_clicks; inc unique_clicks if first click

5. Folder Structure (real files)

uengage-crm/
├── controllers/
│   ├── digitalBillController.js       ✅ config wizard + customer/MIS reads
│   └── invoiceInsightsController.js   ✅ click / widget / feedback / download tracking
├── commonFunctions/
│   ├── digitalBill.js                 ✅ bill builder + delivery + wallet debit
│   └── digitalBillTemp.js             ⚰️ temp/test variant (manual_insert_temp=1)
└── crons/
    ├── processDigitalBills.js         ✅ crm_queue → bill generation (09:57 IST)
    └── digitalBillMail.js             ✅ daily Ops report (11:32 IST)

6. Database

Config (MySQL)

Table Purpose Key fields ✅
link_config Per-business bill config businessId, parentId (0=parent), mode (1 SMS / 2 WA), type, linkValidity, brandColor, enableFooter, footerButton, time (delay min), status, sms_template, whatsapp_template, sender_id, sms_variables, whatsapp_variables, bill_platform (app/website/kiosk/pos/waba), insertedAt, updatedAt
link_widget_copy Master list of available widgets id, name, status
link_widget_mapping Per-business ordered widgets businessId, parentId, widgetId, sequence, title, value, static_banner_link, status, insertedAt

Tracking (MongoDB)

Collection Purpose Key fields ✅
digital_bill_customers One row per bill sent bill_id, name, number, order_id, sent_at, mode, short_url, clicked, download_flag, sent_count, delivered, read_count, message_error, widgets[] (widgetId,clicks,watchTime), roi_calculated, order_source, parent_business_id, child_business_id
digital_bill_mis Daily funnel per parent/child date, total_bill_sent, total_clicks, unique_clicks, total_sms_sent, total_whatsapp_sent, parent_business_id, child_business_id, created_at, updated_at
wallet_config Pricing digital_bill_sms (₹0.145), digital_whatsapp_cost (₹0.92), digital_whatsapp_cost_utility (₹0.25), sms_gst/whatsapp_gst (18)
feedback_configs Feedback flow gate per parent/child (child_business_id="0" = parent)
feedback_customers / feedback_mis Feedback link funnel mirrors bill funnel

Indexes: none defined on the MIS/customer collections ⚠️ (see map-03 §5.1); the digital_bill_customers dedup lookup by child_business_id+order_id is unindexed.

Relationships

link_config (MySQL, by businessId/parentId) drives generation → each send writes one digital_bill_customers doc keyed by order_id and a daily digital_bill_mis roll-up. The feedback link inside a bill flows into the Feedback-NPS funnel (feedback_customers/feedback_mis).

The funnel (E1.8 relevant) ✅

Metric Field Increment rule
Bills sent total_bill_sent +1 per generated bill
Total clicks total_clicks +1 on every /invoice/open
Unique clicks unique_clicks +1 only when clicked==1 (first open)
Opening % derived unique_clicks / total_bill_sent

Common queries ✅

// funnel for a date range
db.digital_bill_mis.aggregate([
  { $match: { parent_business_id, date: { $gte, $lte } } },
  { $group: { _id:null, sent:{$sum:"$total_bill_sent"}, clicks:{$sum:"$total_clicks"}, uniq:{$sum:"$unique_clicks"} } }
])
// dedup check before generating
db.digital_bill_customers.findOne({ child_business_id, order_id })

7. APIs

Config (auth: authMiddleware, all POST)

Route Purpose Function
/links/getWidgetList List available widgets listing_widgets
/links/addBusinessConfig Create/update bill config addBusinessConfig
/update/bill/status Toggle bill on/off (MySQL + business_configs.digital_bill) updateBillStatus
/links/getBusinessConfig Read config + widgets getBusinessConfig
/links/addWidget Add widget to bill addWidget
/links/editWidget Edit widget editWidget
/get_digital_bill_customers Paginated sent list (10/page) getDigitalBillCustomers
/get_digital_bill_mis Funnel aggregates getDigitalBillMis

Insights (auth: injestion_middleware, all POST) ✅

Route Records Function
/invoice/open clicked++, funnel total_clicks/unique_clicks invoiceClick
/feedback_invoice/open feedback clicked++, feedback_mis feedbackClick
/update/widget/insight widgets.$[w].clicks++ or .watchTime set widgetInsight
/invoice/download download_flag++ invoiceDownload

Representative example — record open (verify)

// POST /invoice/open   (injestion_middleware)
{ "bill_id": 88421 }
// 200 → { "status": 1 }
Validation ✅: bill_id required. Failures: 400 missing bill_id; unknown bill_id → no-op update.

Representative example — add config (verify)

// POST /links/addBusinessConfig   Headers: { token: <auth> }
{ "business_id":"12345","parent_business_id":"999","mode":2,"type":1,
  "linkValidity":30,"brandColor":"#C0392B","enableFooter":1,"footerButton":"Order Again",
  "time":30,"status":1,"whatsapp_template":"digital_bill","sender_id":"BRAND",
  "platform":["pos","app"] }
// 200 → { "status": 1, "message": "config saved" }
time is the delivery delay in minutes (immediate = 0; business range 15–240 📄).


8. Code Walkthrough

Generation

processDigitalBills (cron 09:57 IST)
  └─ poll crm_queue {status:1, source:2}         (batch 10,000)
     └─ resolve business (crm_token OR petPooja_business_mapping+addo_business)
        └─ skip Uengage/Zomato/Swiggy sources; skip dup order_id; parent 27234 skips Pick Up
           └─ digitalBilFlow / digitalBill(businessData, data, "petpooja", "pos", points)
                ├─ validate mobile [6-9]\d{9}, platform gate (mapPlatform)
                ├─ scheduled_at = moment().add(link_config.time,"minutes")
                ├─ POST api.uengage.in/links/createLink → {shortened_url, bill_id}
                ├─ build bill: customer, order (subTotal/tax/discount/loyaltyPoints), items[], feedback JWT
                ├─ mode 1 → sms_queue_*  ·  mode 2 → whatsapp_queue_* (gupshup|facebook)
                ├─ debit wallet (cost + GST) → wallet_transactions
                └─ insert digital_bill_customers + upsert digital_bill_mis
     └─ set crm_queue.status = 3

Tracking ✅ — invoiceInsightsController: - invoiceClick(bill_id)$inc clicked; if clicked==1 also $inc unique_clicks; always $inc total_clicks (+upsert digital_bill_mis by date). - widgetInsight(bill_id, widget_id, event_type, watch_time) — arrayFilters update widgets.$[w].clicks ($inc) or .watchTime ($set). - feedbackClick, invoiceDownload — analogous counters.

Reporting ✅ — digitalBillMail (11:32 IST) aggregates digital_bill_mis, compares yesterday vs last-week/2-weeks-ago via dashboard_mis (total_pos_count,total_uen_count), lists enabled-but-unsent businesses with reasons, emails an XLSX to Ops via POST https://www.uengage.in/addoapi/sendCRMMail.

Dependencies: axios (link creation + mail), moment-timezone (IST scheduling), MySQL pool (link_* tables), SMS/WhatsApp queues (downstream send).

⚰️ digitalBillTemp.js is a temp/test twin (hardcoded queue date, manual_insert_temp=1) — processDigitalBills currently imports it as digitalBilFlow ⚠️ (verify which builder is authoritative in prod).


9. Business Rules

Rule Value ✅ Source
Delivery delay link_config.time minutes (immediate=0; 15–240 range 📄) digitalBill.js
Channel mode 1=SMS, 2=WhatsApp link_config
Platform gate bill_platform ∈ {app, website, kiosk, pos, waba}; null=all mapPlatform
Config fallback child → parent (parentId=0)
Dedup one bill per (child_business_id,order_id)
SMS cost ₹0.145 + 18% GST wallet_config
WhatsApp cost ₹0.92 marketing / ₹0.25 utility + 18% GST wallet_config
Cron generate 09:57 IST, batch 10,000 processDigitalBills
Report 11:32 IST daily → Ops XLSX digitalBillMail
Mobile validity [6-9]\d{9}, reject sequential/repeated
Unsent reason threshold wallet balance < 0.30 digitalBillMail

Hidden logic ⚠️: parent 27234 Pick-Up exclusion; digitalBillTemp temp builder in the cron path; feedback-form JWT hash secret embedded in digitalBill.js (security-review item, Security).

Widgets in the bill 📄/✅: itemized receipt, loyalty info (points earned), upsell/product widgets, static banner, feedback-form link, profile-completion widget — configured/ordered via link_widget_mapping.sequence.


10. Performance

  • processDigitalBills batches 10,000 with 0 ms inter-doc delay — CPU-bound on parse/HTTP; the per-order createLink HTTP call is the latency floor ⚠️.
  • Dedup and MIS lookups are unindexed ⚠️ — will degrade as digital_bill_customers grows (date-partitioned queues help downstream, not this collection).

11. Logging

  • Winston standard; per-order failures logged and skipped (cron continues).
  • Wallet debits recorded in wallet_transactions (auditable ledger).

12. Monitoring

  • digitalBillMail is the de-facto daily monitor: sent counts, week-over-week deltas, enabled-but-unsent + reason.
  • Watch opening % drift for E1.8; reconcile total_bill_sent vs actual digital_bill_customers inserts.

13. Troubleshooting

Issue Cause Fix Command
Bills enabled but none sent Wallet < 0.30, no orders, masked numbers, config error Read the reason in daily report inspect digitalBillMail output
Opening % looks wrong (E1.8) unique_clicks/total_bill_sent mismatch Recompute from raw docs db.digital_bill_customers.count({child_business_id, sent_at:{$gte}}) vs digital_bill_mis.total_bill_sent
Duplicate bill sent dedup lookup missed (unindexed / race) Add index; check order_id db.digital_bill_customers.find({order_id}).count()
Bill not sent for a source source not in bill_platform Add platform to config check link_config.bill_platform
Wrong delivery time time misconfigured Update config SELECT time FROM link_config WHERE businessId=?

14. FAQs

  • Immediate vs delayed? time=0 immediate; else now + time minutes (15–240 typical).
  • SMS or WhatsApp? mode on link_config (1/2).
  • Where's the feedback link? Injected into the bill as a JWT-signed URL → routes into Feedback-NPS.
  • Which builder runs in the cron? digitalBillTemp (imported as digitalBilFlow) — verify vs digitalBill.js ⚠️.

15. Cheat Sheet

config      : MySQL link_config (mode 1=SMS/2=WA, time=delay min, bill_platform[])
widgets     : MySQL link_widget_mapping (sequence order)
generate    : processDigitalBills 09:57 IST, crm_queue source=2, batch 10k
funnel      : digital_bill_mis {total_bill_sent, total_clicks, unique_clicks}
opening %   : unique_clicks / total_bill_sent          (E1.8 under review)
track APIs  : /invoice/open, /update/widget/insight, /feedback_invoice/open, /invoice/download
cost        : SMS ₹0.145 · WA ₹0.92 (util ₹0.25) + 18% GST
report      : digitalBillMail 11:32 IST → Ops XLSX
KNOWN BUG   : E1.8 digital bills report logic (P1)

Knowledge Tests

Level 1 — MCQs

  1. link_config.mode = 2 means? a) SMS b) WhatsApp c) email d) disabled — b.
  2. unique_clicks increments when? a) every open b) only the first open of a bill c) on download d) on widget tap — b (clicked==1).
  3. Opening % is derived as? a) clicks/sent b) unique_clicks/total_bill_sent c) download/sent d) sent/clicks — b.
  4. A bill is skipped if the order_id already exists in? a) crm_queue b) digital_bill_mis c) digital_bill_customers d) link_config — c.
  5. time=30 in link_config controls? a) validity days b) delivery delay minutes c) GST d) widget count — b.

Level 2 — Scenarios

  1. A brand says "we enabled Digital Bills yesterday but zero went out." Which report tells you why, and name three possible reasons it lists. (Expect: digitalBillMail; wallet < 0.30, no orders, masked numbers, config error.)
  2. Opening % on the dashboard is 140%. Explain how E1.8 / the increment rules could produce >100% and which two fields you'd reconcile. (Expect: total_clicks vs unique_clicks confusion, or total_bill_sent undercount; reconcile against raw digital_bill_customers.)

Level 3 — Code reading

In invoiceInsightsController.invoiceClick, trace what happens to digital_bill_mis on the second open of the same bill. (Answer: total_clicks +1, unique_clicks unchanged because clicked is now 2, not 1.)

Level 4 — Architecture

processDigitalBills calls createLink over HTTP per order inside a 10,000 batch. Propose an architecture change to remove this as the throughput bottleneck and state the trade-off for E1.8 reporting accuracy.

Level 5 — Prod debugging

Duplicate bills are reaching a subset of customers. Give the query to confirm duplication and the most likely root cause given the dedup lookup is unindexed and the cron batches concurrently.

Practical Assignments

  1. Write a read-only reconciliation script comparing digital_bill_mis.total_bill_sent to counted digital_bill_customers for one parent/day (directly probing E1.8).
  2. Propose and justify indexes for digital_bill_customers (dedup) and digital_bill_mis (funnel range) in a short note.
  3. Document the exact widget set rendered in a bill by reading link_widget_copy + a sample link_widget_mapping, mapping each widgetId to its business purpose.