@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 objection
Usage
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
Subscription
model 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
Customer
class - define a relationship named
subscriptions
linking to yourSubscription
model
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
Subscription
class - define a relationship named
owner
linking to yourUser
model
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.