Pre-read for the technical deep dive

The Digital Seller Portal, and what the Rewards Portal needs from the Ecommerce Core

Two pages before the twenty. What the work is, who sets which number, why the Rewards Portal is waiting on Ecommerce Core rather than the other way round, and the six things that are still open and need an engineering answer.

For Muneeb Meer, with Khawar Rasheed
Session: Friday 11 September, 20:30 to 21:30 WIB, one hour
Source: PRD Digital Seller Portal v0.5, 8 September. This page replaces none of it.

01The gap, in one screen

Habeeb Rahman opened the 8 September call with the whole problem in two sentences, and everything below follows from it.

"In LiveOps, Marthino has a UI where I can go ahead and edit that and configure that. But once the request is submitted, which system does it come to, and where does it get stored? We don't have that system as of today, and that is why this entire discussion came into picture."

The screen exists. The storage does not. So the near-term work is not a portal. It is a place to hold four commercial parameters, a write path for LiveOps, and a read path for everyone else.

TODAY LiveOps screen edit discount, markup, fee nothing no system stores it So the terms live in a spreadsheet, and the Rewards Portal keeps asking. TARGET, SETTLED 8 SEPTEMBER LiveOps the only writer WRITE E-Commerce Core single source of truth per tenant, per product READ Rewards Portal (caches) Client Catalog API Settlement Service 80% of revenue live client contract not yet built discount, markup, transaction fee, tenant split

Diagram 1. Four parameters, not three. The tenant revenue split joins discount, markup and transaction fee. Read is open to the consuming systems, write is LiveOps only, and inside LiveOps only Finance and certain roles. The Core does not model those roles. The Rewards Portal keeps its own copy and serves its API clients from it, so there is no runtime call into the Core.

What this removes

A platform-level answer would have needed roughly 300 pre-configured promo codes. Per tenant, per product removes all of them. The discounts in force today already sit in a spreadsheet held by the Online Catalogue sync team, so loading them is a migration task, not a discovery task.

02Three phases, and only the first one is urgent

The name says portal. The thing anybody is waiting on has no screens at all.

PHASE 0 / BLOCKING Configuration, no interface Store the four parameters in the Core Write API for LiveOps Read API for Rewards, Catalog, Settlement Product identity from the Catalog API Port the existing discount spreadsheet Builds zero screens PHASE 1 / THE PORTAL Onboard the first merchant Sign up, reusing Seller Portal onboarding List gift cards: single, bulk, API, MCP Merchant sets the discount only LiveOps approves and links the supplier Supplier uploads its own code batches Same codebase, second workflow PHASE 2 / LATER A business, not a listing tool Settlement and payout view Stock and code supply management Multiple users per merchant, with roles Performance view Volume-tiered slabs Waits on the Settlement Service

Diagram 2. Phase 0 is what unblocks the Rewards Portal and Jahez. Brian's commitment, in writing: "we can definitely set this without an interface for now to make it quicker and not be a blocker, the interface can be created later if needed." Phase 1 is real work Merit intends to do, and it is no longer work somebody else is waiting on.

It is not a new product. It is the Seller Portal codebase running a second workflow, backend and frontend, confirmed on 4 September. What is separate is the user experience and the data, not the system. A seller who does both gets a switch at the top, and the data stays apart on either side of that switch. Roughly half of the physical Seller Portal is shipping, packing, carriers and returns, and none of it applies here.

03Who sets which number, and at what level

This is the part that gets rebuilt wrong, because the first draft of the PRD had it wrong too.

TermWho sets itWhereLevel
DiscountThe merchantThis portalPer product
MarkupMeritPricing engine, via LiveOpsPer product
Transaction feeMeritPricing engine, via LiveOpsPer product, flat per transaction
Tenant revenue splitMeritLiveOpsPer tenant, never shown to the client
Price and availabilityThe merchantThis portalPer variant
activation_feeMeritPricing enginePer variant, locked as A11 on 29 June
WORKED EXAMPLE / 100 SAR AMAZON KSA, TENANT B, SUPPLIER C Customer pays 100 SAR Merit pays Supplier C 95 SAR 5% discount off face value Gross margin 5 SAR Tenant B, 50/50 split 2.50 SAR Merit keeps 2.50 SAR THE MARGIN FLOOR, TO BE CONFIRMED A supplier discount at or below 1% triggers a markup that brings Merit's margin to at least 2.5%. Every number here is configuration, not code.

Diagram 3. Brian's own example from the Doc, drawn. Note what the merchant never touches: markup, transaction fee and the tenant split are all Merit's, exactly as the physical Seller Portal already splits seller-side margin from marketplace-side markup.

If you read one thing twice, read this

Notes written before 8 September say the discount is set per variant, because a USD 100 Amazon card and a USD 50 one carry different rates. That was corrected in the call. Brian: "per tenant, per offer, offer as in per product." Habeeb: "Yes, per product, correct." Recorded as DEC-0329. Price and availability stay per variant; discount, markup and transaction fee are per product, and every denomination of one card shares them. A per-variant discount implementation is the wrong build, and the older note is still in circulation.

Store every term as a one-row tier table, not a bare number. Volume-tiered slabs are deferred to a later release, and Panda's "8 percent between 250k and 500k dollars, 9 percent above it" is the live case. Two things carry into Phase 0 anyway, because both are cheap now and a data migration later: SLB-EC-01, a flat 4 percent is a scheme with one tier from zero to infinity, and SLB-EC-02, a nullable scheme_id where null means flat. The settled approach: a scheme settles, it never reprices. Price and tax are fixed at order time, and a crossed threshold becomes a credit note.

