Commission Rules
Soryk's commission engine answers one question per order: how much, to whom? The answer is a function of (a) the rules you've defined, (b) the agent attribution on the order, and (c) any overrides you've recorded. Everything is computed on demand from paid Shopify orders — there's no separate "commissions ledger" to keep in sync.
The mental model
Five concepts, in order:
- Rules — definitions you write: scope, base, rate, priority. Stored as
soryk_commission_rulemetaobjects. - Earnings — computed at read-time by matching the most specific rule against each paid order. Never stored — always re-derivable.
- Payouts — when you mark earnings as paid (bulk action). Stored as
soryk_commission_payment. - Alerts — automatic flags when a refund hits an order whose commission was already paid.
- Overrides — manual corrections for ambiguous attributions. Take priority over everything.
Why earnings aren't stored. Computing on read means rule changes apply retroactively to unpaid orders, you can't have stale data, and the audit trail is the union of (rules + paid Shopify orders) — both of which you fully own.
Creating rules
Go to Admin → Commissions → Regole (Rules) → Create rule.
Scope
| Scope | Applies to | Typical use |
|---|---|---|
global | Every agent, every order | The baseline: "5% on everything". |
agent | One specific agent, all their orders | "Marco gets 7%, everyone else gets 5%." |
collection | Any agent, any order containing items in this collection | "Premium wines pay 8%, base catalog pays 5%." |
variant | Any agent, any order containing this exact SKU | SKU-level promotions or specific high-margin items. |
Base
What number to multiply the rate against.
total | Order total — includes everything (subtotal + tax + shipping − discounts). |
subtotal | Just the line items, before tax and shipping. |
no_shipping | Total minus shipping. |
no_tax_no_shipping | Total minus tax and shipping. Most common — agents shouldn't earn commission on tax revenue you forward to the government. |
Priority
An integer for tie-breaks. Higher wins. When two rules apply to the same order, the engine picks the one with the highest priority. If priorities tie, the more specific scope wins (variant > collection > agent > global).
Deduct on refund
If on, refunds against an already-paid commission raise an alert for you to resolve. If off, refunds are silently ignored and the agent keeps the commission.
How the engine calculates
For each paid Shopify order:
- Identify the agent. Lookup order:
soryk_commission_override→ tagsoryk_agent:<email>→ multi-agent fallback (companyId → agents map). - Find candidate rules whose scope matches (agent + collection/variant filters applied).
- Pick the rule with the highest priority. Ties resolved by scope specificity.
- Compute earning = base amount × rate %.
- Mark the earning as paid if it appears in any
soryk_commission_paymentfor this agent.
Performance. The engine pre-builds a companyId → agents[] index and an email → agent map (raw + sanitized) so the per-order work is O(1). Tested smoothly past 10,000 orders per shop.
Recording payouts
- Open Commissions → Da pagare (To pay).
- The list shows all unpaid earnings, grouped by agent.
- Multi-select the rows you want to pay (or use "Select all by agent").
- Click Paga commissioni (Pay commissions).
- In the modal: pick a method (
bank_transfer,cash,paypal,custom), add a note, set the date. - Save. A
soryk_commission_paymentis created and those orders flip to "paid" in the engine output.
To revoke: open Commissions → Pagate (Paid), click the payment, click Delete. The earnings revert to pending.
Idempotency. The bulk-pay endpoint refuses to create a duplicate payment with HTTP 409 if any of the orders are already in another payment for the same agent. This is intentional — it prevents accidental double-payment when two admins are clicking at the same time.
Refund alerts
When Shopify fires a refunds/create webhook on an order whose commission has already been paid, Soryk raises a soryk_commission_alert. You'll see a red banner on the admin home with a "Review →" link.
Resolve from Commissions → Alerts. Options:
- Resolve as deducted — you've recovered the amount in the next payout. Mark resolved, no further action.
- Resolve as ignored — refund was minor, not worth clawing back. Mark resolved with a note.
Alerts are deduplicated on (orderId, paymentId) so refund webhook re-deliveries don't create duplicates.
Manual overrides
For orders where attribution is wrong or ambiguous (e.g. two agents on one company, no explicit tag), use Fix attribution.
- On Da pagare, you'll see a banner: "N orders have ambiguous attribution".
- Click Fix attribution (N).
- For each row, pick the correct agent from the dropdown.
- Save. Each row creates a
soryk_commission_overridethat takes precedence over tags and fallback.
An override can also be deleted to fall back to the regular attribution logic.
Multi-agent fallback
When an order doesn't have a soryk_agent:<email> tag (e.g. placed via the customer app or imported from elsewhere), the engine looks up the order's company and checks how many agents are assigned:
- Zero assigned — no commission attributed. Order shows in the "unattributed" filter.
- One assigned — that agent gets the commission.
- Multiple assigned — order goes to the "ambiguous" pile and surfaces in the Fix attribution banner.
Manager override (Pro tier)
If an agent has a manager set up, the manager can earn an additional override % on top of the regular commission for that agent's orders. Defined per-manager in their profile (manager_override_percent).
Example: Marco's order pays 5% to Marco. His manager Laura has a 1% override. Engine emits two earning rows: 5% to Marco, 1% to Laura.
Online orders inclusion
By default, only agent-mediated draft orders count. To include orders placed directly through the online storefront (no agent involved), turn on commissionsOnOnlineOrders in Settings → Commissions. The multi-agent fallback then determines who earns.
CSV export
From Commissions, click Export CSV. Three datasets:
?dataset=rules— current rule definitions.?dataset=due— unpaid earnings, ready to pay.?dataset=paid— historical payments with order references.
Useful for accounting hand-off and external payout systems (e.g. cargando a un gestionale paghe).
Worked examples
Example A — Tiered by collection
Rule 1 (priority 1): global, no_tax_no_shipping, 5%, deduct_on_refund=true
Rule 2 (priority 10): collection "Premium Wines", no_tax_no_shipping, 8%, deduct_on_refund=true
Order with €1,000 in regular wine + €500 in Premium → Rule 2 wins (highest priority, scope match). Earning = €1,500 × 8% = €120.
Order with €1,000 in regular wine only → Rule 2 doesn't match. Rule 1 wins. Earning = €1,000 × 5% = €50.
Example B — Manager + agent split
Rule 1: agent "Marco", no_tax_no_shipping, 6%, priority 5
Marco's manager Laura has manager_override_percent = 1.5
Marco places a €2,000 order → Marco earns €120 (6%), Laura earns €30 (1.5% override).
Example C — Override on ambiguous order
An order arrives via customer app for company "Acme S.r.l." which has both Marco and Sara assigned. Engine puts it in the ambiguous pile. You open Fix attribution, pick Sara, save. From now on, this specific order pays Sara — even if rules change later.