From 63aaf1eb2149f4d9cdd48b55ce7bf27232c39967 Mon Sep 17 00:00:00 2001 From: siddharthd Date: Tue, 28 Jul 2026 20:32:02 +1000 Subject: [PATCH] fix(orders): read A$ totals and credits-funded receipts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two parse bugs that between them made 218 of 776 captured messages unreadable. Neither was the "old template" they were filed as. Uber writes the currency three ways and only two were handled. "Total A$54.87" is what it sends for ordinary Australian orders — A$ misses [A-Z]{3} by one character — so 98 of 275 Uber Eats mails and 78 of 275 trip mails failed with "no Total found" while the amount sat in plain sight. Most were 2024-2025, i.e. current mail. NZ$, US$, S$, HK$, C$ and a bare rupee/euro/pound symbol are handled the same way. A bare "$" is still left unresolved on purpose: a dozen currencies use it, and the body-wide scan that reads the receipt's own stated code should win. A DoorDash order paid from credits states "Total Charged $0.00" truthfully, above a real subtotal. Read literally that is a $0 order, and validateOrderTotals rejected 88 of them as non-positive — discarding exactly the credit-funded spend this pipeline exists to surface. The order's value is its subtotal; recording zero would show the order and hide what it cost. Guarded on the receipt actually saying credits, so an empty mail still fails rather than inheriting a stray subtotal, and the header cross-check stands down for these or it rejects the figure the parser deliberately overrode. Measured A/B over all 776 captures: 254 parsed by both parsers with zero change to any amount or currency, 0 lost, 214 newly readable. Both fixes mutation-tested — reverting the regex fails 4 tests, removing the credits branch fails 3. Fixtures are real captured receipts, per the 2026-07-26 rewrite: the earlier synthetic suite passed while the parser could not read a real email. No history replay — I7 idempotency refuses re-reads and that needs an explicit update mode. This fixes ingest from here on. --- .../real/currency-and-credits-meta.json | 56 ++ .../fixtures/orders/real/dd-credits-zero.html | 868 ++++++++++++++++++ .../fixtures/orders/real/ue-aud-prefix.html | 63 ++ .../fixtures/orders/real/ue-nzd-prefix.html | 63 ++ .../fixtures/orders/real/ut-aud-prefix.html | 42 + .../fixtures/orders/real/ut-inr-symbol.html | 42 + .../fixtures/orders/real/ut-nzd-prefix.html | 42 + src/__tests__/unit/order-ingestion.test.ts | 82 ++ src/lib/order-parse.ts | 85 +- 9 files changed, 1336 insertions(+), 7 deletions(-) create mode 100644 src/__tests__/fixtures/orders/real/currency-and-credits-meta.json create mode 100644 src/__tests__/fixtures/orders/real/dd-credits-zero.html create mode 100644 src/__tests__/fixtures/orders/real/ue-aud-prefix.html create mode 100644 src/__tests__/fixtures/orders/real/ue-nzd-prefix.html create mode 100644 src/__tests__/fixtures/orders/real/ut-aud-prefix.html create mode 100644 src/__tests__/fixtures/orders/real/ut-inr-symbol.html create mode 100644 src/__tests__/fixtures/orders/real/ut-nzd-prefix.html diff --git a/src/__tests__/fixtures/orders/real/currency-and-credits-meta.json b/src/__tests__/fixtures/orders/real/currency-and-credits-meta.json new file mode 100644 index 0000000..95d6cd7 --- /dev/null +++ b/src/__tests__/fixtures/orders/real/currency-and-credits-meta.json @@ -0,0 +1,56 @@ +[ + { + "file": "dd-credits-zero.html", + "id": "19b692bdf1ea7a08", + "subject": "Order Confirmation for Siddharth from Chilli India", + "receivedAt": "2025-12-29T08:14:00.000Z", + "sender": "DoorDash ", + "why": "DoorDash credits-funded, Total Charged $0.00", + "stated": null + }, + { + "file": "ue-aud-prefix.html", + "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAhmyVi0AAAA", + "subject": "Your Friday evening order with Uber Eats", + "receivedAt": "2025-10-17T09:50:39Z", + "sender": "noreply@uber.com", + "why": "Uber total written as Total A\\$", + "stated": "Total A$54.87" + }, + { + "file": "ue-nzd-prefix.html", + "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAf4nGLUAAAA", + "subject": "Your Saturday evening order with Uber Eats", + "receivedAt": "2025-05-03T19:26:05Z", + "sender": "noreply@uber.com", + "why": "Uber total written as Total NZ\\$", + "stated": "Total NZ$22.83" + }, + { + "file": "ut-aud-prefix.html", + "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAhLnaIfAAAA", + "subject": "Your Saturday morning trip with Uber", + "receivedAt": "2025-09-05T23:17:58Z", + "sender": "noreply@uber.com", + "why": "Uber total written as Total A\\$", + "stated": "Total A$14.19" + }, + { + "file": "ut-nzd-prefix.html", + "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAf1poEUAAAA", + "subject": "Your Sunday afternoon trip with Uber", + "receivedAt": "2025-04-27T13:27:16Z", + "sender": "noreply@uber.com", + "why": "Uber total written as Total NZ\\$", + "stated": "Total NZ$10.83" + }, + { + "file": "ut-inr-symbol.html", + "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAgPBHtAAAAA", + "subject": "Your Friday evening trip with Uber", + "receivedAt": "2025-06-06T18:00:51Z", + "sender": "noreply@uber.com", + "why": "Uber total written as Total \u20b9", + "stated": "Total \u20b9622.74" + } +] \ No newline at end of file diff --git a/src/__tests__/fixtures/orders/real/dd-credits-zero.html b/src/__tests__/fixtures/orders/real/dd-credits-zero.html new file mode 100644 index 0000000..b8b0850 --- /dev/null +++ b/src/__tests__/fixtures/orders/real/dd-credits-zero.html @@ -0,0 +1,868 @@ + + + + DoorDash + + + + + + + + + + + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
DOORDASH
+ + + + + + +
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+ + + + + + +
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + +
+ + + + + + + +
+ + + + + + + +
+ + + + + + + +
+ + + + + + +
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + +
+ + + + + + +
+ + + + + + +
DOORDASH | Pick up your order | Ready now | I Picked Up My Order
+ + + + + + +
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + +
1
+ + + + + + + +
+ + + + + + +
2
+ + + + + + + +
+ + + + + + +
3
+ + + + + + +
+ + + + + + +
+ + + + + + + + +
+ + + + + + + + + +
Prop 65 Restaurant WARNING

