Embedding the flow

How to integrate the cancellation flow with your application

To pass cancellations through your ProsperStack cancellation flow, you'll need to integrate the ProsperStack JavaScript library with your application.

IMPORANT: Currently, if using the Stripe or Chargebee integrations, ProsperStack does not cancel the customer subscription for you. Make sure that you still process the cancellation with your payment processor after a cancellation flow is complete!

There are two options for including the ProsperStack JavaScript library in your application.

Install from npm

You can install the @prosperstack/flow package from npm.

$ npm install @prosperstack/flow

or, with yarn:

yarn add @prosperstack/flow

In your application, import the library:

import flow from '@prosperstack/flow';

Load via script tag

If you don't want to use npm, you can load it with a script tag.

<script src="https://cdn.prosperstack.com/flow/1.0.0/flow.min.js"></script>

If loaded via script tag, the library will be available at window.ProsperStack.flow.

Usage

Call the imported flow method or window.ProsperStack.flow if loaded via script tag.

flow({
  flowId: 'flow_Pic0yGmlYS1rV5hdf7uR8YNM',
  subscription: {
    paymentProviderId: 'sub_H6X0WYrAzq2iGk',
  },
});

or

window.ProsperStack.flow({
  flowId: 'flow_Pic0yGmlYS1rV5hdf7uR8YNM',
  subscription: {
    paymentProviderId: 'sub_H6X0WYrAzq2iGk',
  },
});

Arguments

The flow method accepts the following parameters:

The Payload object

  • flowId: string - (required) - The unique ID of your cancellation flow. You can find this in your ProsperStack cancellation flow settings.

Depending on your payment processor, the payload object requires additional properties.

Payment processor integration (Stripe, Chargebee, etc.)
  • subscription: Subscription - (required) - Object containing details of the subscription being canceled.

    • paymentProviderId - (required) The ID of the payment processor subscription that is being canceled.
Custom integration
  • subscriber: Subscriber - (required) - Object containing details of the subscriber that is canceling.

    • internalId: string | number - (required) - The internal ID in your application of the subscriber that is canceling.
    • name: string - (required) - Name of the subscriber that is canceling.
    • email: string - (required) - Email of the subscriber that is canceling.
  • subscription?: Subscription - (optional) - Object containing details of the subscription that is being canceled.

    • mrr?: number - (optional) - MRR of the subscription that is being canceled.
    • trial?: boolean - (optional) - Designate that the subscription being canceled is a free trial.

Options

The Options object accepts the following properties:

  • skipOffers?: string[] - (optional) - An array of offer IDs that should not be presented.
  • testMode?: boolean - (optional) - Enable test mode.
  • onCompleted?: FlowResult - (optional) - A callback that is called when the cancellation flow is completed. If not supplied, the method will return Promise<FlowResult>.

When the flow is complete

To be notified when the cancellation flow is complete, the library can accept a callback or return a Promise.

The callback or Promise will provide a FlowResult object, which contains the following properties:

  • status: 'canceled' | 'saved' | 'incomplete' - The result of the cancellation flow.
  • flowSession: FlowSession - The completed or aborted FlowSession object. This object contains details of the offers presented and accepted for the cancellation flow session.

The possible values of status are:

  • canceled - The customer chose to cancel their subscription.
  • saved - The customer accepted an offer and chose to keep their subscription.
  • incomplete - The customer closed the cancellation flow without completing it.

Using a callback

flow(
  {
    flowId: 'flow_Pic0yGmlYS1rV5hdf7uR8YNM',
    subscription: {
      paymentProviderId: 'sub_H6X0WYrAzq2iGk',
    },
  },
  {
    onCompleted: (result) => {
      console.log('Flow complete!');
    },
  }
);

Using a Promise

flow({
  flowId: 'flow_Pic0yGmlYS1rV5hdf7uR8YNM',
  subscription: {
    paymentProviderId: 'sub_H6X0WYrAzq2iGk',
  },
}).then((result) => {
  console.log('Flow complete!');
});

Or with await:

const result = await flow({
  flowId: 'flow_Pic0yGmlYS1rV5hdf7uR8YNM',
  subscription: {
    paymentProviderId: 'sub_H6X0WYrAzq2iGk',
  },
});

