Skip to content

Powered by Grav CMS + Helios Theme

Developers

Everything the affiliates add-on registers, in one place. It's for a developer who builds on the program, feeds it from a script or listens to what it does.

REST routes

Every route is under {api}/kahunacart/affiliates, where {api} is the API plugin's base, /api/v1 by default. Authentication, errors and paging work as described in REST API. Money is in minor units with a currency code beside it. Timestamps are unix seconds.

Reads take kahunacart.affiliates.view or kahunacart.affiliates.manage. Writes take manage. The payout routes take kahunacart.affiliates.payouts and the tax routes take kahunacart.affiliates.tax, and each is checked first.

Partners

Route Permission What it does
GET /kahunacart/affiliates view List partners. Filter with status, tracking, customer_id and q, which searches name, email and code. Applications are partners with ?status=applied.
POST /kahunacart/affiliates manage Add a partner, with an optional coupon in the same call.
GET /kahunacart/affiliates/{id} view One partner with their coupons, balances and link. It also carries old_codes (each with code, created_at and created_by, newest first), code_request (the request waiting, or null) and coupon_percent (the discount a coupon for a new code would copy, or null).
PATCH /kahunacart/affiliates/{id} manage Change a partner's details and rate overrides. A new code keeps the old one working. Send coupon: true with it to make a coupon for the new code too. The answer's coupon says whether one was made and, if not, why.
GET /kahunacart/affiliates/code-requests view Every request for a new code still waiting, oldest first, each with its partner's id, name, code, status and coupon_percent.
POST /kahunacart/affiliates/code-requests/{id}/approve manage Give the partner the code they asked for. Send coupon: true to make a coupon for it too. The code is checked again, and a code that was taken or now has a blocked word in it is refused.
POST /kahunacart/affiliates/code-requests/{id}/reject manage Turn the request down, with an optional message for the partner.
POST /kahunacart/affiliates/{id}/status manage Move a partner with action: approve, reject, pause, resume or close, and an optional message.
GET /kahunacart/affiliates/{id}/coupons view A partner's coupons.
POST /kahunacart/affiliates/{id}/coupons manage Give a partner a coupon with coupon_id, or make one with coupon_percent and coupon_code.
DELETE /kahunacart/affiliates/{id}/coupons/{couponId} manage Stop a coupon earning for the partner.
GET /kahunacart/affiliates/{id}/clicks view A partner's daily clicks.
GET /kahunacart/affiliates/clicks view The whole program's daily clicks.
GET /kahunacart/affiliates/orders/{orderId} view An order's referral.
PUT /kahunacart/affiliates/orders/{orderId} manage Credit the order to affiliate_id by hand.
DELETE /kahunacart/affiliates/orders/{orderId} manage Take the partner off the order.
GET /kahunacart/affiliates/attention any permission The counts the section shows and which permissions the caller holds.

Commissions and rates

Route Permission What it does
GET /kahunacart/affiliates/commissions view List commissions. Filter, sort, search and page.
GET /kahunacart/affiliates/commissions/{id} view One commission.
POST /kahunacart/affiliates/commissions/approve manage Approve commissions by ids.
POST /kahunacart/affiliates/commissions/reject manage Reject commissions by ids.
POST /kahunacart/affiliates/commissions/adjustments manage Add a signed adjustment with affiliate_id, amount_minor, currency and a note.
POST /kahunacart/affiliates/commissions/orders/{orderId} manage Credit a paid order whose partner was set by hand.
POST /kahunacart/affiliates/commissions/{id}/release manage Release a disputed commission.
GET /kahunacart/affiliates/balances view Approved and other balances for each partner and currency.
GET /kahunacart/affiliates/overview view The overview's numbers.
GET /kahunacart/affiliates/rates view List product and category rates. Filter with scope, scope_id and affiliate_id, where program means rates for everyone.
POST /kahunacart/affiliates/rates manage Add a rate with scope, scope_id, rate_type, rate_value and an optional affiliate_id.
PATCH /kahunacart/affiliates/rates/{id} manage Change rate_type and rate_value.
DELETE /kahunacart/affiliates/rates/{id} manage Remove a rate.

