@sprdv/cashier v0.0.10
Cashier
Cashier provides an expressive, fluent interface to Stripe's subscription billing services. It is heavily inspired by Laravel Cashier.
Cashier is built on an SQL-friendly ORM called objection.js.
Installation
Cashier can be installed using npm or yarn. Cashier uses objection but doesn't ship with it.
npm install @sprdv/cashier objectionUsage
First you need to give your Stripe secret API key to Cashier:
import { Cashier } from '@sprdv/cashier';
Cashier.withKey('sk_your_secret_api_key');Cashier accepts an optional configuration object:
Cashier.withConfig({
currency: 'usd'
});The following options are available:
- currency: three-letter ISO code (available options). Defaults to
usd.
As Cashier relies on objection, you will need to have it setup in your project as well:
import Knex from 'knex';
import { Model } from 'objection';
import { knexSnakeCaseMappers } from 'objection';
const knex = Knex({
client: 'sqlite3',
connection: {
filename: './db.sqlite'
},
migrations: {
tableName: 'migrations'
},
...knexSnakeCaseMappers()
});
Model.knex(knex);Cashier uses camelCased names in code. If your database uses snake_cased names,
you will need to use objection's knexSnakeCaseMappers function, which will take care of the conversion.
More details can be found in objection's documentation.
Models
Two models are required for Cashier to work:
- Billable model (usually
User) extendingCashier.Customer Subscriptionmodel extendingCashier.Subscription
Extending Cashier's models will add various methods to allow you to perform common billing tasks, such as creating subscriptions, applying coupons, and updating payment method information.
For each model, you will need to update your knex migration and your objection model.
Customer
In the following code examples, we will assume that your billable model is the User class.
Here is how your User migration could look like:
export async function up(knex) {
await knex.schema.createTable('user', (table) => {
table.increments('id');
// ...
table.string('stripe_id');
table.string('card_brand');
table.string('card_last_four', 4);
table.timestamp('trial_ends_at').nullable();
table.timestamp('created_at').defaultTo(knex.raw('CURRENT_TIMESTAMP')).notNullable();
// Indexes
table.unique(['stripe_id']);
});
}
export async function down(knex) {
return knex.schema.dropTableIfExists('user');
}Your User model should:
- extend Cashier's
Customerclass - define a relationship named
subscriptionslinking to yourSubscriptionmodel
Here is how your User model could look like:
import { Model } from 'objection';
import { Customer as C } from '@sprdv/cashier';
export class User extends C {
static get tableName() {
return 'user';
}
static get relationMappings() {
const { Subscription } = require('./subscription');
return {
subscriptions: {
relation: Model.HasManyRelation,
modelClass: Subscription,
join: {
from: 'user.id',
to: 'subscription.userId'
}
}
};
}
}You're free to add other relationships, custom methods, etc. as in any objection model.
Subscription
Here is how your Subscription migration could look like:
export async function up(knex) {
await knex.schema.createTable('subscription', (table) => {
table.increments('id');
table.integer('user_id').notNullable().unsigned();
table.string('name').notNullable();
table.string('stripe_id').notNullable();
table.string('stripe_status').notNullable();
table.string('stripe_plan').notNullable();
table.integer('quantity').notNullable();
table.timestamp('ends_at').nullable();
table.timestamp('trial_ends_at').nullable();
table.timestamp('created_at').defaultTo(knex.raw('CURRENT_TIMESTAMP')).notNullable();
// Indexes
table.index(['user_id', 'stripe_status']);
// Foreign keys
table.foreign('user_id')
.references('id')
.inTable('user')
.onDelete('CASCADE');
});
}
export async function down(knex) {
return knex.schema.dropTableIfExists('subscription');
}Your Subscription model should:
- extend Cashier's
Subscriptionclass - define a relationship named
ownerlinking to yourUsermodel
Here is how your Subscription model could look like:
import { Model } from 'objection';
import { Subscription as S } from '@sprdv/cashier';
export class Subscription extends S {
static get tableName() {
return 'subscription';
}
static get relationMappings() {
const { User } = require('./user');
return {
owner: {
relation: Model.BelongsToOneRelation,
modelClass: User,
join: {
from: 'subscription.userId',
to: 'user.id'
}
}
};
}
}Documentation
The available objects and methods are similar to the ones described in Laravel Cashier's documentation.
The following examples are demonstrating a few of Cashier's core features.
Subscriptions
Creating subscriptions
To create a subscription, first retrieve an instance of your billable model, which typically will be an instance of User.
Once you have retrieved the model instance, you may use the newSubscription method to create the model's subscription:
const user = await User.query().findById(1);
await user.newSubscription('default', 'premium').create(paymentMethod);Checking Subscription Status
Once a user is subscribed to your application, you may easily check their subscription status using a variety of convenient methods.
First, the subscribed method returns true if the user has an active subscription, even if the subscription is currently within its trial period:
const isSubscribed = await user.subscribed('default');
if (isSubscribed) {
//
}If you would like to determine if a user is still within their trial period, you may use the onTrial method.
This method can be useful for displaying a warning to the user that they are still on their trial period:
const subscription = await user.subscription('default');
if (subscription.onTrial()) {
//
}Canceling Subscriptions
To cancel a subscription, call the cancel method on the user's subscription:
const subscription = await user.subscription('default');
await subscription.cancel();Webhooks
This library currently doesn't support handling Stripe webhooks.