Prop 65 Alcohol WARNING
Drinking distilled spirits, beer, coolers, wine, and other alcoholic beverages may increase cancer risk, and, during pregnancy, can cause birth defects.  For more information go to www.P65Warnings.ca.gov/alcohol.

Prop 65 BPA WARNING
+ + \ No newline at end of file diff --git a/src/__tests__/fixtures/orders/real/ue-aud-prefix.html b/src/__tests__/fixtures/orders/real/ue-aud-prefix.html new file mode 100644 index 0000000..91bea8f --- /dev/null +++ b/src/__tests__/fixtures/orders/real/ue-aud-prefix.html @@ -0,0 +1,63 @@ + +
 
 
 
 
 
 
Thanks for being an Uber One member, Siddharth
Here's your receipt for ordering with Thaigerlily.
 
 
TotalA$54.87
You saved A$4.02 on this order with Uber One and promos
 
To view your full receipt go to Uber Eats , or download this PDF
 
Payments 
Uber Cash
10/17/25 8:50 PM
A$54.87
Visit the order page for more information, including invoices (where available).
Portier Pacific is not the seller of the goods.
Switch Payment Method >
 
You ordered from Thaigerlily
Delivered to
19 Lady Penrhyn Dr, Wyndham Vale VIC 3024, Australia
Restaurant Image
Delivered by ANJALI
 
 
Forgot password
Privacy
Terms
Portier Pacific Pty Ltd
Level 8, 1 O’Connell Street, NSW 2000, Sydney, Australia
 
