Integrations

Data Structures

When integrating directly with Churnkey, you'll need to define data structures that map your payment provider's data to Churnkey's expected format. Here are the key data structures to implement
  • Customer
  • Subscription
  • Prices (Standalone, Product, or Family)
  • Coupons (optional)

Let's begin with the Customer data structure.

Customer

The Customer structure represents the end-user or account holder in the system.

Properties

PropertyTypeDescription
idstringUnique identifier for the customer
namestringCustomer's name
lastNamestringCustomer's last name (optional)
emailstringCustomer's email address (optional)
phonestringCustomer's phone number (optional)
addressesArrayArray of customer's addresses (optional)
currencystringISO 4217 currency code associated with the customer (optional)
metadataobjectAdditional customer data as key-value pairs (optional)
createdAtstringISO 8601 formatted date when the customer was created
discountsArrayArray of coupons applied to all subscriptions (customer-level discounts, optional)

Address Properties

PropertyTypeDescription
line1stringFirst line of the address
line2stringSecond line of the address (optional)
citystringCity name
statestringState or province name
postalCodestringPostal or ZIP code
countrystringTwo-letter country code (ISO 3166-1 alpha-2)

Implementation Example

function Customer(data) {
  this.id = data.id;
  this.name = data.name;
  this.lastName = data.lastName;
  this.email = data.email;
  this.phone = data.phone;
  this.addresses = data.addresses || [];
  this.currency = data.currency;
  this.metadata = data.metadata;
  this.createdAt = data.createdAt;
  this.discounts = data.discounts || [];
}

function mapCustomer(providerCustomer) {
  return new Customer({
    id: providerCustomer.id,
    name: providerCustomer.name || `${providerCustomer.firstName || ''} ${providerCustomer.lastName || ''}`.trim(),
    lastName: providerCustomer.lastName,
    email: providerCustomer.email,
    phone: providerCustomer.phone,
    addresses: mapAddresses(providerCustomer.addresses),
    currency: providerCustomer.currency,
    metadata: providerCustomer.metadata,
    createdAt: new Date(providerCustomer.created_at).toISOString(),
    discounts: mapDiscounts(providerCustomer.discounts),
  });
}

function mapAddresses(providerAddresses) {
  if (!providerAddresses) return [];
  return providerAddresses.map(addr => ({
    line1: addr.line1,
    line2: addr.line2,
    city: addr.city,
    state: addr.state,
    postalCode: addr.postal_code,
    country: addr.country,
  }));
}

// Customer-level Coupons

function Coupon(data) {
  this.id = data.id;
  this.name = data.name;
  this.code = data.code;
  this.duration = data.duration;
  this.value = data.value;
  this.redemptions = data.redemptions;
  this.expiresAt = data.expiresAt;
  this.whitelist = data.whitelist;
  this.blacklist = data.blacklist;
}

function mapDiscounts(providerDiscounts) {
  if (!providerDiscounts) return [];
  return providerDiscounts.map(discount => ({
    coupon: new Coupon(discount.coupon),
    start: discount.start ? new Date(discount.start) : undefined,
    end: discount.end ? new Date(discount.end) : undefined,
  }));
}

Subscription

The Subscription structure represents a customer's subscription to a product or service in the system.

Properties

PropertyTypeDescription
idstringUnique identifier for the subscription
customerIdstringID of the customer to whom the subscription belongs
statusobjectCurrent status of the subscription (see Status object)
trialobjectTrial information, if the subscription is in trial (optional)
itemsArrayArray of subscription items (see Item object)
discountsArrayArray of discounts applied to the subscription (optional)
durationobjectDuration of the subscription (see Duration object)
startstringISO 8601 formatted date when the subscription started
metadataobjectAdditional subscription data as key-value pairs (optional)

Status Object Properties

PropertyTypeDescription
namestringStatus name (e.g., 'active', 'paused', 'canceled', 'trial')
currentPeriodobjectInformation about the current period (for active and trial statuses, optional)
startstringISO 8601 formatted date when the pause starts (for paused status, optional)
endstringISO 8601 formatted date when the pause ends (for paused status, optional)
canceledAtstringISO 8601 formatted date when the subscription was cancelled (for canceled status, optional)

Item Object Properties

PropertyTypeDescription
idstringID of the item in the provider system (optional)
priceobjectPrice object associated with this item
quantitynumberQuantity of the item
createdAtstringISO 8601 formatted date when the item was created (optional)

Duration Object Properties

PropertyTypeDescription
amountnumberAmount of the unit for the duration
unitstringUnit of the duration ('day', 'week', 'month', 'year')

Implementation Example

function Subscription(data) {
  this.id = data.id;
  this.customerId = data.customerId;
  this.status = data.status;
  this.trial = data.trial;
  this.items = data.items || [];
  this.discounts = data.discounts || [];
  this.duration = data.duration;
  this.start = data.start;
  this.metadata = data.metadata;
}

