npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@layeredapps/stripe-subscriptions

v1.0.71

Published

Stripe Subscription module for Dashboard

Downloads

4

Readme

Documentation for Stripe Subscriptions module

Shortcuts

Index

Introduction

Dashboard bundles everything a web app needs, all the "boilerplate" like signing in and changing passwords, into a parallel server so you can write a much smaller web app.

The Stripe Subscriptions module adds a complete interface for creating and managing your Stripe products, prices, subscriptions etc. Users can self-cancel their subscriptions at any time and you can nominate a 0+ day period allowing users to refund themselves too. You can optionally require a subscription and/or no unpaid invoices from all users outside of the /account/ and /administrator/ content. The Stripe API documentation supplements this documentation.

Import this module

Install the module with NPM:

$ npm install @layeredapps/stripe-subscriptions

Edit your package.json to activate the module:

"dashboard": {
  "modules": [
    "@layeredapps/stripe-subscriptions"
  ]
}

Setting up your Stripe credentials

You will need to retrieve various keys from Stripe. During development your webhook will be created automatically, but in production with multiple dashboard server instances they share a configured webhook:

- create your Stripe account and find your API keys
- create a webhook for https://your_domain/webhooks/subscriptions/index-subscription-data 
- environment STRIPE_JS=3|2|false
- environment STRIPE_KEY=sk_test_xxxxxxx
- environment STRIPE_PUBLISHABLE_KEY=pk_test_xxxxxxx
- environment SUBSCRIPTIONS_WEBHOOK_ENDPOINT_SECRET=whsec_xxxxxxxx

Configuring your products and prices

This module adds a complete interface for creating products and prices with one-time, recurring and tiered billing. Stripe's nomenclature and structure is used directly so for more information refer to the Stripe documentation and Stripe API documentation.

A subscription is created for one or more "Price" objects, which each hold pricing information for "Product" objects. These objects must be set active before they can be used by users.

  1. Administrator creates product
  2. Administrator activates product
  3. Administrator creates price(s)
  4. Administrator activates price(s)

Starting subscriptions

To create a subscription the user creates a billing profile ie a "Customer" object in Stripe. If your Price objects are free then they do not require payment information, but their Customer object must still be created with basic information.

You can check if a user has a suscription by including the x-has-subscription proxy script @stripe-subscriptions/src/proxy/x-has-subscription.js in your package.json. When requests are made to your application server the x-has-subscription header will be set true or false. Alternatively you can include x-subscrpitions or x-subscriptions-active to include their Stripe Subscription objects.

"dashboard": {
    "proxy": [
        "@stripe-subscriptions/src/proxy/x-has-subscription.js",
        "@stripe-subscriptions/src/proxy/x-subscriptions.js",
        "@stripe-subscriptions/src/proxy/x-subscriptions-active.js"
    ]
}

When a user needs to create a subscription you can respond with a HTTP redirect that Dashboard will follow, example:

HTTP/1.1 302 Found
Location: /select-plan

On a plan selection page you can automatically redirect after creating a billing profile by including a redirect-url URL parameter.

<a href="/account/subscriptions/create-billing-profile?redirect-url=/select-plan%3Fplan=gold%26other=data">Gold plan</a>

Your application uses the API to create their subscription to the Price(s) by POSTING to your Dashboard server:

{your_dashboard_server}/api/user/subscriptions/create-subscription?customerid=X

The POST data can include comma-delimited priceids and quantities if there are multiple billing items:

priceids=priceid
quantities=1
taxrateids=optional
couponid=optional
trial_period_days=optional
billing_cycle_anchor=optional
paymentmethodid=optional (will use default customer payment method)

The API will return their subscription object or throw an exception. You can view the exceptions in api.txt or the documentation website.

Example with plan selection screen:

In these examples "plans" are referred to, these are just identifiers you would have to correlate with Price objects (which are replacing Plan objects in Stripe).

  1. create a page on your application server where users select their plan eg /plans

  2. add the x-has-subscription proxy script to your package.json

  3. user registers and requests your /home path, where you determine they need to create a subscription and redirect to your /plans page

  4. user clicks plan button which links to /account/subscriptions/create-billing-profile?return-url=/plans%3Fplan=gold%26start=true

  5. after creating their payment information user is redirected back to your /plans?plan=gold&start=true, you observe the plan and start parameters and post to the Dashboard API to start their subscription:

    {your_dashboard_server}/api/user/subscriptions/create-subscription?customerid=X

POST data:

priceids=price1
quantities=1