\ No newline at end of file diff --git a/src/__tests__/fixtures/orders/real/ue-nzd-prefix.html b/src/__tests__/fixtures/orders/real/ue-nzd-prefix.html new file mode 100644 index 0000000..32d9500 --- /dev/null +++ b/src/__tests__/fixtures/orders/real/ue-nzd-prefix.html @@ -0,0 +1,63 @@ + +
 
 
 
 
 
 
Thanks for being an Uber One member, Siddharth
Here's your receipt for ordering with McDonald's® (Queenstown).
 
 
TotalNZ$22.83
 
To view your full receipt go to Uber Eats , or download this PDF
 
Payments 
ANZ Rewards ••••2329
5/4/25 7:25 AM
NZ$22.83
Visit the order page for more information, including invoices (where available).
Portier New Zealand is not the seller of the goods.
Switch Payment Method >
 
You ordered from McDonald's® (Queenstown)
Delivered to
37/527 Frankton Road, Queenstown 9300, New Zealand
Restaurant Image
Delivered by ANKIT
 
 
Forgot password
Privacy
Terms
Portier New Zealand Limited
Level 1, 100 Moorhouse Avenue, Christchurch, New Zealand, 8011
 
\ No newline at end of file diff --git a/src/__tests__/fixtures/orders/real/ut-aud-prefix.html b/src/__tests__/fixtures/orders/real/ut-aud-prefix.html new file mode 100644 index 0000000..a3d5f7c --- /dev/null +++ b/src/__tests__/fixtures/orders/real/ut-aud-prefix.html @@ -0,0 +1,42 @@ + +
 
 
 
 
 
 
Thanks for being an Uber One member, Siddharth
We hope you enjoyed your ride this morning.
 
 
TotalA$14.19
You earned A$0.71 Uber One credits with Uber One
 
Trip fareA$12.57
 
SubtotalA$12.57
Booking Fee A$0.99
Victorian Government Levy A$1.32
Uber One Credits-A$0.69
 
Payments 
Uber Cash
9/6/25 9:17 AM
A$14.19
Visit the trip page for more information, including invoices (where available)
Switch Payment Method
Download PDF
 
You rode with SALAH
5.00Rating
Booking Service Provider: Rasier Pacific Pty Ltd
Vehicle Registration: 2AJ1KD
Driver Accreditation Number: 526426
Every rideshare trip starting in Melbourne is insured. See how we are always working to keep you safe:
Learn more
 
UberX
6.77 kilometers | 11 minutes
9:05 AM
40 Cleeland St, Dandenong, VIC 3175, AU
9:17 AM
370 Frankston-Dandenong Rd, Dandenong South, VIC 3175, AU
Report lost item
 
 
My trips
 
 
 
Forgot password
Privacy
Terms
Rasier Pacific Pty Ltd
Level 8, 1 O’Connell Street, NSW 2000, Sydney, Australia
 
Fare does not include fees that may be charged by your bank. Please contact your bank directly for inquiries.
\ No newline at end of file diff --git a/src/__tests__/fixtures/orders/real/ut-inr-symbol.html b/src/__tests__/fixtures/orders/real/ut-inr-symbol.html new file mode 100644 index 0000000..81b2785 --- /dev/null +++ b/src/__tests__/fixtures/orders/real/ut-inr-symbol.html @@ -0,0 +1,42 @@ + +
 
 
 
 
 
 
Thanks for being an Uber One member, Siddharth
We hope you enjoyed your ride this evening.
 
 
Total₹622.74
 
Trip Charge₹649.39
 
Subtotal₹649.39
Rider Promotion-₹136.65
Paid to Airport₹110.00
 
Payments 
Citi Prestige ••••0253
6/6/25 11:30 PM
₹622.74
Visit the trip page for more information, including invoices (where available)
The total of ₹622.74 has a GST of ₹41.20 included.
Switch Payment Method
Download PDF
 
