Embedding the flow

How to integrate the cancellation flow with your application

NOTE: Version 2.0.0 of the flow library introduces some breaking changes. See the upgrade guide for information on upgrading from previous versions.

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

IMPORANT: By default, ProsperStack does not cancel the customer subscription for you when using a billing platform integration (Stripe, Chargebee, etc.). You can enable automatic cancellations in your flow settings. If this is not enabled, 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/2.0.2/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({
  clientId: 'acct_IrbFC8mvFtDAugC8k7uNlRn6',
  subscription: {
    paymentProviderId: 'sub_H6X0WYrAzq2iGk',
  },
});

or

window.ProsperStack.flow({
  clientId: 'acct_IrbFC8mvFtDAugC8k7uNlRn6',
  subscription: {
    paymentProviderId: 'sub_H6X0WYrAzq2iGk',
  },
});

Arguments

The flow method accepts the following parameters:

The Payload object

  • clientId: string - (required) – Your unique client ID. You can find your client ID in your cancellation flow settings page.
  • flowId: string - (optional) - The unique ID of a cancellation flow to use. Bypasses your flow routing rules.

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.
    • properties - (optional) Object containing subscription custom properties.
  • subscriber: Subscriber - (optional) - Object containing details of the subscriber that is canceling.

Working with multiple flows

When using multiple flows, you only need to specify a clientId. Your flow route rules will determine which flow customers are routed to. If you want to bypass routing rules and send customers to a specific cancellation flow, you can specify the flowId in addition to the clientId.

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.
    • properties - (optional) Object containing subscriber custom properties.
  • 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.
    • properties - (optional) Object containing subscription custom properties.
Custom properties

Custom properties allow you to attach custom data to your subscriber and subscription objects for more powerful targeting and segmentation.

You'll need to create custom properties in ProsperStack before storing values in them. Learn more about managing custom properties.

Specify custom properties to attach to either a subscriber or subscription with the properties object. Use an object with the name of the property as the key, for example:

{
  'Number of contacts': 5800,
  'Is professional': true,
  'Last contacted': '2021-05-04',
  'Preferred name': 'Johnny'
}

Learn more about formatting values for custom properties.

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>.
  • onClosed?: void — A callback that is called when the cancellation flow is closed.

When the flow is complete

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

NOTE: If using a confirmation step, the Promise or onCompleted callback may resolve before the flow is completely closed by the subscriber. See the confirmation step notes for more details.

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

  • status: 'canceled' | 'saved' | 'deflected' | '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 as well as all survey data for the cancellation flow session. For more details on the structure of the FlowSession object, see the API reference documention for flow sessions.

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.
  • deflected - The customer clicked the action button in a deflection card.
  • incomplete - The customer closed the cancellation flow without completing it.

Using a callback

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

Using a Promise

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

Or with await:

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

Showing a confirmation message after the flow

After a subscriber completes the flow, you can either use the status property of the flow result to display a confirmation message in your application, or you can add a confirmation step to have ProsperStack show a confirmation message for you.

If you use ProsperStack's confirmation step, note that the Promise (or onCompleted callback) will resolve after the subscriber cancels their subscription or accepts an offer, which occurs before the confirmation step is shown to the subscriber. If you have logic in your application that happens after the subscriber exits out of the cancellation flow (for example, redirecting to a different page in your application), we recommend using the onClosed callback to be notified when the subscriber closes the confirmation step and has completely exited out of the cancellation flow.

Test mode option

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

Note that the testMode option is different than your ProsperStack account's test mode. To integrate the flow library with your account's test mode, provide the client ID found in your account's test mode view for the clientId property in the flow payload object.

Complete examples

Using a payment processor integration

<button id="cancel">Cancel subscription</button>

<script>
  async function cancel() {
    const result = await flow({
      clientId: 'acct_IrbFC8mvFtDAugC8k7uNlRn6',
      subscription: {
        paymentProviderId: 'sub_H6X0WYrAzq2iGk',
        properties: {
          'Is professional': true,
          'Number of contacts': 5800,
        },
      },
      subscriber: {
        properties: {
          'Preferred name': 'Johnny',
        },
      },
    });

    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;
    }
  }

  document.getElementById('cancel').addEventListener('click', cancel);
</script>

Using the custom integration

<button id="cancel">Cancel subscription</button>

<script>
  async function cancel() {
    const result = await flow({
      clientId: 'acct_IrbFC8mvFtDAugC8k7uNlRn6',
      subscriber: {
        internalId: '31245',
        name: 'Jane Smith',
        email: 'jane@example.com',
        properties: {
          'Preferred name': 'Jane',
        },
      },
      subscription: {
        mrr: 99.0,
        trial: false,
        properties: {
          'Is professional': true,
          'Number of contacts': 5800,
        },
      },
    });

    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;
    }
  }

  document.getElementById('cancel').addEventListener('click', cancel);
</script>

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 client secret.

Your client secret can be found in the Settings page of your ProsperStack dashboard under the Account section.

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({
  clientId: 'acct_IrbFC8mvFtDAugC8k7uNlRn6',
  subscription: {
    paymentProviderId: 'sub_H6X0WYrAzq2iGk',
  },
});

const secret = 'my client 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:

{
  clientId: 'acct_IrbFC8mvFtDAugC8k7uNlRn6',
  subscription: {
    paymentProviderId: 'sub_H6X0WYrAzq2iGk'
  }
}

A signed version would look like:

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

Content Security Policy (CSP)

If your application uses a Content Security Policy, you'll need to add the following directives to your policy:

And if loading the library via a <script> tag from the CDN:

Flow changelog

2.0.2

  • Fix TypeScript definition for payload subscriber object

2.0.1

BREAKING CHANGES:

  • Payload clientId is now required

OTHER CHANGES:

  • Payload flowId is now optional
  • Add onClosed callback to options to handle flow closed event

1.1.0

  • Improve error handling and add better user-facing error messages
  • Add support for properties in the subscriber and subscription objects

1.0.1

  • Add deflected to flow session result status

1.0.0

  • Initial release

Upgrade guides

2.0.0

In order to support the new multiple flows feature, version 2.0.0 now requires you to specify your clientId in the payload. You can continue to specify a flowId to direct customers to a specific flow, or omit it to route to your default flow.

You can find your client ID in the Settings screen under the Account section.

Version 2.0.0 also changes the secret key used for signed requests — you should now use the client secret found in the Account section in the Settings page to compute signatures.

Version 2.0.0 also adds an onClosed callback option so you can be notified when the cancellation flow is completely closed. This may be useful when using the new confirmation step feature.

Previous versions of the flow library (< 2.0.0) will continue to function as normal, so you'll only need to make these changes when you upgrade to 2.0.0.

Don't let customers slip away.