Payouts

Route What it does
GET /kahunacart/affiliates/payouts List batches.
POST /kahunacart/affiliates/payouts Create batches from method, currency and an exclude list. The preview is rebuilt on the server, and amounts are never taken from the caller.
POST /kahunacart/affiliates/payouts/preview Preview who would be paid, and the reason for everyone left out.
GET /kahunacart/affiliates/payouts/paypal/connection The last PayPal connection check.
POST /kahunacart/affiliates/payouts/paypal/connection Check the PayPal keys now.
POST /kahunacart/affiliates/payouts/paypal/poll Ask PayPal about every open batch.
GET /kahunacart/affiliates/payouts/{id} One batch with its items.
POST /kahunacart/affiliates/payouts/{id}/run Run a draft.
POST /kahunacart/affiliates/payouts/{id}/resume Finish a store credit batch that was cut off.
POST /kahunacart/affiliates/payouts/{id}/cancel Delete a draft.
POST /kahunacart/affiliates/payouts/{id}/paid Mark every open item of a manual batch paid, with an optional reference.
POST /kahunacart/affiliates/payouts/{id}/retry Make a new draft for failed and returned items.
POST /kahunacart/affiliates/payouts/{id}/poll Ask PayPal about one batch.
GET /kahunacart/affiliates/payouts/{id}/csv/{format} A file: paypal, wise or generic.
POST /kahunacart/affiliates/payouts/{id}/items/{itemId}/paid Mark one item paid. For a manual batch, or a PayPal batch whose outcome is unknown.
POST /kahunacart/affiliates/payouts/{id}/items/{itemId}/failed Mark one item not paid, and release its commissions.
GET /kahunacart/affiliates/{id}/payout-settings A partner's payout method, with bank details masked.
PUT /kahunacart/affiliates/{id}/payout-settings Change a partner's method, PayPal email and bank details.
GET /kahunacart/affiliates/{id}/payout-settings/details Reveal the bank details. Each call is written to the log with who asked.

Tax

Route What it does
GET /kahunacart/affiliates/tax/key Whether KAHUNACART_TAX_KEY is set, its fingerprint, and how many forms need filling in again.
POST /kahunacart/affiliates/tax/key/generate Make a new key and return its .env line once. Nothing is saved.
GET /kahunacart/affiliates/tax/report The year-end report for ?year=.
GET /kahunacart/affiliates/tax/report/iris The IRIS 1099-NEC CSV.
GET /kahunacart/affiliates/tax/report/full The full table CSV.
GET /kahunacart/affiliates/tax/report/statement/{id} A partner's statement PDF.
GET /kahunacart/affiliates/tax/exports The download history.
GET /kahunacart/affiliates/tax/profiles Every tax form, masked.
GET /kahunacart/affiliates/tax/profiles/{id} One partner's form, masked.
POST /kahunacart/affiliates/tax/profiles/{id}/verify Mark a form verified.
POST /kahunacart/affiliates/tax/profiles/{id}/request Ask the partner to fill the form in again, with an optional message.
GET /kahunacart/affiliates/tax/profiles/{id}/document The signed PDF, opened for the caller.

Creatives

Route Permission What it does
GET /kahunacart/affiliates/creatives view List banners, text and links.
POST /kahunacart/affiliates/creatives manage Add one with type, title, body, media and enabled.
POST /kahunacart/affiliates/creatives/order manage Set the order with a list of ids.
PATCH /kahunacart/affiliates/creatives/{id} manage Change one.
DELETE /kahunacart/affiliates/creatives/{id} manage Delete one.

The signed-in customer's routes