04The Rewards Portal is waiting on us, not the reverse

This is the piece of context that changes how the backlog reads.

Rewards Portal separate front end, many tenants, ~80% of Merit revenue at lower margin ORDERS AND DELIVERY RUN THROUGH Ecommerce Solution / Core OMS, PIM, pricing engine, fulfillment Gift cards digital settings the Core does not model today VAT on the full pre-discount amount, two streams Currency exchange live feed, ~3 hourly, one standard across platforms

Diagram 4. Al Fursan does not use the Rewards Portal. So the Rewards Portal does not block Al Fursan; Ecommerce Core blocks the Rewards Portal. The three areas came out of the Habeeb discussions and they sit inside the same migration backlog. First draft of that backlog: 13 September, running to several hundred items.

Blocker

Multi-currency market

Named the single biggest blocker on 12 August and still open. The Rewards Portal does not need the marketplace concept, but it does need a global market, described as configuration rather than a build. Without it there are no multi-currency products at all.

Blocker

LiveOps, the last 20 percent

80 percent done on 19 August and still 80 percent done. The remainder is front end. Nothing that routes to LiveOps can be approved until it lands, activation requests included.

In flight

Product change notifications

New or removed product or brand, and changes to name, terms, description, denomination. Price and stock are explicitly out. Denominations are variants, and one event per change across the whole gift card catalogue will be noisy, so volume is a design input.

MGC stays, as a supplier. It remains the engine that fulfils gift cards because it is already connected to the aggregators and the providers. In the new world OMS triggers it per fulfilment rather than treating it as the system that fulfils orders. The retrofit matters now rather than after cutover, because Merit's digital gift card settings have to still work once MGC sits behind that boundary.

05Two taxes, and reading them as one is how teams get it backwards

Both wrong answers are common, and they are wrong in opposite directions.

STREAM 1 / NOT MERIT'S VAT inside the face value Tax on the goods the customer eventually buys with the card, charged by the retailer at redemption. Merit neither collects it nor remits it. STREAM 2 / MERIT'S, DECISION D4 VAT on Merit's margin Markup, activation fee, processing fee, handling fee. Local rate, in-country only: KSA 15%, UAE 5%, Egypt 14%, UK 20%. Never the face value. No VAT cross-border. WHAT THE INVOICE HAS TO DO Merit raises a ZATCA invoice and prints the breakdown on every line, including the zero. Habeeb: "Total price is 9,200. Product price is 8,000, VAT is 1,200. We show the breakup of this. If it is zero we show it as zero." An STC card carries a 15% line. An Apple card on the same screen carries none. The flag is real data, it varies per product, and clients already see it.

Diagram 5. Reading the two as one rule is how a team concludes either that gift cards are tax free, or that Merit owes 15 percent of face value.

What follows for the build. Every priced line carries a tax block per stream (VAT-EC-01): rate, jurisdiction, taxable base, computed amount, and the party who remits. It is written even when the amount is zero, because an absent line and a zero line are different things on an invoice. The merchant's input stays one all-inclusive price (VAT-SP-01) and Merit derives the breakdown. Never one blended number (VAT-EC-02), because the two streams are remitted by different parties under different registrations. Merit captures the seller's VAT registration status and TRN at onboarding, and a tax class per product: a seller who is not registered has no VAT to charge, and a blanket rate collects money nobody remits.

One rule to carry across from the marketplace side: VAT is calculated on the full pre-discount amount, not on the discounted amount. That fix is already in flight on marketplace with a sample invoice attached, and the same rule has to hold in the Core once the Rewards Portal runs through it.

Old world parity is a requirement, not a design choice. The five fee names stay unchanged, because clients parse them today: handling_fee, discount, processing_fee, activation_fee, delivery_fee. Both FIXED and PERCENTAGE on any line. Discount and markup are one signed field, never two, because they are the same adjustment in two directions and cannot both apply at once.

06What is open, and who has to answer it

Six items. Four of them need an engineering answer, which is why the session exists.

Open itemWhy it mattersAnswer sits with
Does the tenant revenue split share the grain? Discount, markup and transaction fee are per tenant, per product. The split is recorded as per tenant only. The slab discussion then put thresholds on it, which implies a product dimension nobody has stated. Brian, Habeeb
What does the Core API have to expose, and by when? The concrete ask on Muneeb's team: store and serve the four parameters per tenant, per product. Tracked as COM-0947, due 15 September. The existing discounts are already in a spreadsheet, so the data is a load. Muneeb and Khawar
What level does the pricing engine work at? The largest unpriced item in the PRD. Two suppliers of the same card carry different discounts, which is offer level. The engine applies at category or product level today. Either it grows an offer level, or Merit accepts one set of terms per product. Muneeb, Brian
Discount off face value, or a final price? The old world only ever took a final price from the supplier and did not care how it was reached. The PRD says the merchant sets a discount. Both cannot be the input. Muneeb, Brian, Habeeb
Can a merchant add a new product? If a merchant can only edit and remove what Merit already carries, the supplier reference problem disappears entirely, because Merit created every product and holds every reference. Habeeb, Brian
What does Phase 1 cost? No estimate exists, for any of the three new items. The codebase is reused, so the number should be smaller than the name suggests. Muneeb and Khawar
What the session does not do

It approves nothing, sets no dates, and resolves no handover recipient. It is an explanation. The one thing Brian wants out of it: that Muneeb can explain each piece to his own team without opening the PRD again.