Payment Orchestrator
Subscription
Getting Started

Getting Started

This guide walks the full chain once, server-side: create a product, price it with a plan, upsert a customer, save a card, start the subscription, and listen to the webhooks that confirm it. All calls use your access token (Authorization: Bearer at_live_…) against https://api-gateway.lunixi.com, and every list endpoint paginates with pageSize / pageToken.

Amounts are minor-unit integers (kuruş): "price": 49900 = ₺499.00.

Create a product

The product is the sellable thing; plans attach prices to it.

curl -X POST https://api-gateway.lunixi.com/api/v1/subscriptions/products \
  -H "Authorization: Bearer at_live_5f3e2b1a..." \
  -H "Content-Type: application/json" \
  -d '{
    "code": "pro",
    "name": "Pro",
    "description": "For growing teams"
  }'

Create a plan

The plan carries the price, the currency, the optional trial and the billing rule — how often an invoice is generated.

curl -X POST https://api-gateway.lunixi.com/api/v1/subscriptions/plans \
  -H "Authorization: Bearer at_live_5f3e2b1a..." \
  -H "Content-Type: application/json" \
  -d '{
    "productId": "<product-id>",
    "code": "pro-monthly",
    "name": "Pro Monthly",
    "price": 49900,
    "currency": "TRY",
    "trialPeriodDays": 7,
    "billing": {
      "frequency": "MONTHLY",
      "interval": 1,
      "timezone": "Europe/Istanbul",
      "lastDayOfMonthFix": true
    }
  }'

See Plans for dunning fields (maxDunningAttempts, dunningScheduleDays, gracePeriodDays) and the full billing rule.

Upsert the customer

Customers are keyed by your externalId and created with PUT — call it as many times as you like, the same external id always resolves to the same customer. No "did I already create this user?" bookkeeping.

curl -X PUT https://api-gateway.lunixi.com/api/v1/subscriptions/customers \
  -H "Authorization: Bearer at_live_5f3e2b1a..." \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "user-8421",
    "name": "Ayşe",
    "surname": "Yılmaz",
    "email": "[email protected]",
    "gsmNumber": "+905550000000"
  }'

You can later fetch by your own key with GET /api/v1/subscriptions/customers/by-external/user-8421.

Save a card

Card data never touches your server: collect it in the browser with the secure card form (the Portal SDK's cardForm or the hosted checkout form), which returns a paymentMethodToken. Then register the card against the customer:

curl -X POST https://api-gateway.lunixi.com/api/v1/subscriptions/cards \
  -H "Authorization: Bearer at_live_5f3e2b1a..." \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "<customer-id>",
    "paymentMethodToken": "<token-from-card-form>",
    "brand": "VISA",
    "bin": "454360",
    "last4": "1234",
    "expireMonth": 12,
    "expireYear": 2028,
    "isDefault": true
  }'
⚠️

Saving a card requires 3DS. The cardholder authenticates with 3D Secure as part of card registration — a card cannot enter the vault unverified. Control the moment with verificationMode: ON_ADD verifies at save time, FIRST_CHARGE defers verification to the first charge.

Create the subscription

Bring it together: customer + plan + collection method. The idempotencyKey is required — retrying the same request with the same key never creates a second subscription.

curl -X POST https://api-gateway.lunixi.com/api/v1/subscriptions/subscriptions \
  -H "Authorization: Bearer at_live_5f3e2b1a..." \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "<customer-id>",
    "planId": "<plan-id>",
    "idempotencyKey": "sub-user-8421-pro-monthly-001",
    "collectionMethod": "AUTO_CHARGE",
    "paymentCardId": "<card-id>"
  }'

With a 7-day trial the subscription starts in trial and converts automatically; pass skipTrial: true to bill immediately.

Listen to webhooks

Don't poll — react. The two events that matter first:

EventWhen
subscription.createdThe subscription record exists (possibly in trial).
subscription.activatedThe subscription is live — provision access now.

Down the line you'll also want subscription.trial_will_end, invoice.paid, invoice.payment_failed and subscription.canceled. Configure your endpoint and verify signatures as described in Webhooks.

Errors

StatusMeaningWhat to do
400Validation failed (missing idempotencyKey, bad billing rule, negative price…)Fix the request body; the response lists the failing fields.
401Missing or invalid API keySend Authorization: Bearer at_live_….
404Referenced object not found (productId, planId, customerId, paymentCardId)Create the dependency first — follow the step order above.
409Conflict (duplicate code, idempotency replay with a different body)Use a fresh code / a fresh idempotencyKey, or send the identical original body.

Next steps