These are the routes the account tab uses. They're declared public to the API plugin, because a customer is never an API user. The API plugin attaches the signed-in session's customer and refuses the session as proof on a cross-site write, and every controller answers 401 to anyone who isn't a signed-in customer. Orders are shown by number and date only. The one exception is code-check, which answers anyone, because the public application form asks it too.

Route What it does
GET /kahunacart/affiliates/account/status The customer's partner status and code.
GET /kahunacart/affiliates/account/link Their referral link.
POST /kahunacart/affiliates/account/link/build A referral link to any page of the site.
GET /kahunacart/affiliates/account/coupons Their coupons and coupon links.
GET /kahunacart/affiliates/account/clicks Their daily clicks.
POST /kahunacart/affiliates/account/apply Apply to the program. An optional code is the code they want. A code that can't be had gets 422 with the sentence to show.
GET /kahunacart/affiliates/account/code-check Ask whether a code could be had, with ?code=. The answer has available, code (as it would be stored) and message (why not). Counted in the affiliate_code_check rate limit bucket, and over it the answer is 429.
GET /kahunacart/affiliates/account/code-request The partner's own code, their waiting request or null, and can_ask.
POST /kahunacart/affiliates/account/code-request Ask for a new code with code and an optional reason. One may wait at a time, and only an active partner can ask.
DELETE /kahunacart/affiliates/account/code-request Withdraw the waiting request. The answer's withdrawn says whether there was one.
GET /kahunacart/affiliates/account/commissions Their commissions.
GET /kahunacart/affiliates/account/totals Their totals for each currency.
GET /kahunacart/affiliates/account/payouts Their payouts.
GET /kahunacart/affiliates/account/payout-settings Their payout method, with bank details masked.
PUT /kahunacart/affiliates/account/payout-settings Change it.
GET /kahunacart/affiliates/account/tax Their tax form, masked.
POST /kahunacart/affiliates/account/tax Submit it, as JSON or multipart with the PDF.
GET /kahunacart/affiliates/account/tax/form Which form they need and its fields.

The add-on publishes no MCP tools and has no REST reference pages of its own yet.

Permissions

Permission Label
kahunacart.affiliates.view View Affiliates
kahunacart.affiliates.manage Manage Affiliates
kahunacart.affiliates.payouts Pay Affiliates
kahunacart.affiliates.tax See Affiliate Tax Details

Each stands alone. None implies another. See Affiliates.

Events

Each event is fired after the change is written, and a listener that throws can't undo it. Payloads are arrays.

Event Carries Fired when
onKahunaCartAffiliateApplied affiliate A customer applied.
onKahunaCartAffiliateStatusChanged affiliate, from, to, action, message, actor A partner was approved, rejected, paused, resumed or closed.
onKahunaCartAffiliateCodeChanged affiliate, old_code, new_code, actor, request, coupon A partner's code was changed, by your edit or an approved request, after the change has committed. request is the approved request, or null for an edit. coupon says what became of the coupon for the new code (made, code, percent and reason), or is null when none was asked for.
onKahunaCartAffiliateCodeRequested affiliate, request A partner asked for a new code.
onKahunaCartAffiliateCodeRequestRejected affiliate, request, message, actor You turned a request down.
onKahunaCartAffiliateCommissionCreated commission, affiliate, order A sale, renewal or adjustment was written. order is null for an adjustment.
onKahunaCartAffiliateCommissionApproved commission, affiliate, order A commission became approved.
onKahunaCartAffiliateCommissionReversed commission, affiliate, order, clawback, reduced_minor, why A refund or reversal took something back. why says which.
onKahunaCartAffiliatePayoutSent payout, item, affiliate A partner was paid.
onKahunaCartAffiliatePayoutFailed payout, item, affiliate An item failed or came back and its commissions returned to the balance. The item's error says why.
onKahunaCartAffiliatePayoutUnclaimed payout, item, affiliate, reminder, days_left A PayPal payout waits to be claimed. Fired when it first does, with reminder false, and once more a week before PayPal returns it, with reminder true.
onKahunaCartAffiliatePayoutProblem payout, kind, reason PayPal would not give the store a token, did not answer or could not take the batch, or the outcome of a batch is unknown and it now waits for you. kind is paypal or outcome_unknown, and reason is a sentence in plain words. It fires every time the runner meets the problem, so a listener that tells somebody should keep one message for each batch and kind.
onKahunaCartAffiliatePayoutBatchFinished payout, counts Every item in a batch has an outcome. counts is items for each status.
onKahunaCartAffiliateTaxSubmitted affiliate, profile, replaced A partner filled in or replaced their form.
onKahunaCartAffiliateTaxRequested affiliate, profile, message, actor, requested_at You asked for the form again.
onKahunaCartAffiliateTaxExpiring affiliate, profile, expires_at A W-8 expires within 60 days. Once for each form.
onKahunaCartAffiliateTaxExpired affiliate, profile, expires_at A W-8 was marked expired. Once for each form.

