0.0.10 • Published 4 years ago

@sprdv/cashier v0.0.10

Weekly downloads
-
License
MIT
Repository
github
Last release
4 years ago

Cashier

npm version npm downloads npm dependencies code style

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:

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) extending Cashier.Customer
  • Subscription model extending Cashier.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 your Subscription 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 your User 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.

0.0.10

4 years ago

0.0.9

4 years ago

0.0.8

4 years ago

0.0.7

4 years ago

0.0.6

4 years ago

0.0.5

4 years ago

0.0.3

4 years ago

0.0.4

4 years ago

0.0.2

4 years ago

0.0.1

4 years ago