Octany. for/devs
§ 02 — Checkout

Hostad kassa — på dina villkor.

Checkout är Octanys moderna kassa. Tre vägar in: en ren URL, en inbäddad varukorg eller ett JS-API. Alla tre landar på samma hostade sida på checkout.octany.com — Octany sköter varukorg, kund, betalsätt, SCA och tack-sidan.

Om du bara läser ett avsnitt Bygg en URL på din server, skicka användaren dit, och lyssna sedan på subscription.created-webhooken för att aktivera kontot. Det är standardmönstret. Allt nedan är varianter på det.

§ 02.1 Den hostade URL:en #

Den enklaste integrationen. Bygg en URL på servern och länka eller redirecta dit. Ingen JavaScript krävs.

https://checkout.octany.com/checkout/{account-id}/product/{product-id}
  ?email=anna@example.com
  &referenceId=user_42
  &successUrl=https://app.example.com/welcome

Octany skapar en checkout-session, lägger produkten i varukorgen och redirectar till /pay/checkout/{account}/{id} där SPA:n tar över. Användaren stannar kvar i Octany tills köpet är klart eller hen hoppar av.

Domän Produktion: https://checkout.octany.com. Använd den som standard om inte din kontakt på Octany har gett dig en dedikerad miljö.

§ 02.2 Query-parametrar #

Produkten är det enda som måste vara med. Resten är frivilliga förifyllningar eller metadata för spårning.

ParamSyfte
successUrlDit Octany skickar användaren efter lyckad betalning. Måste vara HTTPS.
referenceIdAnvänd den här för att korrelera. Ditt eget ID för besökaren, medlemmen, givaren eller ordern. Hamnar på Subscription/Order och på varje webhook för den resursen.
referenceNameMänskligt läsbar etikett som syns bredvid resursen i admin.
emailFörifyller e-postfältet.
firstName, lastNameFörifyller namnfält.
companyNameFörifyller företagsfält.
roleEn roll att tagga användaren med (för CRM-segmentering).
rolesKommaseparerade roller, t.ex. roles=member,donor.
amountHeltal i öre. Skriver över produktens pris för den här sessionen — för "namnge ditt pris"/påfyllningsflöden.
methodsKommaseparerad allow-lista över betalsätt att visa, t.ex. methods=stripe,swish. Användbart när du vill ha ett Swish-först- eller endast-kort-flöde.
onlyMethodBakåtkompatibel singel-version av methods, t.ex. onlyMethod=swish. Använd methods i nya integrationer.
quantitytrue/false. Sätt quantity=false för att gömma antal-kontroller och låsa antalet i kassan.
lockedBakåtkompatibel låsflagga. locked=true låser antalet även om quantity=true är satt. Använd quantity=false i nya integrationer.
localesv, en, osv. Faller tillbaka på kontots språk. Kan ändras senare i UI:t.
utm_*utm_source, utm_medium, utm_campaign, utm_content, utm_term — fångas in på varukorgen och hamnar på ordern.

Minimalt exempel

<a href="https://checkout.octany.com/checkout/{account}/product/42?referenceId=user_99&email=anna@example.com&successUrl=https://app.example.com/welcome">
  Prenumerera — 99 kr/mån
</a>

Begränsa betalsätt eller lås antal

Vill du att den hostade kassan bara visar vissa betalsätt skickar du methods på URL:en. Octany matchar värdena mot de betalsätt som faktiskt är aktiva för produkten och kontot, och ignorerar resten.

https://checkout.octany.com/checkout/{account}/product/{product-id}?methods=stripe,swish&quantity=false

Använd quantity=false för att förhindra att antalet ändras i kassan. Äldre integrationer kan fortfarande skicka locked=true eller onlyMethod=swish; båda funkar fortfarande, men nya integrationer bör använda methods och quantity.

§ 02.3 Success → din app #

Lita inte på query-params på successUrl som beslutsunderlag. Octany skickar dit användaren när hen kvitterat betalningen, men det är webhooken som är sanningskällan (order.paid, subscription.created).

Så här gör du det robust:

Varför det spelar roll Användaren kan stänga fliken mellan betalning och redirect. 3DS kan slutföras asynkront. Annonsblockerare plockar ibland bort query-params. Webhooken kommer alltid fram till slut — det är den du ska lita på.

Reconciliation efter checkout: få in användaren i appen direkt

