Docs navigation

Customer + Subscription

The shapes the SDK accepts on its customer and subscriptions props. Pass these in analytics mode and in Direct-mode token sessions. With a connected billing provider, you pass only the IDs and Churnkey looks up the rest from the provider.

The Direct prefix marks these as data passed directly from your code, as opposed to the equivalents Churnkey assembles from a connected provider.

DirectCustomer

interface DirectCustomer {
  id:         string
  email?:     string
  name?:      string
  lastName?:  string
  phone?:     string
  currency?:  string                  // ISO 4217
  addresses?: DirectAddress[]
  metadata?:  Record<string, unknown>
}
FieldNotes
idRequired. Sessions are keyed by this. With a connected provider, use the provider's customer ID (cus_xxx for Stripe). In Direct mode, use your internal ID.
emailForwarded to the Churnkey session payload. Used by Feedback AI and email automations on the Intelligence tier.
name, lastName, phoneAvailable on the customer object inside your handlers and listeners. Not forwarded to the session payload — set them in the token payload when you mint if you need them in the dashboard.
currencyUsed to format prices when DirectPrice.amount.currency isn't set.
metadataForwarded to analytics and webhook payloads. Use it for plan tier, account age, or any field your segments target.

DirectAddress

interface DirectAddress {
  line1?:       string
  line2?:       string
  city?:        string
  state?:       string
  postalCode?:  string
  country?:     string               // ISO 3166-1 alpha-2
}

Used by tax-aware offer rendering in jurisdictions that require it.

DirectSubscription

interface DirectSubscription {
  id:           string
  customerId?:  string                // optional cross-reference
  start:        Date | string         // ISO 8601 or Date
  status:       SubscriptionStatus    // tagged union, see below
  items:        DirectSubscriptionItem[]
  duration?:    DurationConfig
  end?:         Date | string
  discounts?:   DirectDiscount[]
  metadata?:    Record<string, unknown>
}
 
interface DirectSubscriptionItem {
  id?:       string
  price:     DirectPrice
  quantity?: number
}
 
interface DurationConfig {
  interval:       'day' | 'week' | 'month' | 'year'
  intervalCount?: number              // defaults to 1
}
 
interface DirectDiscount {
  id?:          string
  coupon?:      DirectCoupon
  start?:       Date | string
  end?:         Date | string
}
 
interface DirectCoupon {
  id?:               string
  name?:             string
  percentOff?:       number
  amountOff?:        number               // smallest currency unit (cents for USD)
  currency?:         string
  duration?:         'once' | 'repeating' | 'forever'
  durationInMonths?: number
  metadata?:         Record<string, unknown>
}

SubscriptionStatus

type SubscriptionStatus =
  | { name: 'active';   currentPeriod: { start: Date | string; end: Date | string } }
  | { name: 'trial';    trial: { start: Date | string; end: Date | string };
                        currentPeriod?: { start: Date | string; end: Date | string } }
  | { name: 'paused';   pause: { start: Date | string; end?: Date | string };
                        currentPeriod?: { start: Date | string; end: Date | string } }
  | { name: 'canceled'; canceledAt: Date | string }
  | { name: 'unpaid';   currentPeriod?: { start: Date | string; end: Date | string } }
  | { name: 'future';   currentPeriod?: { start: Date | string; end: Date | string } }

The status tagged union carries the subscription's lifecycle state. The default Confirm component reads currentPeriod.end to render a "Your access continues until X" notice when present; in connected mode the dashboard can target offers at specific statuses through segments. The SDK does not suppress or substitute offers client-side based on status — your steps and the dashboard config decide what gets shown.

DirectPrice

interface DirectPrice {
  id:           string
  type?:        'standalone' | 'product'
  active?:      boolean
  productId?:   string
  name?:        string
  description?: string
  duration?:    DurationConfig
  amount:       {
    model?:     'fixed' | 'tiered'
    value:      number                  // smallest currency unit (cents for USD)
    currency?:  string                  // ISO 4217
  }
  metadata?:    Record<string, unknown>
}

amount.value is in the smallest currency unit — cents for USD, pence for GBP, yen for JPY (no division). The SDK formats prices based on currency.

A complete example

<CancelFlow
  appId="app_xxx"
  customer={{
    id:       'cus_123',
    email:    'jane@acme.com',
    name:     'Jane',
    metadata: { plan: 'pro', signupDate: '2024-03-12' },
  }}
  subscriptions={[{
    id:    'sub_456',
    start: '2024-03-12',
    status: {
      name: 'active',
      currentPeriod: {
        start: '2025-04-01',
        end:   '2025-05-01',
      },
    },
    items: [{
      price: {
        id:     'price_pro',
        name:   'Pro monthly',
        amount: { value: 2999, currency: 'USD' },
        duration: { interval: 'month' },
      },
    }],
    discounts: [{
      coupon: {
        id:         'STRIPE_LOYAL10',
        percentOff: 10,
      },
      end: '2025-07-01',
    }],
  }]}
  steps={steps}
  handleCancel={handleCancel}
/>

Next steps