Billing and payouts
Two flows of money are covered here: what a brand pays for the platform, and what a brand pays its creators and affiliates directly. This page explains both, for the person running a brand's program and for the partner being paid, and closes with what the person hosting the install needs to set up.
What this covers
- Brand → platform. Every store has a plan. Paid plans are billed monthly to a card; every plan pays a percentage of attributed sales. A brand manages this under Settings → Billing.
- Brand → creators and affiliates. The brand pays approved commission directly: automatically from its own PayPal account, or by bank transfer or another method with export files, and every payout is recorded. Partnely never holds that money and takes no fee on it. A brand pays under Payouts; a partner tells brands where to pay them under Profile & payout → Payouts.
- The payment provider for billing is Stripe. Card and bank debit details are collected on its hosted pages and never pass through Partnely.
What a month costs
A month on a plan is the plan price plus a percentage of the order subtotal (what the shopper paid for products after discounts, excluding VAT, sales tax and shipping) of every order attributed to a creator and approved in the period. Orders nobody sent cost nothing. The percentage is on the sale, never on the creator's commission.
| Plan | Monthly price | On attributed sales |
|---|---|---|
| Free | $0 a month | 4% |
| Essential | $29 a month | 2.9% |
| Growth | $199 a month | 1.99% |
| Enterprise | Custom, invoiced | Negotiated |
The fee on each order is calculated when the order is recorded, at the store's plan rate that day, in the store's currency (an order placed in another currency is converted first, see Currency), and shown on the Overview and under Settings → Billing as the month runs. That rate stays with the order, also when the order is edited in the store later. It is invoiced when the order is approved: an invoice covers the fees of orders approved in its period. Approved orders are final, so a refund, cancellation or chargeback after approval changes neither the fee nor the commission. An order reversed while it is pending is never billed.
On a paid plan the invoice is issued by the subscription each period: the plan price, plus one line for the period's fees. On Free there is no monthly price and no subscription; once a card is on file, the month's fees are invoiced on the 1st by the scheduler described below. Until a card is on file the fees accrue and show under Billing.
Each fee line starts where the previous one stopped, so a store that moves from Free to a paid plan mid-month, or back at the end of a period, has no days billed twice and none skipped. Plan prices are set in US dollars and fees are in the store's own currency. An invoice can hold amounts in one currency only, so a store selling in another currency (DKK or EUR, say) pays the plan on its subscription invoice in dollars and its fees on a separate invoice in its own currency, charged to the same card. The same happens when the fee line arrives after the subscription invoice was finalized.
Enterprise stores are invoiced on agreed terms at a negotiated percentage; nothing on the Billing page charges them.
The trial
A store's first paid plan starts with a 14-day trial: no charge and no fees on orders while it runs, and no card needed to start it. The subscription starts on the provider's checkout page, which asks for a card only when no trial days are left; a card added under Settings → Billing before the trial ends is first charged when it ends. A trial that ends without a card, or without a subscription, moves the store to Free. A store gets one trial; moving to Free and back, or between paid plans, does not start another. A store that picked a paid plan when it signed up starts the subscription under Settings → Billing, and the days left of its trial carry over. Free has no trial because it has nothing to run out.
Changing plans
- Free to a paid plan. The subscription starts on the checkout page, with the trial if it never had one; the card is asked for there when no trial is left, and otherwise added under Billing before the trial ends. The plan on the Billing page changes when the provider confirms the checkout, usually within a minute.
- Between paid plans. Immediate. The difference for the rest of the period is prorated: moving up adds the unused part of the higher price to the next invoice, moving down credits it.
- To Free. The paid plan runs to the end of the period already paid for, and the store is on Free from then. The Billing page shows the date, and picking the paid plan again before that date keeps it running.
- Limits. Every plan carries limits on active creators, tracked orders and emails; Billing shows this month's numbers against them. Passing a limit never stops tracking: the store is asked to move up a plan before the next invoice.
How creators and affiliates get paid
Commission moves through one ledger both sides read: pending when the order arrives, approved when the brand confirms it (by hand, or automatically after the store's review window), then paid. A payout groups a creator's approved, unpaid commission. The brand makes it automatically through its own PayPal (see Paying creators with PayPal) or by hand.
Where creators are paid
A creator gives one payout method under Profile & payout → Payouts, with the details it needs: PayPal (an email address, or the PayPal account itself when the install offers Log in with PayPal), a bank account (an IBAN, or the account's local details in Denmark, the United Kingdom and the United States, or an account number with a SWIFT/BIC code elsewhere, with the holder's town and country), Wise, Venmo, or another method described in a line. The details are stored encrypted. Lists show only a masked label such as “IBAN ·· 6243”; the brand opens the full details on a separate page, only for a creator it owes approved commission, and every view is recorded. When a creator changes their details they are emailed, the brand sees “Details changed” next to them for a week, and automatic PayPal payouts to that creator wait 48 hours.
By hand
The brand pays through whatever it already uses and records the payout under Payouts with a reference. Recording it marks the orders paid on the creator's ledger too. Under Payouts → Export files, files cover what is owed and ready now (at least the minimum), one currency per file: a PayPal Payouts file for brands that do not connect PayPal (not offered, and its download refused, while PayPal is connected, because the connected PayPal pays the same creators), a Wise batch file (at most 1,000 transfers, or 100 for accounts whose payments need approval; check its columns against the template in Wise before uploading), a SEPA credit transfer file (pain.001.001.09, or .03 for banks that still take it) for euro payouts to an IBAN, and a CSV with every field for any other bank. For the SEPA file the brand types the account it pays from (name, IBAN, an optional BIC, which some banks such as Nordea require, and the payment date); it is used for that file only and never stored. A creator whose PayPal or Wise email would have to be changed to fit a spreadsheet cell is left out of those files and appears only in the CSV with every field. Each export is recorded for every creator in it. After paying, tick the creators under Owed now and record one reference for all of them; each is recorded for the amount the page showed, and if more commission was approved since, the brand is asked to reload and record again.
- Minimum payout: $25.00 by default, adjustable per store between $1.00 and $1,000.00. Smaller balances wait for the next payout.
- Payout schedule: the brand picks one under Payouts (monthly on the 1st, twice a month on the 1st and the 15th, or no fixed schedule) and pays in line with it, automatically through its PayPal or by hand. The schedule and the minimum are written into the program agreement every creator signs.
- Currency: commission, the minimum payout and payouts are in the store's currency. Once the store's currency is confirmed, an order placed in another currency is converted into it when the order is recorded, at the ECB euro reference rate published on or before the order's date, and that rate is fixed for the order. An order in a currency with no ECB rate from the seven days before its date is kept in its own currency until a rate is stored, and cannot be approved until then.
- Terms at the order's date: the rate, the attribution window, renewals, gift cards and fixed commission on orders worth nothing are the program's terms in force when the order was placed, whenever the order is recorded. A changed payout schedule or minimum applies to payouts due on or after the day the change takes effect.
- Approved commission is final: an order can be reversed only while it is pending. Once the brand approves it, by hand or automatically after the number of days it chose, a later refund or cancellation in the store changes nothing: the creator keeps the commission and nothing is taken off a later payout. The platform fee on an approved order is final too.
- What each side sees: the brand sees every payout in its history, recorded by hand or sent through its PayPal; the creator sees every payout received, with the brand, the amount, the method and the reference.
- Campaign fees: a flat fee set on a campaign is not part of a payout, which covers commission only. The brand pays it to the creator directly, and it does not show under the creator's Earnings.
Paying creators with PayPal
A brand can connect its own PayPal business account under Payouts → Pay creators with PayPal, on every plan. Creators who gave a PayPal account are then paid from the brand's PayPal balance, automatically on the payout schedule or when the brand presses Pay ready creators now. The money moves from the brand's PayPal to the creator's; Partnely sends the request and never holds the money, and takes no fee on it. PayPal charges the brand its own fee for each payout, shown on the payout.
Before connecting
- Use a PayPal Business account and ask PayPal to switch on Payouts for it, at paypal.com/payoutsweb/landing or in the developer dashboard under My Account → Payouts. PayPal may review the account first.
- At developer.paypal.com, switch to Live, open Apps & Credentials and create an app of the type Merchant. An app just for Partnely is the easiest to recognise later.
- Paste the app's Client ID and Secret into the panel under Payouts, with the mode Live. Sandbox is for testing with PayPal's sandbox accounts, where no real money moves. A Sandbox connection does not pay creators on a live install.
- Keep enough PayPal balance in the program's currency to cover what is owed on a payout day. PayPal pays payouts from the balance, not from a card.
What happens on saving
- The Client ID and Secret are checked with PayPal. If PayPal does not accept them, nothing is saved and the panel says so.
- If the app is not allowed to send payouts yet, the connection is saved with the warning “Payouts isn't switched on for this PayPal app yet.” Once PayPal switches Payouts on, save the connection again.
- A webhook is registered on the brand's PayPal app automatically, for the payout batch and payout item events, pointing to
https://partnely.app/api/webhooks/paypal/<store id>. Nothing needs to be set up in PayPal for it. When the registration fails (an install that PayPal cannot reach, say), the connection is saved anyway and statuses are checked once a day and whenever the brand presses Refresh status. - The Secret is stored encrypted and never shown again. Disconnecting, or connecting another PayPal app, waits until no PayPal batch is still open or held; it then removes the webhook from PayPal where it can and deletes the stored credentials.
Who is paid, and when
- On the schedule: with “Pay automatically on the schedule” switched on, the daily scheduler pays on the schedule's days, the 1st, or the 1st and the 15th. A store without a fixed schedule is paid only when the brand presses Pay ready creators now.
- Ready: a creator whose payout method is PayPal and whose approved, unpaid commission, in a currency PayPal supports for payouts, reaches the store's minimum. Creators who left the program or were paused are still paid what was approved. Each currency is sent as its own batch.
- Held: a creator whose payout details changed in the last 48 hours waits for the next payout, and the row says so.
- Not paid through PayPal: creators without a PayPal account stay under Owed now, for the brand to pay by hand or with an export file. A store that is restricted or whose owner's account is closed is skipped. A connection shows an error when PayPal refuses the app or the account itself (the credentials, Payouts access, a restricted or unverified account); payouts on the schedule then wait until a payout goes through or the connection is saved again. A refusal for the balance, the currency or a limit does not stop the schedule: the next payout day tries again.
- Never twice: the payouts are claimed before anything is sent, so a double click, or the scheduler and a click at the same moment, cannot pay the same orders twice. A store has at most one open PayPal batch per currency.
Statuses
A batch is sending until PayPal accepts it. A network error or an error on PayPal's side keeps it sending, and it is sent again with the same ids for up to 25 days, which PayPal pays only once. A batch still not confirmed after that is never sent again; it is held, with its payouts still in progress, for the brand to check in PayPal. Under Payouts the brand then enters the batch id PayPal shows for it, which reads the batch from PayPal like any other, or confirms that PayPal has no such batch, which owes its orders again. When PayPal refuses the batch as a whole (not enough balance in the currency, Payouts not switched on, a restricted PayPal account), it fails at once: every payout in it is marked failed with the reason, its orders are owed again, the connection shows the error when the fix is in PayPal itself, and the store's owner is emailed, at most once a day. When PayPal refuses a creator's PayPal email, only that creator's payout fails and the others are sent at once; that creator is not sent again until they save their payout details. Once PayPal accepts a batch, each payout follows PayPal's status for it:
| PayPal status | What it means | On the ledger |
|---|---|---|
PENDING | PayPal is processing it. | Processing |
ONHOLD | PayPal is reviewing it. | Processing |
UNCLAIMED | The creator has no PayPal account for that address yet, or has not confirmed it. PayPal returns the money to the brand after 30 days. | Processing, waiting for the creator to claim it |
SUCCESS | Credited to the creator's PayPal account. | Paid, with PayPal's transaction id as the reference |
RETURNED | Not claimed within 30 days, or canceled. The money is back in the brand's PayPal. | Failed; the orders are owed again |
FAILED | Not sent. Nothing left the brand's balance. | Failed with PayPal's reason; the orders are owed again |
BLOCKED | PayPal blocked the payment. | Failed; the orders are owed again |
REFUNDED | The creator sent the money back. | Failed; the orders are owed again |
REVERSED | PayPal reversed the payment. | Failed; the orders are owed again |
A batch is complete when none of its payouts is pending, on hold or unclaimed. Statuses arrive through the webhook within minutes, and the daily scheduler and Refresh status check every open batch as well; a status applied twice changes nothing.
How the money is protected
- No card data on the platform. Cards and bank debit details are entered on the provider's pages; Partnely stores a label and the provider's ids.
- Signed, idempotent webhooks. Every message from the provider is verified against the endpoint's signing secret and recorded by its event id, so a redelivered event changes nothing twice and a failed one is retried. PayPal's notifications are checked with PayPal before anything is applied, and applying one twice changes nothing.
- Payout details are encrypted. Creators' payout details and a brand's PayPal secret are stored encrypted. A brand sees a creator's full details only while it owes that creator approved commission, and every view and export is recorded; everywhere else it sees a masked label.
- Approval comes first. Orders sit in the store's review window before they are approved, and orders that raised a signal (a self-referral, a repeat customer, an order seconds after the click) are stamped for a look. Only approved orders are ever paid out or billed.
- Approved means locked. An order can be reversed only while it is pending, which takes it off the creator's balance and the brand's invoice. Once approved, by hand or after the store's chosen number of days, the commission and the fee are final.
- Keys are hashed. A store's API key is stored as a hash and shown once; webhook secrets are per store.
When a card fails
If an invoice cannot be collected the store is marked past due on the Billing page and the provider retries the card over the following days. Payouts through the brand's PayPal and by hand are unaffected. Once an invoice is paid the store is active again.
If the invoice is still unpaid 14 days after it was issued, the store is restricted: its tracked links stop sending visitors to the store and no new creators or affiliates can join the program, and automatic PayPal payouts wait until the restriction lifts. The same applies to a store without a card whose platform fees not yet billed reach about $25.00 (for example €25.00 or DKK 175.00): it is asked for a card, and restricted if none is added within 14 days. The store's owner is emailed when a card is asked for, 3 days before a restriction and when it starts, and the dashboard says what to do. Orders are tracked throughout. A card added is billed the fees owed at once, and the restriction lifts as soon as what is owed is paid.
The daily scheduler
Several things happen on a calendar: the day's ECB exchange rates, with pending orders that were waiting for one converted, orders approved automatically after a store's review window, PayPal payouts (on the 1st, or the 1st and the 15th), status checks of PayPal payouts still open, trials that ended without a subscription, the monthly fee invoice for stores without a subscription (from the 1st; a store whose invoice could not be created that day is tried again on the next run), and card requests and restrictions. The app has no background worker; one script does all of it and is meant to be run once a day by whatever scheduler the host offers (cron, a platform job), on the machine that holds the database, with the website's settings and the operator's time zone. It is safe to run more often: a PayPal batch PayPal has accepted is never sent again, one it has not confirmed is sent again with the same ids for at most 25 days, and a period that is already invoiced is skipped.
TZ=Europe/Copenhagen node scripts/run-payouts.mjs
It reads DATABASE_URL and the Stripe keys from the environment (a .env in the project is read too). It exits with code 1 when anything failed (an invoice, a job) and posts the failures as JSON to ALERT_WEBHOOK_URL when that is set. In production without a Stripe key it refuses and exits 1. Elsewhere without a key it still runs: local invoice rows are written, which is how a development database gets its invoices.
Setting it up
For whoever hosts the install. Everything is optional; without the keys the Billing page says billing is not connected and plan changes are recorded without a charge.
- Stripe Dashboard → Developers → API keys: the secret key goes in
STRIPE_SECRET_KEY. - Product catalog: create the two paid plans as recurring monthly prices with the amounts in
src/lib/pricing.ts, and put their ids inSTRIPE_PRICE_ESSENTIALandSTRIPE_PRICE_GROWTH. - Developers → Webhooks: add the URL below as an endpoint for events on the platform account, subscribed to exactly these events:
checkout.session.completed,customer.subscription.created,customer.subscription.updated,customer.subscription.deleted,customer.updated,invoice.created,invoice.finalized,invoice.updated,invoice.paid,invoice.payment_succeeded,invoice.payment_failed,invoice.voided,invoice.marked_uncollectible,payment_method.attached,charge.dispute.created,charge.dispute.closed. Its signing secret goes inSTRIPE_WEBHOOK_SECRET. - Customer portal: enable it under Settings → Billing → Customer portal so “Add or update card” can open it; with tax on, also let customers update their billing address and tax ID there.
- Tax (optional, off until set): activate Stripe Tax, add the registrations, set a tax behaviour (exclusive) on the two prices, and set
STRIPE_TAX=on. Checkout then asks for the billing address and VAT number, and subscription and fee invoices are taxed.
https://partnely.app/api/billing/webhook
STRIPE_SECRET_KEY= STRIPE_WEBHOOK_SECRET= STRIPE_PRICE_ESSENTIAL= STRIPE_PRICE_GROWTH= STRIPE_TAX=off ALERT_WEBHOOK_URL=
A note on timing: when a subscription invoice is created, Stripe waits about an hour before finalizing it. Partnely uses that window to add the period's fee line, so the endpoint must be reachable when the invoice is created; a missed event is retried by Stripe and applied on arrival.
Log in with PayPal
Optional. Creators can always type their PayPal email address. With Log in with PayPal set up, they can also connect their PayPal account, which confirms the address and gives brands the PayPal account id to pay. It runs on the operator's own PayPal app, not on a brand's.
- On the operator's PayPal business account, at developer.paypal.com → Apps & Credentials, create an app: Live for production, Sandbox for testing.
- In the app, under Other features, tick Log in with PayPal and open Advanced settings.
- Set the Return URL to the address below.
- Tick the attributes Email, Account verification status and PayPal account ID (payer ID). Nothing else is needed.
- Enter the privacy policy URL
https://partnely.app/legal/privacyand the user agreement URLhttps://partnely.app/legal/terms. - Save. PayPal reviews a Live app before it can be used, which usually takes a few weeks; a Sandbox app needs no review. Leave the variables unset in production until the review has passed.
- Put the app's Client ID and Secret in
PAYPAL_LOGIN_CLIENT_IDandPAYPAL_LOGIN_CLIENT_SECRET, andliveorsandboxinPAYPAL_LOGIN_MODE. Without them the Connect PayPal button is hidden and creators type their email address instead. Set all three or none; the server warns when only some are set.
https://partnely.app/api/payouts/paypal/connect/callback
PAYPAL_LOGIN_CLIENT_ID= PAYPAL_LOGIN_CLIENT_SECRET= PAYPAL_LOGIN_MODE=live
Brands' own PayPal apps need no settings on the install: each brand saves its credentials under Payouts, and its webhook is registered when it does.