Embedding the flow
How to integrate the cancellation flow with your application or service
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.
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.3.3/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: {
platformId: "sub_H6X0WYrAzq2iGk",
},
});
or
window.ProsperStack.flow({
clientId: "acct_IrbFC8mvFtDAugC8k7uNlRn6",
subscription: {
platformId: "sub_H6X0WYrAzq2iGk",
},
});
Arguments
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.
The Payload 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 subscription platform, the payload object requires additional properties.
Subscription platform integration
subscription: Subscription
- (required) - Object containing details of the subscription being canceled.platformId
- (required) The ID of the subscription from your connected subscription platform 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.
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.currency?: string
- (optional) - Three-letter ISO currency code (i.e.usd
) thatmrr
is specified in.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 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.
Options
The Options
object accepts the following properties:
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
— (optional)A callback that is called when the cancellation flow is closed.
onError?: (err: Error) => void
— (optional)A callback that is called when an error occurs with the cancellation flow.
sign?: (payload: string) => string | Promise<string>
— (optional)A callback that is called with the JSON-encoded flow payload to enable request signing. Expects a hex HMAC-SHA256 signature digest to be returned.
displayMode?: "full_screen" | "modal"
— (optional)Launch the flow either full screen (default) or as a modal over the top of the current page. When presented as a modal, the flow layout changes slightly to accomodate the reduced dimensions, looking more like the mobile version. Requires
@prosperstack/flow
version 2.3.0.
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 abortedFlowSession
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 theFlowSession
object, see the API reference documentation 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: {
platformId: "sub_H6X0WYrAzq2iGk",
},
},
{
onCompleted: (result) => {
console.log("Flow complete!");
},
}
);
Using a Promise
flow({
clientId: "acct_IrbFC8mvFtDAugC8k7uNlRn6",
subscription: {
platformId: "sub_H6X0WYrAzq2iGk",
},
}).then((result) => {
console.log("Flow complete!");
});
Or with await
:
const result = await flow({
clientId: "acct_IrbFC8mvFtDAugC8k7uNlRn6",
subscription: {
platformId: "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.
Complete examples
Using a subscription platform integration
<button id="cancel">Cancel subscription</button>
<script>
async function cancel() {
const result = await flow({
clientId: "acct_IrbFC8mvFtDAugC8k7uNlRn6",
subscription: {
platformId: "sub_H6X0WYrAzq2iGk",
properties: {
is_professional: true,
num_contacts: 5800,
},
},
subscriber: {
properties: {
preferred_name: "Johnny",
},
},
});
switch (result.status) {
case "canceled":
// Process the 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,
num_contacts: 5800,
},
},
});
switch (result.status) {
case "canceled":
// Process the 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.platform_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>
Signing requests
In order to verify the authenticity of requests to begin a cancellation session, we recommend signing your requests with your account secret.
When calling the flow()
method, you can either provide a callback function to
the sign
option property or supply a SignedPayload
object in the payload
parameter.
The sign
callback
The sign
callback is called with the JSON-encoded payload and expects a hex
HMAC-SHA256 signature digest to be returned.
Here's an example of what calling a backend method to sign the request payload might look like using the sign property:
flow(
{
clientId: "acct_IrbFC8mvFtDAugC8k7uNlRn6",
subscription: {
platformId: "sub_H6X0WYrAzq2iGk",
},
},
{
sign: async (payload) => {
const response = await fetch("https://app.example.com/sign", {
body: {
payload,
},
});
const signature = await response.json();
return signature;
},
}
);
The SignedPayload object
Instead of using the sign
option, you can also pass in a SignedPayload
object to the flow()
method.
The SignedPayload
object should have the following properties:
payload: string
- A string containing a JSON-encoded Payload object.signature: string
- The hex HMAC-SHA256 signature digest.
Here's an example of what calling the flow()
method with a SignedPayload
might look like:
flow({
payload:
'{"clientId":"acct_IrbFC8mvFtDAugC8k7uNlRn6","subscription":{"platformId":"sub_H6X0WYrAzq2iGk"}}',
signature: "c9efbfb8121c892b5de8c4e7c0c9182db7f3d87aa4ee463191e06fe36f4b00be",
});
Generating the signature
The payload signature is a hex HMAC-SHA256 digest computed from the JSON-encoded flow payload 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: {
platformId: "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!
Content Security Policy (CSP)
If your application uses a Content Security Policy, you'll need to add the following directives to your policy:
connect-src
—https://api.prosperstack.com
frame-src
—https://app.prosperstack.com
And if loading the library via a <script>
tag from the CDN:
script-src
-https://cdn.prosperstack.com
Flow changelog
2.3.0
- Add flow
displayMode
option
2.2.1
- Add handler for restarting the loading spinner for flow rerouting
2.2.0
- Add
sign
property to flow options - Add support for new
platform
properties - Mark
paymentProvider
properties as@deprecated
for removal in next major release
2.1.2
- Hide loading spinner when flow initializes to prevent it showing through transparent backgrounds when using custom CSS
- Add
cancel_reason
and questionprimary
property to TypeScript type definitions
2.1.1
- Add TypeScript type definitions for offer details and metadata
2.1.0
- Add
onError
callback to receive any errors that occur during a flow - Properly export all TypeScript types from the root module
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 thesubscriber
andsubscription
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
.