function mapSubscription(providerSubscription) {
  return new Subscription({
    id: providerSubscription.id,
    customerId: providerSubscription.customerId,
    status: mapStatus(providerSubscription.status),
    trial: mapTrial(providerSubscription.trial),
    items: mapItems(providerSubscription.items),
    discounts: mapDiscounts(providerSubscription.discounts),
    duration: mapDuration(providerSubscription.duration),
    start: new Date(providerSubscription.start).toISOString(),
    metadata: providerSubscription.metadata,
  });
}

function mapStatus(providerStatus) {
  const statusMap = {
    future: { name: 'future' },
    trial: {
      name: 'trial',
      currentPeriod: mapPeriod(providerStatus.currentPeriod),
    },
    active: {
      name: 'active',
      currentPeriod: mapPeriod(providerStatus.currentPeriod),
    },
    paused: {
      name: 'paused',
      start: providerStatus.start ? new Date(providerStatus.start).toISOString() : undefined,
      end: providerStatus.end ? new Date(providerStatus.end).toISOString() : undefined,
    },
    canceled: {
      name: 'canceled',
      canceledAt: providerStatus.canceledAt ? new Date(providerStatus.canceledAt).toISOString() : undefined,
    },
    unpaid: {
      name: 'unpaid',
      currentPeriod: mapPeriod(providerStatus.currentPeriod),
    },
    error: {
      name: 'error',
      reason: providerStatus.reason,
    },
  };

  return statusMap[providerStatus.name] || { name: 'unknown' };
}

function mapPeriod(period) {
  if (!period) return undefined;
  return {
    start: new Date(period.start).toISOString(),
    end: new Date(period.end).toISOString(),
  };
}

function mapTrial(trial) {
  return trial ? mapPeriod(trial) : undefined;
}

function mapItems(providerItems) {
  if (!providerItems) return [];
  return providerItems.map(item => ({
    id: item.id,
    price: mapPrice(item.price),
    quantity: item.quantity,
    createdAt: item.createdAt ? new Date(item.createdAt).toISOString() : undefined,
  }));
}

function mapPrice(providerPrice) {
  // Implement price mapping based on your Price model, see the next section.
}

function mapDiscounts(providerDiscounts) {
  // Implement discount mapping based on your specific discount structure
  return providerDiscounts || [];
}

function mapDuration(providerDuration) {
  return {
    amount: providerDuration.amount,
    unit: providerDuration.unit,
  };
}

Price Models

1. Standalone Price

The Standalone Price structure represents the cost structure for a product or service when prices exist independently in the system, without being directly tied to a product. This model is typically used by providers like Braintree.

Properties

PropertyTypeDescription
idstringUnique identifier for the price
namestringName of the price plan
descriptionstringDescription of the price plan (optional)
currencystringISO 4217 currency code
amountobjectPrice amount details (see Amount structure below)
durationobjectBilling duration of the plan
overridesobjectAmount overrides for specific currencies/countries

Amount Structure

PropertyTypeDescription
modelstringPricing model (e.g., 'fixed', 'tiered')
currencystringISO 4217 currency code
unitnumberPrice per unit in cents (for fixed model)
flatnumberFlat price not affected by quantity (optional)
tiersarrayList of tiers (for tiered model)
modestringTier mode (for tiered model)

Implementation

function mapPrice(providerPrice) {
  return {
    id: providerPrice.id,
    name: providerPrice.name || 'Unnamed Price',
    description: providerPrice.description || undefined,
    currency: providerPrice.currency.toUpperCase(),
    amount: mapAmount(providerPrice.amount),
    duration: mapDuration(providerPrice.duration),
    overrides: mapOverrides(providerPrice.overrides),
  };
}

function mapAmount(providerAmount) {
  if (providerAmount.model === 'fixed') {
    return {
      model: 'fixed',
      currency: providerAmount.currency,
      unit: providerAmount.unit,
      flat: providerAmount.flat || undefined,
    };
  } else if (providerAmount.model === 'tiered') {
    return {
      model: 'tiered',
      currency: providerAmount.currency,
      tiers: providerAmount.tiers.map(mapTier),
      mode: providerAmount.mode || 'total',
    };
  }
}

function mapTier(providerTier) {
  return {
    upTo: providerTier.upTo,
    unit: providerTier.unit,
    flat: providerTier.flat || undefined,
  };
}

function mapDuration(providerDuration) {
  return {
    value: providerDuration.value,
    unit: providerDuration.unit,
  };
}

function mapOverrides(providerOverrides) {
  return {
    currency: providerOverrides.currency || {},
    country: providerOverrides.country || {},
  };
}

2. Product Price

The Product Price model is used when prices are associated with a specific product. This model is typically used by providers like Stripe and Paddle. It extends the Standalone Price model by adding a product association.

Additional Property

PropertyTypeDescription
productIdstringID of the product this price belongs to

Implementation

function mapProductPrice(providerPrice) {
  const basePrice = mapPrice(providerPrice);
  return {
    ...basePrice,
    productId: providerPrice.productId,
  };
}

3. Family Price

The Family Price model is used when prices belong to a product, and products are grouped into families (or groups). This model is typically used by providers like Chargebee and Maxio. It builds upon the Product Price model by adding family associations.

Family Structure

