In a marketplace, the question of who brought the customer in has a financial answer. Some traffic comes from the platform's own SEO, ads, and social media. The rest is brought in by sellers: from Instagram, their own site, a trade fair, a business card, or a QR code tucked into a parcel. If the commission is identical either way, a seller has no reason to send their own traffic to the marketplace.
At Artovnia I solved this with Share & Save. When a seller brings in the customer and the purchase is correctly attributed to their link, the commission drops from 18% to 13% + VAT. From the outside it looks like a simple affiliate scheme, but in a store with many sellers you have to solve click attribution, splitting the cart between sellers, dynamic commission calculation, protection against reusing the same click, race conditions, idempotency, retries after failures, auditing, and the effect of the adjustment on payouts and invoices. The whole thing runs on Medusa.js v2, Next.js, and Artovnia's own marketplace layer.
What Share & Save is, and what it is not
Share & Save is not a discount code for the customer. The product price stays the same, and the benefit goes to the seller, because the seller did the work of acquiring the buyer. The second rule matters just as much: one attributed click lowers the commission on exactly one order. The mechanism does not unlock a week of lower rates or a loyalty tier, it rewards a specific sale.
The reduction is expressed in percentage points:
const STANDARD_COMMISSION_RATE = 18 // %
const SHARE_SAVE_COMMISSION_RATE = 13 // %
const COMMISSION_REDUCTION_POINTS = 5 // ppThere is also a minimum allowed commission rate, so Share & Save coexists with other mechanisms without dropping below the floor the business model accepts. The number of reduction points, the minimum commission, the length of the attribution window, and the program status are all parameters an admin changes without a deployment.
Medusa.js gives you a foundation, not a finished marketplace
Medusa.js gives you products, carts, orders, workflows, events, modules, custom APIs, and an extensible admin panel. Per-seller commissions, seller payouts, split orders, referral programs, and sales attribution are domain logic specific to a given platform, and you write them yourself.
At Artovnia the base commission calculation is one stage of the process, and Share & Save runs later as an additional finalizer that modifies the already calculated commission on a specific order line. I did not build a second, parallel settlement system next to the existing one, so payouts and invoices never need to know why a commission is 13% instead of 18%. They get a single final value.
Step 1: the seller's signed link
Every seller can share a link to their profile or to a specific product, in the form /sklep/{seller-handle}. The same address powers the link copied from the panel, the QR code, print materials, and social media posts. When a real user arrives through it, the storefront creates a unique click identifier.
A plain URL parameter like ?seller=123 is no basis for a financial decision, because the user can change it at will. So the click is signed:
type AttributionPayload = {
sellerId: string
clickId: string
issuedAt: number
source: string
}
// the signed payload lands in an HttpOnly cookie
const cookieValue = sign(payload, process.env.ATTRIBUTION_SECRET)UTM parameters can ride along, but they serve analytics only. UTMs do not decide commissions, because analytics can be blocked or modified, and a financial process needs a stronger source of truth.