You rode with Kangkan Nath
4.98Rating
License Plate: AS01JC6802
 
Premier
14.42 kilometers | 15 min
11:01 PM
Airport Exit Rd, Borjhar, Guwahati, Assam 781015, India
11:17 PM
2, Assam Trunk Rd, opposite BIJARNIA HARDWARE, Adabari, Guwahati, Assam 781014, India
Report lost item
 
 
My trips
 
 
 
Forgot password
Privacy
Terms
Uber India Systems Private Limited
 
Fares are inclusive of GST. Please download the tax invoice from the trip detail page for a full tax breakdown.
\ No newline at end of file diff --git a/src/__tests__/fixtures/orders/real/ut-nzd-prefix.html b/src/__tests__/fixtures/orders/real/ut-nzd-prefix.html new file mode 100644 index 0000000..73099a2 --- /dev/null +++ b/src/__tests__/fixtures/orders/real/ut-nzd-prefix.html @@ -0,0 +1,42 @@ + +
 
 
 
 
 
 
Thanks for being an Uber One member, Siddharth
We hope you enjoyed your ride this afternoon.
 
 
TotalNZ$10.83
 
Rider Service FeeNZ$1.03
Trip fareNZ$9.80
 
SubtotalNZ$10.83
 
Payments 
ANZ Rewards ••••2329
4/28/25 1:27 AM
NZ$10.83
Visit the trip page for more information, including invoices (where available)
Switch Payment Method
Download PDF
 
You rode with Lovepreet Singh
5.00Rating
 
UberX
1.16 kilometers | 2 min
2:31 PM
23 Margaret Place, Frankton, Queenstown 9300, NZ
2:33 PM
7 Sutherland Lane, Frankton, Queenstown 9300, New Zealand
Report lost item
 
 
My trips
 
 
 
Forgot password
Privacy
Terms
Rasier New Zealand Limited
Level 1, 100 Moorhouse Avenue,
Christchurch, 8011
 
