Headlessly
Billing

Customer

Stripe Customer -- the billing identity that links an Organization to payment processing.

Schema

import { Noun } from 'digital-objects'

export const Customer = Noun('Customer', {
  name: 'string!',
  email: 'string!#',
  organization: '-> Organization',
  stripeCustomerId: 'string##',
  subscriptions: '<- Subscription.customer[]',
  invoices: '<- Invoice.customer[]',
  payments: '<- Payment.customer[]',
  paymentMethod: 'string',
  currency: 'string',
  contact: '-> Contact',
  taxExempt: 'string',
})

Fields

FieldTypeRequiredDescription
namestringYesBilling name -- typically the company or individual name
emailstringYesBilling email address (indexed)
paymentMethodstringNoDefault payment method identifier (e.g. pm_card_visa)
currencystringNoPreferred currency code (e.g. usd, eur)
taxExemptstringNoTax exemption status (e.g. none, exempt, reverse)
stripeCustomerIdstringNoStripe Customer ID (unique, indexed) -- populated automatically on creation

Relationships

FieldDirectionTargetDescription
organization->OrganizationThe company this customer belongs to
contact->ContactThe CRM contact linked to this billing entity
subscriptions<-Subscription.customer[]All subscriptions for this customer
invoices<-Invoice.customer[]All invoices billed to this customer
payments<-Payment.customer[]All payments received from this customer

Verbs

VerbEventDescription
createCreatedCreate a new customer (also creates in Stripe)
updateUpdatedUpdate customer fields (syncs to Stripe)
deleteDeletedSoft-delete the customer

Verb Lifecycle

Every verb follows the full conjugation pattern -- execute, before hook, after hook:

import { Customer } from '@headlessly/billing'

// Execute
await Customer.create({
  name: 'Acme Dev',
  email: 'billing@acme.dev',
  organization: 'org_fX9bL5nRd',
})

// Before hook -- runs before creation is processed
Customer.creating(customer => {
  console.log(`About to create customer ${customer.name}`)
})

// After hook -- runs after the Stripe customer is created
Customer.created(customer => {
  console.log(`Customer ${customer.name} created with Stripe ID ${customer.stripeCustomerId}`)
})

Cross-Domain Patterns

Customer bridges the gap between CRM and Billing. When a deal closes, the CRM Contact becomes a billing Customer:

import { Deal } from '@headlessly/crm'

Deal.won((deal, $) => {
  const contact = await $.Contact.get(deal.contact)
  await $.Customer.create({
    name: contact.name,
    email: contact.email,
    organization: deal.organization,
    contact: contact.$id,
  })
})

Customer also connects downstream to Support -- when a customer submits a ticket, their billing context (plan, subscription status, payment history) is immediately available:

import { Customer } from '@headlessly/billing'

const customer = await Customer.get('customer_fX9bL5nRd', {
  include: ['subscriptions', 'invoices', 'payments'],
})

Query Examples

SDK

import { Customer } from '@headlessly/billing'

// Find all customers
const customers = await Customer.find()

// Get a specific customer
const customer = await Customer.get('customer_fX9bL5nRd')

// Find customers by currency
const euroCustomers = await Customer.find({ currency: 'eur' })

// Find customer by Stripe ID
const customer = await Customer.find({ stripeCustomerId: 'cus_PxN7kRmT' })

MCP

headless.ly/mcp#search
{ "type": "Customer", "filter": { "currency": "usd" } }
headless.ly/mcp#fetch
{ "type": "Customer", "id": "customer_fX9bL5nRd", "include": ["subscriptions"] }
headless.ly/mcp#do
const customers = await $.Customer.find({ currency: 'usd' })

REST

GET https://headless.ly/~acme/customers?currency=usd
GET https://headless.ly/~acme/customers/customer_fX9bL5nRd
POST https://headless.ly/~acme/customers
Content-Type: application/json

{ "name": "Acme Dev", "email": "billing@acme.dev" }

Event-Driven Patterns

React to customer lifecycle events across domains:

import { Customer } from '@headlessly/billing'

// When a customer is created, set up their default subscription
Customer.created((customer, $) => {
  await $.Subscription.create({
    customer: customer.$id,
    plan: 'plan_Nw8rTxJv',
    status: 'Trialing',
    currentPeriodStart: new Date(),
    currentPeriodEnd: new Date(Date.now() + 14 * 86400000),
    startedAt: new Date(),
  })
})

// Track customer creation as an analytics event
Customer.created((customer, $) => {
  $.Event.create({
    type: 'billing.customer_created',
    data: { name: customer.name, organization: customer.organization },
  })
})

Stripe Sync

Customer is the root of the Stripe billing graph. Every write to a Customer in headless.ly is mirrored to Stripe, and every Stripe webhook (customer.created, customer.updated, customer.deleted) is processed back into the headless.ly event log. The stripeCustomerId field is the bidirectional link.

On this page