profile is always masked and never holds a full tax ID.

AffiliateService::changeCode() is the one way a code ever changes. It claims the new code and keeps the old one in a single transaction, then fires onKahunaCartAffiliateCodeChanged. It refuses to run inside a transaction that's already open, because the event, and the partner's email it sends, would go out for a change that might still roll back. It throws a LogicException there. Pass any write that belongs with the change as $alongside, and it runs inside the same transaction.

What the add-on listens to

Event What it does
onKahunaCartCheckoutValidate Stamps the visitor's link on the order. It adds no errors.
onKahunaCartOrderCompleted and onKahunaCartOrderPaid Credit a sale. A checkout that captures at once fires only the first.
onKahunaCartSubscriptionRenewed Credits a renewal.
onKahunaCartRefundApplied and onKahunaCartOrderReversed Take commissions back.
onKahunaCartDisputeOpened Holds an unpaid commission.
onKahunaCartDisputeClosed Follows the outcome: won lets the hold go, lost takes the commission back (a clawback when it was paid), and open, set by hand, holds it again. Needs KahunaCart 1.5.18.
onConsentRegisterServices and onConsentChanged Declare the kc_ref cookie as marketing, and drop it when consent is withdrawn.

Twig, shortcode and partial

Name What it does
kahunacart_affiliate_program() Draws the public program block. Use it in a theme template.
  • Commission 30% of each order you send us, after discounts and before tax and shipping
  • Cookie window 60 days: a visitor who buys within that time of clicking your link counts
  • When it's yours Each commission waits 30 days in case the order is refunded
  • Payouts By store credit or PayPal, once you're owed $50.00 or more. Take store credit and we add 20%.
Apply now No account needed. If you already have one, you can sign in to apply with it.
The same block as a shortcode. Needs the Shortcode Core plugin.
partials/kahunacart-affiliates/program.html.twig The partial both draw. Copy it into your theme at the same path to change it. It is given program, apply_url and stylesheets.

CLI

Command What it does
bin/plugin kahunacart-affiliates tax:status Show whether KAHUNACART_TAX_KEY is set and how partners' tax forms stand.
bin/plugin kahunacart-affiliates tax:rekey Encrypt signed forms stored before a key was set, or with --from=<old key> or --from-env=NAME move every encrypted tax detail from an old key to the current one.

See Tax details.

Jobs

Job Runs What it does
affiliates.release_holds About every 15 minutes Approves commissions whose holding period has ended.
affiliates.poll_paypal_payouts Every 10 minutes Sends unanswered PayPal requests again, reads open batches and sends unclaimed reminders.
affiliates.email When queued Sends one partner email.
affiliates.statements Once a month, in the first week Queues last month's statements.
affiliates.payouts_due Daily Sends Affiliate payouts due once a month, on the first day any partner is over the payout minimum. It starts a day after the worker first ticks.

The tax sweep that sends expiry emails runs inside the worker's tick about once every 23 hours.

Notifications