Click to zoom
Preview bots break a plain redirect
A link shared on Facebook, LinkedIn, or a messenger is usually visited first by a crawler fetching the title, description, og:image, og:url, and the rest of the Open Graph metadata. If that bot received a plain redirect to the canonical product URL, the social platform could start sharing the destination address instead, and the attribution would go with it. The link would pass manual testing and fail exactly where it is used most.
Preview bots therefore get HTML with the right Open Graph metadata while the Share & Save URL stays the address circulating on social media. Only a real user triggers the actual click attribution.
Step 2: attribution reaches the Medusa cart
A browser cookie is not enough, the click information has to reach the backend. The storefront syncs the signed attribution to the cart through a dedicated Store API endpoint and stores it in the cart metadata, at several points in the buying process, including right before checkout completion.
That does not turn cart.metadata into trusted data. The client controls the request to the API, so metadata can be tampered with. The storefront is responsible only for transporting context, the backend makes the decision about money. During order completion, Share & Save re-checks the signature, the seller identity, the click timestamp, whether the click was already used, the commission type, the minimum rate, and whether this is the seller's own purchase. Only after the full validation can the commission be reduced.
Step 3: a cart with many sellers
In a regular store you can say an order came from campaign X. In a marketplace the customer arrives through seller A's link and then, in one session, buys a product from seller A, a product from seller B, and a product from seller C. Lowering the commission for all three would make no sense, because only seller A brought the customer in.
Share & Save therefore does not act on the cart as a whole. It covers only the line items of the seller tied to the click, while the other makers settle under their normal terms. A cart with many sellers is split into separate orders at Artovnia. The Share & Save context travels with the order, but the finalizer still verifies that the current order belongs to the right seller. If it does not, the commission stays unchanged.
Step 4: one click, one order
Two near-simultaneous orders can reference the same clickId. Both ask the database at the same moment whether the click has been used, both get a negative answer, and both grant the lower commission. The "one click, one discounted order" rule stops holding, because there is room for a race condition between the read and the write:
// not enough: the read and the write are two separate operations
const used = await clickService.isUsed(clickId)
if (!used) {
await commissionService.applyShareSave(orderLineId, clickId)
}The click has to be claimed atomically. At Artovnia that means a claim with a unique key derived from the click identifier, which lets the database decide which process won:
try {
await clickClaimService.create({ clickId, orderId })
} catch (error) {
if (isUniqueViolation(error)) {
return skip("click_already_used")
}
throw error
}Only one order creates the claim, the other is told the click is spent. In financial logic that guarantee is worth more than assuming two requests probably will not arrive at the same time.
Step 5: recalculating the commission
Once every condition passes, the new rate is calculated roughly like this:
const newRate = Math.max(currentRate - reductionPoints, minimumRate)
// currentRate = 18
// reductionPoints = 5
// minimumRate = 13
// newRate = 13Share & Save works on the current commission, not on the seller's original rate. Commission promotions, a referral program, individual seller terms, or other financial adjustments may have already changed it. If each of those mechanisms calculated its own reduction from the starting value, the final result would quickly become unpredictable. The order of finalizers is explicit, and every adjustment works on the state left by the previous one.
Idempotency and retries
A commerce workflow does not run exactly once. It can be interrupted by a timeout, an application restart, an external service error, a dropped connection, a worker crash, or a job retry. Every Share & Save decision therefore gets its own idempotency key, and re-running the same process replays the earlier result instead of applying the reduction a second time:
const idempotencyKey = `share-save:${orderLineId}:${clickId}`
const existing = await decisionStore.get(idempotencyKey)
// a retry returns the same decision: 18% -> 13%, never 18% -> 13% -> 8%
if (existing) {
return existing
}When the commission finalizer fails, the order is not left in an unknown state, it goes into the retry process. The next run replays decisions that were already written correctly and resolves the missing pieces, and after a series of failed attempts the case is flagged for manual review. A transient failure does not turn into a financial error you later have to reconstruct by hand for the whole order.
Audit trail: where did 13% come from?
Showing Commission: 13% in the admin panel is not enough, because a few weeks later someone will ask why this order carries a different rate than the standard 18%. Every Share & Save decision goes into the audit log together with the click identifier, its source, the rate before and after the adjustment, the reduction amount, and the program parameters in force at the time.
I also record the cases where no reduction was applied, along with the reason:
type ShareSaveSkipReason =
| "unverified_click"
| "window_expired"
| "self_purchase"
| "click_already_used"
| "flat_rate"
| "rate_at_floor"
| "no_commission_line"An admin does not have to reconstruct events from application logs, because the system answers why a decision went the way it did. In financial logic, auditability weighs as much as getting the number right.
Payouts and invoices need no separate integration
Share & Save finishes its work on the shared commission model. A correctly adjusted final value is all that payouts, invoices, finance dashboards, and reporting need, and they read exactly the same data as before. There are no two parallel settlement worlds, one regular and one for Share & Save. There is a single final commission and an audit trail explaining where it came from.
A material generator for sellers
Attribution is worth little if the seller has no simple way to use it. The Artovnia seller panel has a section that generates the link, a QR code, print materials, and social media graphics. Sellers use them on Instagram, on Facebook, inside the parcel, at trade fairs, at a stand, or on business cards.
A conversation at a fair can turn into measurable online sales: the customer scans the QR code, passes through the Share & Save link, the system records the attribution, and if a valid purchase follows, the seller pays a lower commission.