PropertyTypeDescription
idstringUnique identifier for the family
namestringCustomer-facing name of the product family
descriptionstringCustomer-facing description of the product family

Product Structure

PropertyTypeDescription
idstringUnique identifier for the product
familyIdstringID of the family to which the product belongs
namestringName of the product
descriptionstringDescription of the product (optional)

Implementation

function mapFamily(providerFamily) {
  return {
    id: providerFamily.id,
    name: providerFamily.name,
    description: providerFamily.description || undefined,
  };
}

function mapProduct(providerProduct) {
  return {
    id: providerProduct.id,
    familyId: providerProduct.familyId,
    name: providerProduct.name,
    description: providerProduct.description || undefined,
  };
}

function mapFamilyPrice(providerPrice) {
  const productPrice = mapProductPrice(providerPrice);
  return {
    ...productPrice,
    familyId: providerPrice.familyId,
  };
}

Usage Example

Here's an example of how to use these price models in your application:

// Standalone Price
const rawPrice = await provider.fetchPrice(priceId);
const price = mapPrice(rawPrice);
console.log(`Price: ${price.name} (${price.currency} ${price.amount.unit / 100})`);

// Product Price
const rawProductPrice = await provider.fetchProductPrice(productPriceId);
const productPrice = mapProductPrice(rawProductPrice);
console.log(`Product Price: ${productPrice.name} for Product ${productPrice.productId}`);

// Family Price
const rawFamily = await provider.fetchFamily(familyId);
const family = mapFamily(rawFamily);

const rawProducts = await provider.fetchProductsForFamily(familyId);
const products = rawProducts.map(mapProduct);

const rawPrices = await provider.fetchPricesForProduct(productId);
const prices = rawPrices.map(mapFamilyPrice);

console.log(`Family: ${family.name}`);
products.forEach(product => {
  console.log(`- Product: ${product.name}`);
  const productPrices = prices.filter(price => price.productId === product.id);
  productPrices.forEach(price => {
    console.log(`  - Price: ${price.name} (${price.currency} ${price.amount.unit / 100})`);
  });
});

Coupon

The Coupon structure represents discounts or promotional offers that can be applied to subscriptions.

PropertyTypeDescription
idstringUnique identifier for the coupon
codestringCoupon code used for redemption (optional)
namestringCustomer-facing name of the coupon (optional, defaults to code if not provided)
durationobjectDefines how long the discount will be applied
valueobjectDefines the value of the coupon (percentage or fixed amount)
redemptionsobjectStatistics and limits for coupon redemptions (optional)
expiresAtstringISO 8601 formatted date after which the coupon expires (optional)
whitelistobjectSpecifies conditions for which the coupon is applicable (optional)
blacklistobjectSpecifies conditions for which the coupon is not applicable (optional)

Duration

The duration property of a coupon defines how long the discount will be applied. It can have one of the following types:

TypeDescription
foreverCoupon is valid during the lifetime of the subscription
onceCoupon is valid only for a single charge (subscription cycle)
periodCoupon is valid for a specified period after it's first applied
cycle-amountCoupon is valid for a specified number of cycles after it's first applied

For the period type, the duration is further specified using the following properties:

PropertyTypeDescription
amountnumberAmount of the unit for the duration
unitstringUnit of the duration: 'day', 'week', 'month', or 'year'

Value

The value property defines the discount amount of the coupon:

TypeDescription
percentPercentage discount
amountFixed amount discount

For percentage discounts:

PropertyTypeDescription
valuenumberThe percentage value of the discount
currencystringISO 4217 currency code (optional)

For fixed amount discounts:

PropertyTypeDescription
valuenumberThe fixed discount amount in the smallest currency unit
currencystringISO 4217 currency code

Redemptions

The redemptions property tracks the usage and limits of the coupon:

PropertyTypeDescription
currentnumberNumber of times the coupon has been redeemed
maxnumberMaximum number of times the coupon can be redeemed (optional)

Whitelist and Blacklist

These properties can be used to restrict the coupon's applicability:

PropertyTypeDescription
productIdsstringArray of product IDs for which the coupon is applicable/not applicable
productFamilyIdsstringArray of product family IDs for which the coupon is applicable/not applicable

Note: Whitelist takes precedence over the blacklist.

Example Usage

const coupon = {
  id: 'SUMMERSALE2023',
  code: 'SUMMER20',
  name: 'Summer Sale 20% Off',
  duration: {
    type: 'period',
    amount: 3,
    unit: 'month',
  },
  value: {
    type: 'percent',
    value: 20,
  },
  redemptions: {
    current: 50,
    max: 1000,
  },
  expiresAt: '2023-08-31T23:59:59Z',
  whitelist: {
    productIds: ['PROD001', 'PROD002'],
    productFamilyIds: ['FAM001'],
  },
};

This example shows a coupon that offers a 20% discount for 3 months, applicable to specific products and product families, with usage limits and an expiration date.


These data structures serve as the cornerstone of your Churnkey integration. In the next step, we'll implement retrieval functions in Data Retrieval Endpoints. These functions will enable Churnkey to fetch and view data from your API efficiently.