Fare does not include fees that may be charged by your bank. Please contact your bank directly for inquiries.
\ No newline at end of file diff --git a/src/__tests__/unit/order-ingestion.test.ts b/src/__tests__/unit/order-ingestion.test.ts index 1f21789..09d4906 100644 --- a/src/__tests__/unit/order-ingestion.test.ts +++ b/src/__tests__/unit/order-ingestion.test.ts @@ -330,3 +330,85 @@ describe("orderDescription", () => { ); }); }); + +/** + * Two parse failures that between them accounted for 218 of the 287 unreadable + * messages in the 776-message capture set. Neither was an "old template": the + * Uber one fails on current 2024-2025 mail, and the DoorDash one fails on every + * order paid from credits, in every year. + */ +describe("currency notations Uber actually sends", () => { + const uber = (f: string, subject: string) => + parseOrderHTML(html(f), meta({ subject, sender: "Uber Receipts " })); + + it("reads a symbol-prefixed Australian total", () => { + // "Total A$54.87". The old pattern allowed a 3-letter ISO code or a bare + // "$", so A$ — which is what Uber sends for ordinary domestic orders — + // matched neither and 98 of 275 Uber Eats mails were unreadable. + const o = uber("ue-aud-prefix", "Your Friday evening order with Uber Eats"); + expect(o.totals.total_charged).toBe(54.87); + expect(o.currency).toBe("AUD"); + }); + + it("reads NZ$ as New Zealand dollars, not Australian", () => { + // The prefix is the only thing distinguishing them, and getting it wrong + // books a Queenstown dinner at the wrong rate rather than failing loudly. + const o = uber("ue-nzd-prefix", "Your Saturday evening order with Uber Eats"); + expect(o.totals.total_charged).toBe(22.83); + expect(o.currency).toBe("NZD"); + }); + + it("reads a bare rupee symbol on a trip", () => { + const o = uber("ut-inr-symbol", "Your Friday evening trip with Uber"); + expect(o.totals.total_charged).toBe(622.74); + expect(o.currency).toBe("INR"); + }); + + it("still reads the space-separated ISO form", () => { + // The [Family] LKR receipts depend on this and must not regress. + const o = uber("ut-nzd-prefix", "Your Sunday afternoon trip with Uber"); + expect(o.totals.total_charged).toBe(10.83); + expect(o.currency).toBe("NZD"); + }); +}); + +describe("credits-funded orders are orders", () => { + const credits = () => + parseOrderHTML( + html("dd-credits-zero"), + meta({ subject: "Order Confirmation for Siddharth from Chilli India" }) + ); + + it("records the subtotal when the card was charged nothing", () => { + // The receipt says "Subtotal $71.86 ... Total Charged $0.00" — truthfully, + // because credits covered it. Reading that as a $0 order threw away the + // credit-funded spend this pipeline exists to surface. + const o = credits(); + expect(o.totals.total_charged).toBe(71.86); + expect(o.totals.subtotal).toBe(71.86); + expect(o.flags).toContain("credits_funded_zero_charge"); + }); + + it("books the amount as credits, not as a card charge", () => { + const o = credits(); + expect(o.payment.credits_amount).toBe(71.86); + expect(o.payment.card_amount).toBeNull(); + expect(o.payment.card_last4).toBeNull(); + }); + + it("passes validation instead of being rejected as non-positive", () => { + // Both stated totals are $0.00 and agree, so the header cross-check has to + // stand down here or it rejects the very figure the parser overrode. + const o = credits(); + expect(validateOrderTotals(o, html("dd-credits-zero"))).toEqual({ ok: true }); + }); + + it("does not invent a total when the receipt never says credits", () => { + // The guard that keeps this from becoming "any zero total borrows the + // subtotal" — a genuinely empty receipt must still fail. + const notCredits = html("dd-credits-zero").replace(/Paid with/gi, "Charged to"); + const o = parseOrderHTML(notCredits, meta({ subject: "Order Confirmation for Siddharth from Chilli India" })); + expect(o.totals.total_charged).toBe(0); + expect(validateOrderTotals(o, notCredits).ok).toBe(false); + }); +}); diff --git a/src/lib/order-parse.ts b/src/lib/order-parse.ts index 873228c..29c1668 100644 --- a/src/lib/order-parse.ts +++ b/src/lib/order-parse.ts @@ -282,6 +282,29 @@ function parseMerchant(platform: string, meta: MessageMeta, text: string): strin throw new OrderParseError(`cannot determine merchant`, meta.messageId); } +/** + * Currency notations Uber actually uses in receipt totals. + * + * Deliberately narrow: only prefixes that are unambiguous. "R$" (BRL) and "$" + * alone are excluded — a bare dollar sign is used by a dozen currencies and + * resolving it here would overrule the body-wide scan that reads the receipt's + * own stated code. + */ +const SYMBOL_PREFIX_CURRENCY: Record = { + A: "AUD", + NZ: "NZD", + US: "USD", + S: "SGD", + HK: "HKD", + C: "CAD", +}; + +const SYMBOL_CURRENCY: Record = { + "₹": "INR", + "€": "EUR", + "£": "GBP", +}; + function parsePayment(platform: string, html: string, text: string): PaymentBreakdown { const out: PaymentBreakdown = { credits_amount: null, @@ -467,8 +490,9 @@ export function parseOrderHTML(html: string, meta: MessageMeta): ParsedOrder { if (total === null) { throw new OrderParseError("no total stated anywhere in receipt", meta.messageId); } + const subtotal = tdPairValue(clean, "Subtotal"); totals = { - subtotal: tdPairValue(clean, "Subtotal"), + subtotal, taxes: tdPairValue(clean, "Taxes"), delivery_fee: tdPairValue(clean, "Delivery Fee"), service_fee: tdPairValue(clean, "Service Fee"), @@ -476,13 +500,43 @@ export function parseOrderHTML(html: string, meta: MessageMeta): ParsedOrder { discounts: tdPairValue(clean, "Discounts"), total_charged: total, }; + + // An order paid entirely from DoorDash credits states "Total Charged + // $0.00" — truthfully, because nothing was charged to a card — while the + // items above it add up to a real amount. Read literally that is a $0 + // order, and `validateOrderTotals` rejected 88 of them as "non-positive + // total 0", which is the single largest cause of parse failures in the + // captured mail and discards exactly the credit-funded spend this pipeline + // exists to make visible. + // + // The order's value is its subtotal. Recording that keeps a credits meal + // countable in budgets; recording zero would show the order and hide what + // it cost. Guarded on the receipt actually saying credits, so a genuinely + // zero-value mail still fails rather than inheriting a stray subtotal. + if (total === 0 && subtotal !== null && subtotal > 0 && /Paid with[\s\S]{0,60}?credits/i.test(text)) { + totals.total_charged = subtotal; + flags.push("credits_funded_zero_charge"); + } } else { - // Uber Eats states a Total, optionally in a foreign currency: - // "Total $25.33" | "Total LKR 3,783.20" + // Uber Eats states a Total, optionally in a foreign currency. Uber writes + // the currency in three different notations and all three occur in real + // mail: + // "Total $25.33" bare — the home currency + // "Total LKR 3,783.20" ISO code, space-separated + // "Total A$54.87" symbol-prefixed: A$, NZ$, US$, S$, HK$, C$ + // "Total ₹1,240.00" a bare symbol + // Only the first two were handled. The prefixed form is not exotic: it is + // what Uber sends for ordinary Australian orders, so 176 of 550 captured + // messages — most of them 2024-2025, i.e. current mail rather than legacy + // templates — failed with "no Total found" while the amount sat in plain + // sight in the body. `A$` misses `[A-Z]{3}` by a character. + // // The [Family] orders are placed for family in Sri Lanka and are priced in // LKR — reading those as dollars would inflate them ~200x, which is a large // part of why they must not reach a budget untagged. - const m = text.match(/(?:New Total|Total)\s*(?:([A-Z]{3})\s*)?\$?\s*([\d,]+\.\d{2})/); + const m = text.match( + /(?:New Total|Total)\s*(?:([A-Z]{3})\s*)?(?:([A-Z]{1,2})?\$|([₹€£]))?\s*([\d,]+\.\d{2})/ + ); if (!m) throw new OrderParseError("no Total found", meta.messageId); totals = { subtotal: extractLabelled(text, "Item subtotal"), @@ -491,9 +545,17 @@ export function parseOrderHTML(html: string, meta: MessageMeta): ParsedOrder { service_fee: extractLabelled(text, "Service Fee"), tip: null, discounts: null, - total_charged: money(m[2]), + total_charged: money(m[4]), }; - if (m[1]) explicitCurrency = m[1].toUpperCase().replace(/\$$/, ""); + // A symbol is only evidence of currency when it is qualified. A bare "$" + // stays unset so the body-wide scan below still gets its say — the receipt + // often names the currency elsewhere, and guessing AUD here would overrule + // it. + explicitCurrency = + (m[1] && m[1].toUpperCase()) || + (m[2] && SYMBOL_PREFIX_CURRENCY[m[2].toUpperCase()]) || + (m[3] && SYMBOL_CURRENCY[m[3]]) || + explicitCurrency; } // ---- payment ------------------------------------------------------------- @@ -584,7 +646,16 @@ export function validateOrderTotals( } // Cross-check the header total against the table total where both exist. - if (html && order.platform === "doordash") { + // + // Skipped for a credits-funded order: both stated totals are $0.00 there and + // agree with each other, but the recorded amount is deliberately the + // subtotal, so the check would reject every one of them for "disagreeing" + // with a figure the parser overrode on purpose. + if ( + html && + order.platform === "doordash" && + !order.flags.includes("credits_funded_zero_charge") + ) { const header = collapse(decodeEntities(stripTags(html))).match( /Total:\s*\$?([\d,]+\.\d{2})/i );