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.
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";
If you don't want to use npm, you can load it with a script tag.
<script src="https://cdn.prosperstack.com/flow/2.1.2/flow.min.js"></script>
If loaded via script tag, the library will be available at window.ProsperStack.flow
.
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",
},
});
The flow
method accepts the following parameters:
payload: Payload | SignedPayload
- A Payload object, or a SignedPayload object if using signed requests.options?: Options
- (optional) - An Options object.clientId: string
- (required) – Your unique client ID. You can find your client ID in your ProsperStack 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.
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.
properties
- (optional) Object containing subscriber custom properties.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
.
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 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 unique identifier 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.
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?: (result: FlowResult) => void
— (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.onError?: (err: Error) => void
- A callback that is called when an error occurs with the cancellation flow.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.flow(
{
clientId: "acct_IrbFC8mvFtDAugC8k7uNlRn6",
subscription: {
paymentProviderId: "sub_H6X0WYrAzq2iGk",
},
},
{
onCompleted: (result) => {
console.log("Flow complete!");
},
}
);
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",
},
});
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.
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.
<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>
<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.
cancelSubscription();
break;
case "saved":
// The customer accepted an offer and didn't cancel. Churn
// prevented!
// The details of the accepted offer will be present in the
// response payload.
const offerDetails = result.flowSession.offer_accepted.details;
// Metadata stored on the ProsperStack offer is returned as
// an object.
const offerMetadata = result.flowSession.offer_accepted.metadata;
// You can use the offer details to make the appropriate
// changes to the customer's subscription.
switch (offerDetails.type) {
case "coupon":
applyCoupon({
amountOff: offerDetails.amount_off,
});
break;
case "trial_extension":
extendTrial({
days: offerDetails.days,
});
break;
case "change_plan":
changePlan({
newPlanId: offerDetails.payment_provider_plan_id,
});
break;
case "pause_subscription":
pauseSubscription({
length: offerDetails.interval_count,
});
break;
case "custom":
// Offer metadata can be used to pass custom data from
// a ProsperStack offer to your application.
const code = offerMetadata.code;
processCustomOffer(code);
break;
}
break;
case "incomplete":
// No action needed!
break;
}
}
document.getElementById("cancel").addEventListener("click", cancel);
</script>
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.
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",
});
If your application uses a Content Security Policy, you'll need to add the following directives to your policy:
connect-src
— https://api.prosperstack.comframe-src
— https://app.prosperstack.comAnd if loading the library via a <script>
tag from the CDN:
script-src
- https://cdn.prosperstack.comcancel_reason
and question primary
property to TypeScript type definitionsonError
callback to receive any errors that occur during a flowsubscriber
objectBREAKING CHANGES:
clientId
is now requiredOTHER CHANGES:
flowId
is now optionalonClosed
callback to options to handle flow closed eventproperties
in the subscriber
and subscription
objectsdeflected
to flow session result statusIn 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
.