Example with just one subscription type

  1. create a page on your application server where the user consents to creating a subscription eg /subscribe

  2. add the x-has-customer-billing and x-has-subscription proxy scripts to your package.json

  3. user registers and enters your application at your /home path where you determine they need to create a subscription

  4. if the user needs billing information you redirect to /account/subscriptions/create-billing-profile?return-url=/subscribe and after the user is redirected back to your /subscribe page

  5. create the subscription:

    {your_dashboard_server}/api/user/subscriptions/create-subscription?customerid=X

POST data:

priceids=price1
quantities=1

Example with subscriptions shared by users

If you were using this module in conjunction with the Organizations module you might want one person to create a subscription that is shared by members of their org.

  1. create a page on your application server where users select their plan and organization eg /subscribe

  2. add the x-has-subscription proxy script to your package.json, and the x-organizations proxy script from the Organizations module so you have that information for the plan

  3. user registers and requests your /home path, where you determine they need to create a subscription and redirect to /account/subscriptions/create-billing-profile?return-url=/subscribe

  4. after creating their payment information user is redirected back to your /subscribe page and you create the subscription:

    {your_dashboard_server}/api/user/subscriptions/create-subscription?customerid=X

POST data:

priceids=price1
quantities=1
  1. store the organization and subscription ids in your database so you can identify when users are members of an organization with a subscription

Example with metered billing

When you use metered billing prices are set based on usage amount, eg disk storage on a server.

You can set usage amounts using the API:

{your_dashboard_server}/api/user/subscriptions/create-usage-record?subscriptionid=X

POST data:

subscriptionitemid=X
quantity=8
action=increment|set

The subscription item id can be found in subscription.items.data array, eg subscription.items.data[0].id. You can include the users' subscription information in requests Dashboard makes to your server by adding the x-subscriptions proxy script in your package.json so this information is automatically sent to your application server.

Customizing billing information entry

You can adjust certain parts of the payment information collection with environment variables.

Use AUTOMATIC_BILLING_PROFILE_DESCRIPTION=true to skip customers assigning a description to their profiles.

Use AUTOMATIC_BILLING_PROFILE_FULL_NAME=true to use the full name from the user's profile which you can collect at registration. The user can update their profile to keep these matched.

Use AUTOMATIC_BILLING_PROFILE_EMAIL=true to use the contact email from the user's profile which you can collect at registration. The user can update their profile with a different email address if necessary.

Use REQUIRE_BILLING_PROFILE_ADDRESS=false to disable the address collection. You can enable it on a per-request basis by creating a server script and overriding the global setting:

module.exports = {
    after: (req) => {
        if (!req.account) {
            return
        }
        if (some condition you determine) {
            req.requireBillingProfileAddress = true
        }
    }
}

If you use AUTOMATIC_BILLING_PROFILE_EMAIL and AUTOMATIC_BILLING_PROFILE_FULL_NAME but the user does not have a profile they provide those values as normal. To ensure the user has a profile with this information use REQUIRE_PROFILE=true and USER_PROFILE_FIELDS=contact-email,full-name to collect it at registration.

Styling the Stripe elements

Using StripeJS version 3 sensitive fields like credit card numbers are created by Stripe in nested iframes and styled using a JavaScript object passed to their script.

You can specify your own JavaScript styling on your application server at /stripe-element-style.json. This module will automatically detect if the browser is in light or dark mode, or switched mode, and apply your settings:

{
    "light": {
        "base": {
            "color": "#666666",
            "fontFamily": "\"Helvetica Neue\", Helvetica, sans-serif",
            "fontSmoothing": "antialiased",
            "fontSize": "16px",
            "::placeholder": {
                "color": "#EEEEEE"
            }
        },
        "invalid": {
            "color": "#990000",
            "iconColor": "#fa755a"
        }
    },
    "dark": {
        "base": {
            "color": "#666666",
            "fontFamily": "\"Helvetica Neue\", Helvetica, sans-serif",
            "fontSmoothing": "antialiased",
            "fontSize": "16px",
            "::placeholder": {
                "color": "#EEEEEE"
            }
        },
        "invalid": {
            "color": "#990000",
            "iconColor": "#fa755a"
        }
    }
}

Provided server, content and proxy handlers

This module comes with some convenience scripts you can add to your package.json:

| Type | Script path | Description | |----------|-----------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | proxy | @layeredapps/stripe-subscriptions/src/proxy/x-customers.js | Dashboard will bundle the user's Customer objects in x-customers header. | | proxy | @layeredapps/stripe-subscriptions/src/proxy/x-has-customer-billing.js | Sets x-has-customer-billing to true or false if the user has a Customer object with payment method. | | proxy | @layeredapps/stripe-subscriptions/src/proxy/x-has-customer.js | Sets x-has-customer to true or false if the user has a Customer object. | | proxy | @layeredapps/stripe-subscriptions/src/proxy/x-has-subscription.js | Sets x-has-subscription to true or false if the user has a subscription that is not canceled or canceling. | | proxy | @layeredapps/stripe-subscriptions/src/proxy/x-invoices-latest.js | Dashboard will bundle the user's latest Invoice objects in x-invoices-latest header. | | proxy | @layeredapps/stripe-subscriptions/src/proxy/x-invoices-open.js | Dashboard will bundle the user's open Invoice objects in x-invoices-open header. | | proxy | @layeredapps/stripe-subscriptions/src/proxy/x-invoices.js | Dashboard will bundle the user's Invoice objects in x-invoices header. | | proxy | @layeredapps/stripe-subscriptions/src/proxy/x-prices.js | Dashboard will bundle all active prices in x-prices header. | | proxy | @layeredapps/stripe-subscriptions/src/proxy/x-subscriptions-active.js | Dashboard will bundle all the user's active subscriptions in x-subscriptions-active header. | | proxy | @layeredapps/stripe-subscriptions/src/proxy/x-subscriptions.js | Dashboard will bundle all the user's subscriptions in x-subscriptions header. | | server | @layeredapps/stripe-subscriptions/src/server/bind-stripe-key.js | The Stripe API key object will be bound to req.stripeKey |. | | server | @layeredapps/stripe-subscriptions/src/server/check-before-cancel-subscription.js | Require users complete steps, such as deleting data, before canceling their subscription. Set a CHECK_BEFORE_CANCEL_SUBSCRIPTION path such as /check-delete on your Application server, Dashboard will query this API passing ?subscriptionid=xxxxx and you may respond with { "redirect": "/your-delete-requirements" } or { "redirect": false }" to enforce the requirements. | | server | @layeredapps/stripe-subscriptions/src/server/require-payment-confirmation.js | If the user has an invoice requiring payment confirmation they will be redirected to provide it. They can still access /account and /administrator routes (if an administrator) | | server | @layeredapps/stripe-subscriptions/src/server/require-payment.js | If the user has an invoice requiring payment they will be redirected to provide it. They can still access /account and /administrator routes (if an administrator) |

This module includes "private" content scripts that are configured automatically:

|----------|----------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | content | @layeredapps/stripe-subscriptions/src/content/embed-stripe-element-style.js | When STRIPE_JS=3 this loads "/stripe-elements-style.json" from this module or your application server to style Stripe's special fields created with stripe.js. The style object is cached for 60 seconds. |

Storage engine

By default this module will share whatever storage you use for Dashboard. You can specify nothing, specify an alternate storage backend, or specify the same type with a separate database.

SUBSCRIPTIONS_STORAGE=mysql
SUBSCRIPTIONS_DATABASE_URL=mysql://username:password@host:port/db

If your Dashboard is configured with database read replication servers this module will follow that configuration. You can also specify module-specific read replication:

SUBSCRIPTIONS_STORAGE_REPLICATION=true
SUBSCRIPTIONS_DATABASE_URL=postgres://1.0.0.0:5432/subscriptions
SUBSCRIPTIONS_READ_DATABASE_URL1=postgres://1.0.0.1:5432/subscriptions
SUBSCRIPTIONS_READ_DATABASE_URL2=postgres://1.0.0.2:5432/subscriptions
SUBSCRIPTIONS_READ_DATABASE_URL3=postgres://1.0.0.3:5432/subscriptions

Access the API

Dashboard and official modules are completely API-driven and you can access the same APIs on behalf of the user making requests. You perform GET, POST, PATCH, and DELETE HTTP requests against the API endpoints to fetch or modify data. This example fetches the user's subscriptions using NodeJS, you can do this with any language:

You can view API documentation within the NodeJS modules' api.txt files, or on the documentation site.

const subscriptions = await proxy(`/api/user/subscriptions/subscriptions?accountid=${accountid}&all=true`, accountid, sessionid)

const proxy = util.promisify((path, accountid, sessionid, callback) => {
    const requestOptions = {
        host: 'dashboard.example.com',
        path: path,
        port: '443',
        method: 'GET',
        headers: {
            'x-application-server': 'application.example.com',
            'x-application-server-token': process.env.APPLICATION_SERVER_TOKEN,
            'x-accountid': accountid,
            'x-sessionid': sessionid
        }
    }
    const proxyRequest = require('https').request(requestOptions, (proxyResponse) => {
        let body = ''
        proxyResponse.on('data', (chunk) => {
            body += chunk
        })
        return proxyResponse.on('end', () => {
            return callback(null, JSON.parse(body))
        })
    })
    proxyRequest.on('error', (error) => {
        return callback(error)
    })
    return proxyRequest.end()
  })
}