Test mode

Test mode makes it possible to test your integration without performing any actual cancellations. When test mode is active, the onCompleted callback or Promise will behave normally but no actions (e.g. cancellation or offer acceptance) are performed on ProsperStack. Additionally, there is no validation of the paymentProviderId.

Complete examples

Using a payment processor integration

function cancel() {
  window.ProsperStack.flow(
    {
      flowId: 'flow_Pic0yGmlYS1rV5hdf7uR8YNM',
      subscription: {
        paymentProviderId: 'sub_H6X0WYrAzq2iGk',
      },
    },
    {
      onCompleted: function (result) {
        switch (result.status) {
          case 'canceled':
            // Process the payment provider cancellation and whatever other logic
            // your application requires when a customer cancels.
            break;

          case 'saved':
            // The customer accepted an offer and didn't cancel. Churn prevented!
            // For most offers, no further action is needed.
            console.log(result.flowSession);

            // For custom offers, additional action is required.
            if (
              result.flowSession.offer_accepted.id ===
              'offr_9bxR7jLsMvnA6NsoRjGlk5sh'
            ) {
              // Whatever logic your application needs to process the custom offer.
            }
            break;

          case 'incomplete':
            // No action needed!
            break;
        }
      },
    }
  );
}

<button onclick="cancel()">Cancel subscription</button>;

Using the custom integration

function cancel() {
  window.ProsperStack.flow(
    {
      flowId: 'flow_Pic0yGmlYS1rV5hdf7uR8YNM',
      subscriber: {
        internalId: '31245',
        name: 'Jane Smith',
        email: 'jane@example.com',
      },
      subscription: {
        mrr: 99.0,
        trial: false,
      },
    },
    {
      onCompleted: function (result) {
        switch (result.status) {
          case 'canceled':
            // Process the payment provider cancellation and whatever other logic
            // your application requires when a customer cancels.
            break;

          case 'saved':
            // The customer accepted an offer and didn't cancel. Churn prevented!
            if (
              result.flowSession.offer_accepted.id ===
              'offr_9bxR7jLsMvnA6NsoRjGlk5sh'
            ) {
              // Whatever logic your application needs to process the custom offer.
            }
            break;

          case 'incomplete':
            // No action needed!
            break;
        }
      },
    }
  );
}

<button onclick="cancel()">Cancel subscription</button>;

Signing requests

If your flow has enabled the option to automatically cancel subscriptions in your payment processor, you must sign your requests so that ProsperStack can verify their authenticity.

When calling the flow method, supply a SignedPayload object in the payload parameter.

The SignedPayload object

  • payload: string - A string containing a JSON-encoded Payload object.
  • signature: string - The hex HMAC-SHA256 signature digest.

The signature property expects a hex HMAC-SHA256 digest computed from the JSON-encoded payload property and your flow's secret key.

Your flow's secret key can be found in your ProsperStack cancellation flow settings.

Computing the HMAC digest will look a little different depending on your server language, but the following is an example of what it might look like in Node.js:

import crypto from 'crypto';

const payload = JSON.stringify({
  flowId: 'flow_Pic0yGmlYS1rV5hdf7uR8YNM',
  subscription: {
    paymentProviderId: 'sub_H6X0WYrAzq2iGk',
  },
});

const secret = 'my flow secret';

const digest = crypto
  .createHmac('sha256', secret)
  .update(payload)
  .digest('hex');

Creating the signature should take place on your backend server, so that the secret is not exposed to the client. Make sure not to do this in the browser!

Once you've computed the signature and have your JSON-encoded payload, you can use them in place of the standard Payload object.

For example, given the payload:

{
  flowId: 'flow_Pic0yGmlYS1rV5hdf7uR8YNM',
  subscription: {
    paymentProviderId: 'sub_H6X0WYrAzq2iGk'
  }
}

A signed version would look like:

flow({
  payload:
    '{"flowId":"flow_Pic0yGmlYS1rV5hdf7uR8YNM","subscription":{"paymentProviderId":"sub_H6X0WYrAzq2iGk"}}',
  signature: 'c9efbfb8121c892b5de8c4e7c0c9182db7f3d87aa4ee463191e06fe36f4b00be',
});

Don't let customers slip away.