Data Structures
- 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
Property | Type | Description |
---|---|---|
id | string | Unique identifier for the customer |
name | string | Customer's name |
lastName | string | Customer's last name (optional) |
string | Customer's email address (optional) | |
phone | string | Customer's phone number (optional) |
addresses | Array | Array of customer's addresses (optional) |
currency | string | ISO 4217 currency code associated with the customer (optional) |
metadata | object | Additional customer data as key-value pairs (optional) |
createdAt | string | ISO 8601 formatted date when the customer was created |
discounts | Array | Array of coupons applied to all subscriptions (customer-level discounts, optional) |
Address Properties
Property | Type | Description |
---|---|---|
line1 | string | First line of the address |
line2 | string | Second line of the address (optional) |
city | string | City name |
state | string | State or province name |
postalCode | string | Postal or ZIP code |
country | string | Two-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
Property | Type | Description |
---|---|---|
id | string | Unique identifier for the subscription |
customerId | string | ID of the customer to whom the subscription belongs |
status | object | Current status of the subscription (see Status object) |
trial | object | Trial information, if the subscription is in trial (optional) |
items | Array | Array of subscription items (see Item object) |
discounts | Array | Array of discounts applied to the subscription (optional) |
duration | object | Duration of the subscription (see Duration object) |
start | string | ISO 8601 formatted date when the subscription started |
metadata | object | Additional subscription data as key-value pairs (optional) |
Status Object Properties
Property | Type | Description |
---|---|---|
name | string | Status name (e.g., 'active', 'paused', 'canceled', 'trial') |
currentPeriod | object | Information about the current period (for active and trial statuses, optional) |
start | string | ISO 8601 formatted date when the pause starts (for paused status, optional) |
end | string | ISO 8601 formatted date when the pause ends (for paused status, optional) |
canceledAt | string | ISO 8601 formatted date when the subscription was cancelled (for canceled status, optional) |
Item Object Properties
Property | Type | Description |
---|---|---|
id | string | ID of the item in the provider system (optional) |
price | object | Price object associated with this item |
quantity | number | Quantity of the item |
createdAt | string | ISO 8601 formatted date when the item was created (optional) |
Duration Object Properties
Property | Type | Description |
---|---|---|
amount | number | Amount of the unit for the duration |
unit | string | Unit 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
Property | Type | Description |
---|---|---|
id | string | Unique identifier for the price |
name | string | Name of the price plan |
description | string | Description of the price plan (optional) |
currency | string | ISO 4217 currency code |
amount | object | Price amount details (see Amount structure below) |
duration | object | Billing duration of the plan |
overrides | object | Amount overrides for specific currencies/countries |
Amount Structure
Property | Type | Description |
---|---|---|
model | string | Pricing model (e.g., 'fixed', 'tiered') |
currency | string | ISO 4217 currency code |
unit | number | Price per unit in cents (for fixed model) |
flat | number | Flat price not affected by quantity (optional) |
tiers | array | List of tiers (for tiered model) |
mode | string | Tier 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
Property | Type | Description |
---|---|---|
productId | string | ID 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
Property | Type | Description |
---|---|---|
id | string | Unique identifier for the family |
name | string | Customer-facing name of the product family |
description | string | Customer-facing description of the product family |
Product Structure
Property | Type | Description |
---|---|---|
id | string | Unique identifier for the product |
familyId | string | ID of the family to which the product belongs |
name | string | Name of the product |
description | string | Description 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.
Property | Type | Description |
---|---|---|
id | string | Unique identifier for the coupon |
code | string | Coupon code used for redemption (optional) |
name | string | Customer-facing name of the coupon (optional, defaults to code if not provided) |
duration | object | Defines how long the discount will be applied |
value | object | Defines the value of the coupon (percentage or fixed amount) |
redemptions | object | Statistics and limits for coupon redemptions (optional) |
expiresAt | string | ISO 8601 formatted date after which the coupon expires (optional) |
whitelist | object | Specifies conditions for which the coupon is applicable (optional) |
blacklist | object | Specifies 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:
Type | Description |
---|---|
forever | Coupon is valid during the lifetime of the subscription |
once | Coupon is valid only for a single charge (subscription cycle) |
period | Coupon is valid for a specified period after it's first applied |
cycle-amount | Coupon 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:
Property | Type | Description |
---|---|---|
amount | number | Amount of the unit for the duration |
unit | string | Unit of the duration: 'day', 'week', 'month', or 'year' |
Value
The value
property defines the discount amount of the coupon:
Type | Description |
---|---|
percent | Percentage discount |
amount | Fixed amount discount |
For percentage discounts:
Property | Type | Description |
---|---|---|
value | number | The percentage value of the discount |
currency | string | ISO 4217 currency code (optional) |
For fixed amount discounts:
Property | Type | Description |
---|---|---|
value | number | The fixed discount amount in the smallest currency unit |
currency | string | ISO 4217 currency code |
Redemptions
The redemptions
property tracks the usage and limits of the coupon:
Property | Type | Description |
---|---|---|
current | number | Number of times the coupon has been redeemed |
max | number | Maximum number of times the coupon can be redeemed (optional) |
Whitelist and Blacklist
These properties can be used to restrict the coupon's applicability:
Property | Type | Description |
---|---|---|
productIds | string | Array of product IDs for which the coupon is applicable/not applicable |
productFamilyIds | string | Array 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.
Integrations
This guide outlines how to integrate your payment provider directly with Churnkey's subscription management system without using the SDK. You'll implement a set of standardized functions that Churnkey will use to interact with your payment provider's API.
Data Retrieval
Retrieval endpoints are responsible for fetching individual items and listing multiple items for each data type. These functions will interact with your payment provider's API and return data in the format expected by Churnkey.