Webhooken är sanningskällan för "betalningen gick igenom", men den kan släpa några sekunder — och när din successUrl är en deep link tillbaka in i en desktop- eller mobilapp vill du att användaren ska komma in direkt, inte efter en webhook-rundtur. Det finns två varianter; den andra är den vi rekommenderar när latens eller leveransfördröjning är ett bekymmer:

  1. Polla din egen backend från success-sidan. Funkar, men du får en race condition mot webhooken — om din webhook-handler inte hunnit köra säger din databas fortfarande "ingen prenumeration" och användaren fastnar i spinnern.
  2. Polla Octany direkt från success-sidan med GET /subscriptions?filter[reference_id]=<din_user_id>. Hittar prenumerationen så snart den finns, oavsett när webhooken kommer fram. Synka in i din lokala cache och redirecta sedan (webb → nästa sida, native-app → ett custom URL scheme som myapp://billing/success).
// Success-sida — polla Octany var 2,5 sekund tills prenumerationen är aktiv
app.get('/billing/pending/status', async (req, res) => {
  const subs = await octany.subscriptions.find({ reference_id: req.user.id })
  const sub = subs[0]
  if (sub && ['active', 'trialing'].includes(sub.status)) {
    await syncSubscriptionLocally(req.user, sub)         // samma syncer som webhooken anropar
    return res.json({ ready: true, redirect: 'myapp://billing/success' })
  }
  res.json({ ready: false })
})

Webhook-handlern bör köra samma syncer så att båda vägarna landar i identiskt lokalt tillstånd — den som hinner först vinner, den andra blir en no-op. Vi har testat mönstret genom att stoppa webhook-leveranserna i tio minuter; användaren kom ändå in i appen inom sekunder från att den pollade prenumerationen dök upp.

§ 02.4 Inbäddningsbar varukorg #

För varukorgar med flera produkter på en innehållssajt. Levererar ett loader-skript som injicerar två iframes — en flytande "fab"-knapp och en utfällbar varukorg — och exponerar JS-API:et window.Octany.

Installera

<!-- 1. Konfigurera -->
<script>
  window.OctanyConfig = {
    id: '{account-id}',
    locale: 'sv',  // frivilligt; faller tillbaka på kontots språk
    methods: ['stripe', 'swish'],
    quantity: false,
  }
</script>

<!-- 2. Ladda loadern -->
<script src="https://embed.octany.com/loader.js" async></script>

Loadern hämtar internt aktuell version av cart-appen (från app.octany.com/pay/embed-version) och drar matchande app.js/app.css från embed.octany.com, så din sida kan ligga kvar på den stabila loader-URL:en medan Octany släpper nya versioner.

window.OctanyConfig.methods är embed-motsvarigheten till methods-parametern på den hostade kassan: den begränsar vilka betalsätt kassan visar. Sätt quantity: false för att låsa antalet på radobjekt i checkout-sessioner som startas från embed:en.

§ 02.5 window.Octany-API #

När loadern bootat (lyssna på event:et octany-cart-readydocument om du behöver vänta in det) finns window.Octany tillgängligt.

// Skicka in referens-ID:n / UTM som ska hamna på ordern
Octany.set({
  referenceId: 'user_42',
  referenceName: 'Anna Persson',
  email: 'anna@example.com',
})

// Direkt-checkout — samma som URL:en ovan, fast byggd åt dig
Octany.checkout(productId, {
  successUrl: 'https://app.example.com/welcome',
  amount: 19900,  // frivilligt, namnge-ditt-pris i öre
})

// Varukorgsdrivet flöde
Octany.cart.add(productId)        // lägg i den flytande varukorgen
Octany.cart.open()                // fäll ut varukorgen
Octany.cart.close()
Octany.cart.hide()                // göm den flytande "fab"-knappen
Octany.cart.checkout()            // gå vidare till kassan med varukorgens innehåll
Octany.cart.requestCheckoutUrl()  // → Promise<url>, ingen redirect

DOM-event

Varukorgen skickar custom events på document:

EventNärevent.detail
octany-cart-updated Något lagts till, tagits bort eller fått nytt antal { cart: { items, total, ... } }

Använd dem för att uppdatera din egen sidchrome (varukorgsbadge etc.) utan att peta i iframen direkt.

Automatisk UTM-fångst

Embed:en läser utm_source/medium/campaign/content/term från värdsidans URL vid laddning och skickar dem genom Octany.set(...) så att de hamnar i utm_parameters på den resulterande ordern. Du behöver inte koppla det manuellt.

Det som inte finns på window.Octany

Det finns medvetet ingen Octany.portal() eller Octany.manage(). Hantering av prenumerationer — byta kort, se fakturor, säga upp — sker via de signerade billing-portal-URL:erna som ligger på själva subscription-resursen: update_card_url, invoices_url och cancel_url. För att skicka en inloggad användare till "hantera prenumeration": hämta hens prenumeration server-side (GET /subscriptions?filter[reference_id]=…) och redirecta sedan till önskad URL. Det finns ingen "ge mig en färsk portal-URL för den här kunden"-endpoint — URL:erna ligger redan i subscription-payloaden.

§ 02.6 Komplett exempel #

Paywall-mönster: hostad kassa + webhook-aktivering.

// 1. Server-side: bygg URL:en när användaren klickar "Prenumerera"
app.post('/subscribe', (req, res) => {
  const params = new URLSearchParams({
    referenceId: req.user.id,
    email: req.user.email,
    successUrl: 'https://app.example.com/subscribed',
  })
  res.redirect(`https://checkout.octany.com/checkout/${ACCOUNT}/product/${PRODUCT_ID}?${params}`)
})

// 2. Webhook-handler aktiverar användaren när betalningen landar
app.post('/octany/webhook', /* signaturverifiering — se Webhooks-sidan */ (req, res) => {
  const event = req.body
  if (event.name === 'subscription.created' && event.data.reference_id) {
    activateUser(event.data.reference_id, event.data.id)
  }
  res.sendStatus(204)
})

// 3. Success-sidan pollar en backend-endpoint som webhooken fyller på
app.get('/subscribed', (req, res) => {
  res.render('thank-you', { pollUrl: '/me/subscription-status' })
})

Success-sidan litar aldrig på själva redirecten — den pollar en backend-endpoint som kollar användarens octany_subscription_id, satt av webhook-handlern. Mönstret klarar stängda flikar, asynkron 3DS och annonsblockerare som plockar bort query-params.