00Read this first
Partner checkout builds on the platform-wide API admin and OAuth flows. It is written for external developers who cannot inspect the source code. Read these guides before implementing checkout:
Those guides explain how to obtain access tokens, configure scopes, and authenticate as an API admin reseller.
01What partner checkout does
Partner checkout lets an eligible reseller create and manage a billed-account signup on behalf of a new client. The API can:
- discover which products and add-ons are currently valid for the reseller
- list allowed billing countries and zone codes
- check whether an email, ABN, or NZ employer IRD number is already in use
- preview pricing and validation results before creating anything
- create the customer, address, company record, order, subscription, add-ons, totals, and post-pay record
- optionally send the client a welcome email with a password-reset link
- replay a previously successful execute request safely by idempotency key
- list, inspect, and cancel orders created by that reseller
- start a client on a free trial with no order and nothing to pay, then convert that trial into a paid subscription later (see "Free Trials")
02Base URL and authentication
- Base path:
/api - Auth header:
Authorization: Bearer <access_token> - Caller must be an API admin reseller in customer group
9
If the authenticated customer is not an API admin, or is not in group 9, the API returns 403.
03OAuth scopes
| Purpose | Scope |
|---|---|
| Product discovery | partner.checkout.preview or partner.checkout.write or partner.checkout.cancel |
| Availability checks | partner.checkout.preview or partner.checkout.write or partner.checkout.cancel |
| Zone discovery | partner.checkout.preview or partner.checkout.write or partner.checkout.cancel |
Preview order (dry_run=true) | partner.checkout.preview |
Execute order (dry_run=false) | partner.checkout.write |
| Cancel order | partner.checkout.cancel or partner.checkout.write |
| List or inspect past orders | Any partner checkout scope above |
| Create a free trial | partner.checkout.write |
| List trials | Any partner checkout scope above |
| Cancel a trial | partner.checkout.cancel or partner.checkout.write |
| List client renewals | Any partner checkout scope above |
| Change a client's renewal | partner.checkout.write |
| Look up a client | Any partner checkout scope above |
openid is still required on the OAuth authorization request. Legacy openapi remains accepted in the wider OAuth flow.
04Recommended integration sequence
GET/api/partner-checkout/options
GET/api/partner-checkout/zones
GET/api/partner-checkout/availability?email=...&abn=...
or ...&irdNumber=...
POST/api/partner-checkout/orders with dry_run=true
Same call again with the same business data, dry_run=false, and an Idempotency-Key
Persist the returned customer_id, order_id, and subscription_id
Use GET/api/partner-checkout/orders and GET/api/partner-checkout/orders/{order_id} for reconciliation and support
Use POST/api/partner-checkout/orders/cancel only when the reseller needs to cancel a qualifying order
To let the client evaluate the product before paying, start at "Free Trials" below instead of step 4, then come back to steps 4 and 5 with end_customer_id when the trial ends.
Do not hardcode product IDs, add-on IDs, pricing, or zone codes. Discover them dynamically.
05Country, identifier & address rules
Only Australia (AU) and New Zealand (NZ) are supported. Country is inferred from the company identifier:
Australia
- Send
company.abn
New Zealand
- Send
company.ird_number
- You must provide exactly one of
abnorird_number. billing_address.country_codeis not accepted in requests.billing_address.companyis not accepted in requests.customer.passwordis not accepted in requests.billing_address.zone_codemust match the inferred country.zone_codeis case-insensitive in the request and normalized to uppercase.- If a zone is invalid, the API returns
422and includes the valid codes for that country in the error detail.
06Endpoint reference
1. Discover products and pricing
Use this endpoint to discover the current subscription products, add-on products, pricing, required scopes, and reseller-specific mandatory requirements.
Important response areas:
subscription_products: valid base products fororder.product_idadd_on_products: optional or required add-onsmandatory_requirements.training_session_product_id: mandatory training session product expected during signupmandatory_requirements.training_session_product_name/mandatory_requirements.training_session_prices: display name and per-country (AU/NZ) retail pricing for the mandatory training session line, so the setup fee can be shown before previewing an order. These match the one-offtraining_sessionline returned by preview/execute (full retail price, no partner discount, RRP equal to its own price)mandatory_requirements.required_owned_add_on_keys: branded add-on families this reseller must include for new client signupsper_employee_minimum: minimum quantity for per-employee base productsshow_per_employee_pricing: whether this reseller can use per-employee plansprices: country-specific partner pricing and, where available, retail RRP comparison values
2. Discover zones
Use this endpoint to fetch the supported countries and valid zone_code values for billing addresses. The response currently includes AU and NZ. Use the returned zone.code values as billing_address.zone_code.
3. Check availability
Use this endpoint to check whether one or more proposed identifiers are already in use.
Query parameters: email, abn, irdNumber. You can send any combination of these in one request, but at least one must be provided.
Each result includes provided, normalized, valid_format, exists, available, message, required_owned_add_ons, has_all_required_owned_add_ons.
Normalization behaviour: emails are trimmed and lowercased; ABNs and IRD numbers are digit-normalized before validation.
4. Preview or execute an order
Use this endpoint to preview validation and pricing with dry_run=true, or to create the full customer and order with dry_run=false.
Headers: Authorization: Bearer <token>, Content-Type: application/json, Idempotency-Key: <unique value> required for dry_run=false.
Request body
| Field path | Type | Required | Notes |
|---|---|---|---|
dry_run | boolean | Yes | true validates and prices only. false creates records. |
end_customer_id | integer | No | Order for a client you already provisioned instead of creating a new one. Use this to convert a free trial. See "Ordering for an existing client". |
send_customer_welcome_email | boolean | No | Default false. Sends branded onboarding email with reset link when true. Ignored when end_customer_id is set. |
customer.first_name | string | Yes | 1-32 chars |
customer.last_name | string | Yes | 1-32 chars |
customer.email | Yes | Must be unique | |
customer.phone | string | Yes | 3-32 chars |
company.legal_name | string | Yes | 1-128 chars |
company.abn | string | Conditional | AU only. Exactly one of abn or ird_number must be sent. |
company.ird_number | string | Conditional | NZ only. Exactly one of abn or ird_number must be sent. |
billing_address.address_1 | string | Yes | 3-128 chars |
billing_address.address_2 | string | No | Max 128 chars |
billing_address.city | string | Yes | 2-128 chars |
billing_address.postcode | string | Yes | 2-10 chars |
billing_address.zone_code | string | Yes | Must be a valid zone for the inferred country |
order.product_id | integer | Yes | Must be a subscription product returned by /options |
order.add_on_product_ids | integer[] | No | Optional compatible add-ons; max 50 IDs |
order.extra_company_qty | integer | No | Additional company slots where supported |
order.per_employee_qty | integer | Conditional | Required for per-employee products; must meet reseller minimum |
order.add_free_trial_month | boolean | No | Default true. Adds one extra free month, so a monthly signup gets 2 months and an annual signup gets 13. Granted once per client: if they already had a free trial, it is not granted again. See "The free month is once per client". |
order.subscription_end_date | string (ISO date) | No | Optional aligned subscription expiry. Triggers prorated pricing on the base subscription and recurring add-ons. See "Subscription term alignment & prorating". |
order.client_reference | string | No | Reseller's own reference, max 255 chars |
order.metadata | object | No | Up to 50 keys and under 4 KB serialized |
oauth_onboarding | object | No | Opt in to single-flow OAuth onboarding. See "OAuth single-flow onboarding" below. |
oauth_onboarding.redirect_uri | string | Conditional | Required when oauth_onboarding is sent. Must exactly match a redirect URI registered on your OAuth client. |
oauth_onboarding.scope | string | No | Default openid payroll.write. Must include openid (legacy openapi accepted) and payroll.write. |
oauth_onboarding.state | string | No | Opaque value echoed back to your redirect_uri. Auto-generated if omitted. |
Business rules
- Duplicate customer email, ABN, and IRD values block checkout, unless they belong to the client named by
end_customer_id. - Unknown fields are rejected with
422. - Base
product_idmust be a subscription product, not an add-on. - Per-employee products are only allowed when the reseller is configured for them.
- Non-per-employee products reject
per_employee_qty. - Per-employee products reject
extra_company_qty. extra_company_qtymay be capped to the plan limit; when capped, a warning is returned.- Extra-company add-ons must not be passed directly in
add_on_product_ids; useextra_company_qty. - Every chosen add-on must match the base product's billing cycle.
- If the reseller owns required branded add-on integrations, the corresponding partner add-on products must be included.
- A mandatory training session product is always added to the order.
- When
subscription_end_dateis supplied, the base subscription and recurring add-ons are prorated; the training session is still charged at full price. See "Subscription term alignment & prorating".
Preview response
When dry_run=true, nothing is created. The response still contains the fully resolved pricing and address information:
mode = "preview"validated = truecustomer_id,address_id,order_id, andsubscription_idarenullorder_linesshows the base subscription, training session line, and any add-onstotalsshows subtotal, tax, total, and any welcome-offer promo discountwarningsdescribes non-fatal conditions such as capped extra-company quantity or the free trial monthfree_trial_month_appliedsays whether a free month was granted on this order
Execute response
When dry_run=false, the API creates: a new customer, a billing/shipping address, a company record with the ABN or employer IRD number, an order with payment_code = "partner_checkout_billed_account", order totals and order products, a subscription and any subscription add-ons, a post_pay record for billed-account invoicing, order history, optional promo assignment, and an optional subscription-trial record when the free extra month is used.
Important execute response fields: customer_id, address_id, order_id, subscription_id, order_status_id, emails_sent, idempotency_replayed.
The magic onboarding link (when oauth_onboarding is used) is never returned in the response. It is emailed only to the customer's verified address.
OAuth single-flow onboarding (oauth_onboarding)
Normally, provisioning a customer and getting an OAuth payroll.write token for them are two separate loops: you create the order, the customer sets a password from the welcome email, then they (or you) run the OAuth ceremony separately before you can create their payroll company.
Supplying an oauth_onboarding block collapses that into a single flow:
You send dry_run=false with oauth_onboarding.redirect_uri (and optionally scope / state).
The new customer is emailed a single-use, 24-hour magic link. Opening it signs them in without a password and drops them straight onto your OAuth consent screen.
With one click of consent, their browser is redirected to your redirect_uri with ?code=...&state=....
You exchange the code at POST /api/oauth/token for a payroll.write access token, then create their company with PUT /api/company/create.
No second login and no separate OAuth round-trip are required. After consent the customer is given the chance to set a password (or skip and do it later) before landing back on your callback.
Requirements and behaviour:
- You must have a registered OAuth client on your account (a 1-to-1 client owned by your api-admin customer). The
redirect_urimust exactly match one of its registered redirect URIs, andscopemust includepayroll.write. Adry_run=truerequest validates all of this without creating anything or sending email. - The magic link is emailed only to the customer's verified address and is never returned in the API response. Handing a partner a passwordless login link would bypass the consent this flow exists to capture.
- The link is single-use and expires after 24 hours. Re-provisioning (a new order) issues a fresh link.
oauth_onboardingreplaces the password-reset welcome email;send_customer_welcome_emailis ignored when it is supplied.
See also the OAuth Authentication Guide for the token exchange this flow feeds into.
5. Cancel an order
Use this endpoint to cancel an order previously created by the authenticated reseller.
- Reseller must own the order.
- Order must not already be cancelled.
- Cancellation is only allowed within 60 days of the created customer's
date_added. partner.checkout.cancelis preferred, butpartner.checkout.writeis also accepted.
6. Inspect one order
Returns the current status and history for one order created by the reseller, including totals and currency, customer and company details, subscription ID and expiry, cancel_window_closes, cancellable, and ascending history[].
7. List orders
Lists only the orders created by the authenticated reseller, newest first. Query parameters: status, limit, offset.
8. Resend the onboarding link
Re-issues and re-emails the single-use OAuth magic onboarding link for the customer created by one of your orders. Use this when the original link (valid for 24 hours, single-use) expired before the customer signed in.
Request body mirrors the oauth_onboarding block of POST /api/partner-checkout/orders:
{
"oauth_onboarding": {
"redirect_uri": "https://partner.example.com/oauth/callback",
"scope": "openid payroll.write",
"state": "partner-corr-abc123"
}
}
The original redirect_uri/scope/state are never stored, so you must supply them again. They are re-validated against your current OAuth client, so a deleted client or a changed redirect URI fails 400 rather than minting a dead link.
Rules:
- Requires
partner.checkout.write. - Reseller must own the order.
- The customer must have been provisioned with
oauth_onboardingoriginally, and must not have completed onboarding yet (409otherwise; for an already-active customer, use a standard password reset instead). - Any previously issued, unused link is invalidated; only the newest link works.
- The magic link is emailed only to the customer's verified address and is never returned in the response.
- If the email cannot be sent, the call returns
502and no new link is issued.
{
"customer_id": 192601,
"order_id": 192484,
"email_sent": true,
"expires_in_hours": 24,
"scope_used": "partner.checkout.write"
}
07Free trials
A free trial lets your client evaluate Lightning Payroll before anyone is billed. It is deliberately not an order:
- No order, order line, order total, or post-pay record is created.
- Nothing appears on an invoice, and you are not billed.
- The trial subscription is never renewed.
- The trial runs for one calendar month from the day you create it.
When the trial ends, you place the real order with POST /api/partner-checkout/orders and pass the trial's customer_id as end_customer_id.
Trial sequence
POST/api/partner-checkout/trials provisions the client and emails them their access
Persist the returned customer_id and subscription_trial_id
GET/api/partner-checkout/trials to watch days_remaining
POST/api/partner-checkout/orders with dry_run=true and end_customer_id to confirm the price
Repeat with dry_run=false and an Idempotency-Key
The free month is once per client
order.add_free_trial_month defaults to true and adds one extra month to the subscription, so a monthly signup gets 2 months and an annual signup gets 13.
That free month is granted once per client. If the client has already had a free trial, whether from POST /api/partner-checkout/trials, a signup on our website, or an earlier order of yours that carried a free month, it is not granted a second time.
When that happens the request still succeeds. It does not error:
{
"free_trial_month_applied": false,
"warnings": [
"A free trial month was already provided for this customer (trial ended 2026-08-31), so no additional free month was applied to this order."
]
}
Read free_trial_month_applied rather than assuming, so you never quote a client 13 months and deliver 12. In practice this only triggers when you send end_customer_id, because a brand-new client has no prior trial and always receives the free month.
Ordering for an existing client
Send end_customer_id on POST /api/partner-checkout/orders to order for a client you already provisioned. Then:
- No new customer record is created; the order attaches to that client.
- The
customerblock is still required, andcustomer.emailmust match that client's email, otherwise the API returns409. This stops an order being attached to the wrong account. - The
companyblock is still required. Re-sending the same ABN or IRD number is accepted because that client already holds it. - The client's billing address is updated in place from
billing_address. - Any still-running trial subscription is retired, so the paid plan's employee and company limits take effect immediately.
- The password-reset welcome email is not resent, since the client already has a password. Use
POST /api/partner-checkout/orders/{order_id}/resend-onboarding-linkif they never signed in.
You may only pass an end_customer_id for a client you provisioned, meaning a trial you created or a client you already have an order for. Anything else returns 403. An unknown ID returns 404.
9. Create a trial
Creates a client on a free one-month trial. Requires partner.checkout.write and an Idempotency-Key.
| Field path | Type | Required | Notes |
|---|---|---|---|
customer.first_name | string | Yes | 1-32 chars |
customer.last_name | string | Yes | 1-32 chars |
customer.email | Yes | Must be unique | |
customer.phone | string | Yes | 3-32 chars |
company.legal_name | string | Yes | 1-128 chars |
company.abn | string | Conditional | AU only. Exactly one of abn or ird_number must be sent. |
company.ird_number | string | Conditional | NZ only. Exactly one of abn or ird_number must be sent. |
billing_address | object | No | Optional for a trial, since nothing is billed. Same fields and rules as the order endpoint. When omitted, no address record is created and a warning says so. |
send_customer_welcome_email | boolean | No | Default true. Emails the client a branded link to set their password and start the trial. |
oauth_onboarding | object | No | Same block and behaviour as the order endpoint. Replaces the password-reset email with a single-use magic link into your consent screen. |
{
"customer": {
"first_name": "Alice",
"last_name": "Nguyen",
"email": "alice@example.com",
"phone": "+61 7 3000 0000"
},
"company": {
"legal_name": "Sunrise Hospitality Pty Ltd",
"abn": "10000000000"
}
}
Rules:
- The company's ABN or IRD number is claimed at this point, so no other reseller can start a competing trial for the same entity, and your later order for that company is accepted.
- A duplicate client email returns
409. - An ABN or IRD number already held by a different client returns
409. - Too many claim attempts in a minute returns
429. - The client's onboarding email carries your branding when you have white-label branding configured.
- You receive no invoice or confirmation email, because nothing was charged.
{
"scope_used": "partner.checkout.write",
"warnings": [],
"customer_id": 192601,
"address_id": null,
"subscription_id": 88214,
"subscription_trial_id": 26411,
"customer_email": "alice@example.com",
"company": {
"legal_name": "Sunrise Hospitality Pty Ltd",
"abn": "10000000000",
"ird_number": null,
"country_code": "AU",
"currency_code": "AUD"
},
"trial_start_date": "2026-07-31",
"trial_expiry": "2026-08-31",
"days_remaining": 31,
"site_mode": "au",
"emails_sent": true,
"idempotency_replayed": false
}
10. List trials
Paginated list of the trials you created, newest first. Accepts any partner checkout scope.
| Name | Type | Notes |
|---|---|---|
status | string | Optional filter: active, expired, or cancelled. Any other value returns 422. |
limit | integer | 1-100, default 25 |
offset | integer | Default 0 |
Each row returns subscription_trial_id, subscription_id, customer_id, customer_email, company_name, status, trial_start_date, trial_expiry, days_remaining, cancelled_at, converted, and converted_order_id.
statusisactivewhile the trial runs,cancelledif you ended it early, andexpiredonce it lapsed on its own.days_remainingis zero or negative once the trial has ended.convertedistrueonce you have placed a paid order for that client, andconverted_order_idnames it.- Trials that started on our own website are not listed, because they are not yours.
11. Cancel a trial
Ends a trial before its expiry date, for example when it was provisioned against the wrong entity. Requires partner.checkout.cancel or partner.checkout.write.
{
"subscription_trial_id": 26411,
"reason": "Provisioned against the wrong entity."
}
Rules:
- You may only cancel a trial you created (
403otherwise), and an unknown ID returns404. - Cancelling twice returns
409. - The trial subscription's expiry is set to yesterday, so the client's access ends immediately.
- The client record and the trial history are kept for audit; nothing is deleted.
- Cancelling does not release the company's claimed ABN or IRD number, because that record can carry live accounting-integration connections. If you need to re-provision the same company under a different email address, contact support.
{
"subscription_trial_id": 26411,
"subscription_id": 88214,
"customer_id": 192601,
"previous_trial_expiry": "2026-08-31",
"trial_expiry": "2026-07-30",
"cancelled_at": "2026-07-31T02:14:09",
"reason": "Provisioned against the wrong entity."
}
08Managing renewals
Upgrades move a client up a tier immediately. Moving a client down a tier works differently: a mid-term downgrade is rejected at checkout, because a part-used higher plan is worth more than the lower plan being bought. What you can set is the plan a client renews onto.
For clients whose headcount moves seasonally, put them on a monthly billing cycle. Monthly does not change the downgrade rule, but it means a renewal comes around every month rather than once a year, so a client can move up when they need to and back down at the end of the month.
12. List upcoming renewals
Lists every client subscription you onboarded that has not yet renewed, oldest expiry first. Query parameters: limit (default 50, max 200), offset.
Each entry carries the current plan, is_monthly, auto_renew_on, and pending_renewal.
pending_renewal is null until a renewal order has been prepared. Those are created 30 days before expiry for annual subscriptions, and same-day as the previous renewal for monthly ones, so a monthly client nearly always has one you can change. Check it is non-null, and that its editable flag is true, before calling the update below.
13. Change a pending renewal
Sets the plan a client renews onto, and whether they renew at all. Supply at least one field.
{
"renewal_product_id": 280,
"auto_renew_on": true,
"send_renewal_notification": true
}
Business rules:
- The subscription must be one your account onboarded, else
403. renewal_product_idedits the pending renewal order, so409if none exists yet or it is no longer pending.- The product must be one of your own subscription plans; add-ons, ad-hoc products and discount products are
422. - The line's price and tax resolve from the renewal order's billing country, falling back to your account's billing address;
409if neither resolves to AU or NZ. - The line is repriced at your negotiated reseller rate.
- Per-employee quantity is kept as it is, unless it falls below the client's
per_employee_minimum(default 15), in which case it is raised to that floor. auto_renew_onis stored against the end customer, so it applies to every subscription that customer holds. The response lists each affected subscription inauto_renew_affects_subscription_ids.- A short notification is emailed to you whenever the line actually changes. Set
send_renewal_notificationtofalseto suppress it, for example when your own users drive the change.
No Idempotency-Key is needed. Editing an existing renewal line is idempotent by construction.
09Looking up a client
14. Resolve a client
Resolves a customer id into who that client is and whether they are currently subscribed. Requires any partner checkout scope.
Use it to turn the customerId on a webhook payload, or one you stored at onboarding, into a name, an email and the plans the client holds right now.
You can look up three kinds of client: clients you onboarded through partner checkout, clients you started on a free trial, and clients who have authorised your OAuth client.
The last case is the one that matters if your integration connects to customers who already subscribe rather than selling to them. Those clients have no order attributed to you, but a live authorisation is enough on its own. Any other customer id returns 404, whether or not it exists, so this cannot be used to discover ids you were not already given.
Cancelling a trial or an order does not end the relationship, so those clients remain visible.
{
"customer_id": 12345,
"customer_name": "Alice Nguyen",
"company_name": "Acme Pty Ltd",
"email": "alice@example.com",
"has_active_subscription": true,
"subscriptions": [
{
"subscription_id": 987,
"product_id": 240,
"product_name": "Enterprise",
"expiry": "2027-03-01",
"status": "active",
"is_monthly": false,
"is_trial": false,
"auto_renew_on": true,
"quantity": 15
}
]
}
Notes:
has_active_subscriptionis the same check that decides whether a client's data is writable. When it isfalse, reads keep working but writes return403, so testing this first separates a lapsed client from a genuine fault in your integration.subscriptionslists the current term of each subscription, latest expiry first. It is empty for a client who has never subscribed.statusbecomeslapsedon the day afterexpiry. A subscription expiring today is stillactive.is_trialmarks a free trial. A trial reads asactiveright up until it ends, so check this flag before treating a client as a paying customer.- A trial was never sold, so it has no order line behind it. On a trial,
product_id,product_name,quantityandis_monthlyare allnull. Treatis_trialas the reason those fields are missing, rather than expecting a product. auto_renew_onis stored against the customer rather than the subscription, so every entry carries the same value.
Access to this endpoint is not a substitute for the webhook stream. It answers "what is this client's state now", not "what changed".
10Idempotency
Idempotency only applies to execute requests where dry_run=false, and to trial creation.
Idempotency-Keyis required.- Maximum length is
255. - Same key plus same payload returns the original successful execute response with
idempotency_replayed=true. - Same key plus different payload returns
409. - Keys are shared across orders and trials, so do not reuse an order's key for a trial.
11Pricing and promo behaviour
- Australia uses
AUD. - New Zealand uses
NZD. /optionscan show both partner pricing and mapped retail RRP values.- Preview and execute responses can include a welcome-offer promo discount on the base subscription line.
- Preview and execute responses return identical
order_linesandtotalsfor the same payload: preview is the price the partner agrees to, and execute persists those exact numbers.
Per-line GST and RRP disclosure
Every entry in order_lines includes the GST treatment that was applied and, where available, the retail comparison values:
| Field | Description |
|---|---|
tax_rate | Tax rate applied to this line. 0.1000 for AU GST, 0.0000 for NZ (GST-Free Export). |
currency_code | AUD or NZD. |
unit_price_ex_tax | Per-unit partner price excluding tax. AU base. |
unit_price_inc_tax | Per-unit partner price including tax. AU = ex_tax × 1.10. NZ = ex_tax (no NZ GST line). |
rrp_unit_price_ex_tax | Per-unit retail price excluding tax. null when no retail counterpart exists (e.g. Figtree, per-employee REF SKUs). Prorated when subscription_end_date is set. |
rrp_unit_price_inc_tax | Per-unit retail price including tax. Same GST rules as unit_price_inc_tax. |
rrp_line_total_inc_tax | RRP × quantity, including tax. |
LP-462 NZ pricing detail. Intellitron Pty Ltd is AU-GST-registered but not NZ-GST-registered, so sales to NZ are GST-Free Exports. To keep the numeric advertised price identical across AU and NZ, the NZ ex-tax price is uplifted by AU GST. NZ tax_rate stays 0.0000.
AU customer
- Pays
$1100 inc-taxfor the product
NZ customer
- Pays
$1100 ex-tax,$0 GST,$1100 inc-tax
The numeric advertised price is the same; the GST treatment differs.
12Subscription term alignment & prorating
Partners often need a new Lightning Payroll subscription to expire on the same date as the partner's own renewal cycle. Supply order.subscription_end_date to align the term and have the base subscription and recurring add-ons prorated to that window.
Rules:
subscription_end_datemust be strictly after today.- For annual base products it must be less than 365 days from today.
- For monthly base products it must be less than 30 days from today.
- Out-of-range values are rejected with
422. - The mandatory training session line is always charged at full retail; it is one-off labour, not a recurring service.
- The aligned date is recorded as the subscription's exact expiry; the next term renews at full price under normal renewal rules.
Prorata formula
paid_days = (subscription_end_date - today) - (30 if add_free_trial_month else 0) factor = max(0, paid_days) / term_days # term_days = 365 annual, 30 monthly unit_price = round(base_after_reseller_discount * factor) # NZ uplift applied after
If paid_days <= 0 (e.g. a partner aligning a customer for a window smaller than the free trial month) the prorated lines are charged $0. The training session line remains at full price regardless.
Response fields
subscription_end_date: echoed when supplied,nullotherwise.prorata_factor: the multiplier applied to prorated lines,nullwhen no alignment was requested.warnings: includes a description of the prorata factor and any $0-floor or trial-month-consumed notes.
13Email behaviour
On successful execute:
- The authenticated reseller receives a billed-account confirmation email.
- If
oauth_onboardingwas supplied, the new customer receives a branded magic onboarding email (see "OAuth single-flow onboarding"); this replaces the password-reset welcome email. - Otherwise, if
send_customer_welcome_email=true, the new customer receives a branded onboarding email with a password-reset link.
If email sending fails, the order can still succeed and emails_sent will be false.
If a magic onboarding link expires before the customer uses it, re-send a fresh one with POST /api/partner-checkout/orders/{order_id}/resend-onboarding-link (endpoint 8). Unlike execute, a resend whose email fails returns 502 and issues no new link.
14Error reference
| Status | Typical causes |
|---|---|
| 400 | Invalid product selection, invalid add-on combination, missing execute idempotency key |
| 403 | Caller is not an eligible API admin reseller, missing required scope, or attempting to access another reseller's order |
| 404 | Order not found |
| 409 | Duplicate email, ABN, or IRD; reused idempotency key with a different payload; already-cancelled order; cancellation window expired; onboarding-link resend for a customer not provisioned with OAuth onboarding or who already completed onboarding |
| 422 | Validation errors, missing required fields, unsupported extra fields, invalid ABN/IRD, invalid zone code |
| 500 | Unexpected server-side failure |
| 502 | Onboarding-link resend could not send the email; no new link was issued |
15Production checklist
Discover products and zones dynamically from the API.
Run availability checks before previewing or executing.
Always preview before executing.
Use Idempotency-Key on every execute request.
Persist customer_id, order_id, and subscription_id for support and reconciliation.
Treat warnings as actionable integration signals.
Handle emails_sent=false separately from order success.
Use order lookup endpoints to reconcile retries and support cancellations.