Click to zoom
Configuration instead of values hardcoded in the codebase
An admin manages the program parameters from the panel:
interface ShareSaveConfig {
is_enabled: boolean
commission_reduction_points: number
min_commission_rate: number
attribution_window_days: number
}Changing the business model does not require editing code, cutting a release, and deploying the backend. Each order also keeps the parameters that applied at the moment of the decision, so moving from 5 pp to 4 pp does not recalculate old orders, and the audit trail still shows the rates actually used to settle them.
Last-click, and a deliberate no to cross-device
Share & Save uses a last-click model. If a customer visits seller A's link first and then opens a valid link from seller B, the current attribution becomes seller B. I do not keep a history of every touchpoint just to resolve a commission. An unambiguous rule gives sellers clear terms and is easier to audit than an elaborate marketing attribution model.
Devices work the same way. If a customer scans a QR code on their phone and later opens Artovnia on a laptop by themselves, the system does not link the two sessions, because the cookie stays on the device where the click happened. I did not implement fingerprinting or cross-device identity matching. More aggressive attribution is technically possible, its cost and complexity just did not match the value it would deliver.
Commission as its own domain
Early in a marketplace project, commission usually looks like this:
const commission = orderTotal * 0.18A few months into production you add per-seller rates, minimum commissions, promotions, referral programs, refunds and partial refunds, corrections, split orders, different tax bases, retries, and reconciliation. Commission stops being a percentage stored on the seller record and becomes a domain process of its own.
That is how I designed Share & Save. Not as an exception inside checkout
if (shareSave) {
commission = 13
}but as one more auditable rule operating on the shared commission system. Later extensions do not force a rewrite of the checkout logic.
What I used from Medusa.js
Share & Save is not a Medusa.js feature, it is custom Artovnia marketplace logic. The framework gave me the pieces to assemble it: the Store API and custom endpoints, cart and order metadata, workflow hooks, custom modules and data models, events, admin panel extensions, background jobs, and integration with the existing order system.
Responsibilities spread across several layers instead of piling into one enormous checkout handler. The storefront captures and carries the attribution, the backend verifies it, the commission system makes the financial decision, the audit trail records the outcome, retries handle recovery, and the remaining modules consume only the final value.
Is Medusa.js a good fit for a marketplace?
There is no ready "multi-vendor" switch in Medusa. What there is instead is an architecture you can build your own marketplace logic on, and that is where the framework shows its strengths. The hard part stops being the product catalog or checkout and becomes commissions, payouts, split orders, seller onboarding, refunds, moderation, reconciliation, integrations, and compliance.
Share & Save illustrates that well. To a seller it reads as "share a link and pay a lower commission". On the system side it looks like this:
// signed attribution -> storefront -> cart -> multi-vendor split
// -> order validation -> click claim -> commission adjustment
// -> audit trail -> payouts / invoicesThat second part decides whether the feature stays correct across thousands of orders, network failures, and further changes to the business model. If you are planning a marketplace on Medusa.js, it pays to answer early which processes set it apart from a regular store, and how to design them so they survive that growth.
Building a store or a marketplace on Medusa.js?
I design and ship e-commerce platforms on Medusa.js v2: from stores with a custom storefront to marketplaces for many sellers, along with commission and payout systems, custom workflows, and integrations with external systems. Artovnia is one of those builds.
Multi-vendor marketplace on Medusa.js

Click to zoom