Code Audience
affiliates.application_received, affiliates.application_approved, affiliates.application_rejected, affiliates.commission_new, affiliates.payout_sent, affiliates.payout_failed, affiliates.payout_unclaimed, affiliates.tax_requested, affiliates.tax_expiring, affiliates.tax_expired, affiliates.statement, affiliates.code_changed, affiliates.code_request_rejected Partner
affiliates.owner_application, affiliates.owner_batch_finished, affiliates.owner_payouts_due, affiliates.owner_payout_problem, affiliates.owner_tax_form, affiliates.owner_commission, affiliates.owner_commission_reversed, affiliates.owner_code_request Owner

The owner rows and when each is sent are in The partner's side. The two commission rows register switched off (enabledByDefault: false, which needs KahunaCart 1.5.3). Each message has a dedupe key: affiliates.owner_payouts_due once per month in the store's time zone, affiliates.owner_payout_problem once per payout item and status or per batch and kind, affiliates.owner_tax_form once per form, the commission rows once per commission (a reversal once per amount taken back), and affiliates.owner_code_request once per request.

The add-on also opts two of its dashboard alerts in to being pushed (notify:): affiliates_paypal_payouts (high) and affiliates_tax_forms (normal). Core makes alert.affiliates_paypal_payouts and alert.affiliates_tax_forms rows for them. See Dashboard alerts → Pushing an alert to your phone.

Partner emails are templates in templates/emails/ named affiliate-*.html.twig and affiliate-*.txt.twig. See The partner's side.

Tables

The add-on adds its tables to KahunaCart's database through the base plugin's migrations, and a store reset empties all of them.

Table Holds
kahunacart_affiliates Partners.
kahunacart_affiliate_codes Every code each partner has had, the current one included, with code unique, so the database decides who has a code. Old codes are the rows that aren't a partner's current code.
kahunacart_affiliate_code_requests Partners' requests for a new code, with the code asked for, the reason and how it was decided.
kahunacart_affiliate_coupons Which coupon belongs to which partner.
kahunacart_affiliate_clicks The daily click counter for each partner.
kahunacart_affiliate_rates Product and category rates.
kahunacart_affiliate_referrals One row for each order that has a partner, or a removal.
kahunacart_affiliate_commissions Commissions, with a unique dedupe_key such as sale:<order id>.
kahunacart_affiliate_payouts Batches, each with a random ref.
kahunacart_affiliate_payout_items One row for each partner in a batch.
kahunacart_affiliate_tax_profiles Tax forms.
kahunacart_affiliate_tax_exports The download history.
kahunacart_affiliate_creatives Banners, text and links.

Export sets, merge and erasure

A store bundle carries the program in three sets, so the permission that guards each is the permission needed to take it out. See Export and restore.

Set Holds Permission
affiliates Partners, their codes and code requests, their coupons, rates and creatives. kahunacart.affiliates.manage
affiliates.commissions Referrals, commissions, batches and items, and click counters. kahunacart.affiliates.payouts
affiliates.tax Tax forms, without the secrets below. kahunacart.affiliates.tax

A bundle never carries a partner's bank details, the full tax ID, the foreign tax ID, the date of birth or the signed PDF. The last four digits and the rest of the form do travel. A restored form is marked Needs re-entry.

A merge matches a partner by code, a commission by its dedupe_key, a referral by its order and a batch by its ref. It never deletes, and it never overwrites the columns a set leaves out.

Codes are matched by the code alone, and a code is never moved from one partner to another. A code nobody has here is added for its partner. A code the same partner already has is left as it is. A code that belongs to another partner here stays theirs, is left out of the merge and is named in the report. A partner in the bundle whose code is an old code of a partner already here is matched to that partner, and keeps this store's current code. Code requests are matched by partner, code and the time they were made. After every import each partner's current code is claimed if it isn't yet, which also covers a bundle from a store that never kept old codes.

Merging two customers moves the partner record to the survivor. Erasing a customer is refused while a paid payout is inside the retention period, and afterwards the partner row is anonymised. See Customers.