The Safe Surfer deployment supports several different ways of restricting accounts based on their billing status:
To try out chargebee billing, you can create a free test instance. Chargebee handles many different billing gateways and provides a framework and UI to work with every aspect of selling subscriptions or products.
After creating a test instance, you should be prompted to set up some products. You can proceed with this if you like, but we’ll create our own ones in this tutorial.
To integrate Safe Surfer with Chargebee, first create an API key. Make sure it’s a “Full Access” key. Then, add the key and your chargebee site to your values.yaml
. At the same time, create a strong password which we’ll use to set up the webhook next:
api:
accounts:
chargebee:
site: example-test # for example-test.chargebee.com
apiKey: my-api-key
webhookPassword: generate-a-strong-password
Make sure to upgrade the helm chart. Then, create a webhook on Chargebee. The Webhook URL
should be https://api.ss.example.com/v2/webhooks/user-subscription
, changing api.ss.example.com
to your own API domain. You should Protect webhook URL with basic authentication
, then use the webhookPassword
from your values as the password. Select Version 2
, set as primary, then select all subscription-related events to be sent. Once done, it should look something like this:
You can now test the webhook. You’ll most likely get a 500 response because the events don’t necessarily exist, but the Response received
should look something like the following:
Headers
HTTP/1.1 500 Internal Server Error
Date: Tue, 28 Mar 2023 03:11:38 GMT
Content-Type: application/json
Content-Length: 115
Connection: keep-alive
Vary: Origin
Strict-Transport-Security: max-age=15724800; includeSubDomains
Body
{"meta":{"timestamp":1679973098,"code":500,"errorCode":"","message":"Couldn't post user subscription"},"spec":null}
Warning To reduce the amount of requests made to Chargebee’s API, the API caches product catalog resources such as plans and addons. After modifying a plan or addon, you should re rollout the API, e.g.
kubectl rollout restart deployment safesurfer-api
. Changing theplanConfig
in yourvalues.yaml
will automatically do this for you.
Now we can start creating a plan and addons. Create a new product family named Safe Surfer Test
:
Then, create a new plan named Safe Surfer Test
:
Then, select any price point to set a price:
Then, select your new price point:
Then, create an addon:
Then, create a price point for the addon. Ensure there is a matching currency and billing period between the plan and the addon, or the addon cannot be attached to the plan.
Note The
Flat Fee
option for addons is not supported. To create an on/off addon, you can simply set amaxQuantity
of1
.
Now it’s time to sign a customer up to the plan. We have a couple of choices here:
We’ll look at both approaches in the next section.
To allow using the new plan and addon from the API, we’ll need to add them to our values.yaml
like so. In the following example, we will set up the quotas so that no devices can be created until the user has an active plan. The addon will allow the user to use AlphaSOC Security Alerts in various quantities.
api:
# Don't allow creating devices by default
quotas:
devices:
maxValue: 0
client_devices:
maxValue: 0
security_alert_endpoints_per_hour:
maxValue: 0
accounts:
chargebee:
planConfig:
plans:
# If the user has this active plan, allow creating devices and client devices
Safe-Surfer-Test:
enabled: true
quotas:
devices:
maxValue: 10
client_devices:
maxValue: 1000
addons:
- Security-Alerts-Test
addons:
# If the user has this addon, allow using 1 unit of security alerts per addon amount
Security-Alerts-Test:
enabled: true
quotas:
security_alert_endpoints_per_hour:
valueDelta: 1
maxQuantity: 100
prorateOnUpgrade: true
prorateOnDowngrade: true
invoiceImmediately: true
title: 'Security Alerts'
description: 'The amount of devices to scan for Security Alerts. Provided by AlphaSOC.'
icon: 'bug'
Then upgrade the chart. We will show two ways of creating the subscription/addons for the user:
Let’s create a new user to try the API approach with. Use the admin app to create one. Remember that you can add e.g. +2
after your name to create a separate account while routing emails towards the same account:
You can find the user ID using the Browse
tab:
Obtain an auth token for the user. If your accounts are still set to managed
, use the method described here. If your accounts aren’t managed, you can use the regular signin API. We will use the $BILLING_AUTH_TOKEN
variable below as a placeholder for the auth token.
To preview the costs associated with creating the subscription, call the estimate endpoint:
$ curl "$SS_API_ADDR/v2/user/subscription/estimate" -d '{"planID": "Safe-Surfer-Test", "planPriceID": "Safe-Surfer-Test-USD-Monthly", "addons": [{"id": "Security-Alerts-Test", "quantity": 5}]}' -H "Authorization: Bearer $BILLING_AUTH_TOKEN" | jq
{
"meta": {
"timestamp": 1680129315,
"code": 201,
"errorCode": "",
"message": "OK"
},
"spec": {
"immediateCosts": null,
"nextRenewalCosts": {
"total": 12.5,
"totalDiscount": 0,
"items": [
{
"name": "Safe Surfer Test",
"cost": 7.5,
"discount": 0
},
{
"name": "Security Alerts Test",
"cost": 5,
"discount": 0
}
],
"discountValidOnce": false,
"discountValidUntil": null
},
"unbilledCharges": null,
"quotas": {
"alert_emails": {
"maxValue": 10,
"currentValue": 2,
"imposedBy": "plan"
},
"alerts_dismissed": {
"maxValue": 5000,
"currentValue": 0,
"imposedBy": "default"
},
"cast_events": {
"maxValue": 0,
"currentValue": 0,
"imposedBy": "default"
},
"client_devices": {
"maxValue": 1000,
"currentValue": 0,
"imposedBy": "plan"
},
"custom_rules_domain_list_per_device": {
"maxValue": 1000,
"currentValue": 0,
"imposedBy": "default"
},
"devices": {
"maxValue": 10,
"currentValue": 0,
"imposedBy": "plan"
},
"evasion_attempts": {
"maxValue": 1000,
"currentValue": 0,
"imposedBy": "default"
},
"logged_notifications": {
"maxValue": 0,
"currentValue": 0,
"imposedBy": "default"
},
"router_diagnostics_reports": {
"maxValue": 10,
"currentValue": 0,
"imposedBy": "default"
},
"screencasts": {
"maxValue": 0,
"currentValue": 0,
"imposedBy": "default"
},
"screentime_deadlines_per_device": {
"maxValue": 10,
"currentValue": 0,
"imposedBy": "default"
},
"screentime_timers_per_device": {
"maxValue": 10,
"currentValue": 0,
"imposedBy": "default"
},
"screentime_timetables_per_device": {
"maxValue": 10,
"currentValue": 0,
"imposedBy": "default"
},
"security_alert_endpoints_per_hour": {
"maxValue": 5,
"currentValue": 0,
"imposedBy": "plan"
},
"security_alert_ignore_rules": {
"maxValue": 1000,
"currentValue": 0,
"imposedBy": "default"
},
"vpn_attempts": {
"maxValue": 20000,
"currentValue": 0,
"imposedBy": "default"
},
"web_push_subscriptions": {
"maxValue": 0,
"currentValue": 0,
"imposedBy": "default"
}
}
}
}
The main thing of interest here is nextRenewalCosts
, which shows the costs of the user’s next bill after signing up. If the plan had no trial period, immediateCosts
would be filled out instead.
Now that we’ve previewed the costs, we can create the subscription:
$ curl "$SS_API_ADDR/v2/user/subscription" -d '{"chargebee": {"planID": "Safe-Surfer-Test", "planPriceID": "Safe-Surfer-Test-USD-Monthly", "addons": [{"id": "Security-Alerts-Test", "quantity": 5}]}}' -H "Authorization: Bearer $BILLING_AUTH_TOKEN" | jq
{
"meta": {
"timestamp": 1680129879,
"code": 201,
"errorCode": "",
"message": "OK"
},
"spec": null
}
You should now be able to view the current subscription status:
$ curl "$SS_API_ADDR/v2/user/subscription" -H "Authorization: Bearer $BILLING_AUTH_TOKEN" | jq
{
"meta": {
"timestamp": 1680130314,
"code": 200,
"errorCode": "",
"message": "OK"
},
"spec": {
"isActive": true,
"status": "TRIAL",
"nextBill": 1680738515,
"canSubscribeChargebee": true,
"canSubscribeGooglePlay": false,
"chargebeeSubscription": {
"id": "284c33be-af39-4bc5-a16d-5a82134e45fb",
"planId": "Safe-Surfer-Test",
"planPriceId": "Safe-Surfer-Test-USD-Monthly",
"createdAt": 1680130115,
"nextBillingAt": 1680738515,
"trialEnd": 1680738515,
"cancelledAt": 0,
"cancelReason": "",
"currentTermEnd": 0,
"startDate": 0,
"customerCardStatus": "no_card",
"status": "in_trial",
"addons": [
{
"id": "Security-Alerts-Test",
"planPriceId": "",
"quantity": 5,
"updatable": true,
"minQuantity": null,
"maxQuantity": null,
"prorateOnUpgrade": true,
"prorateOnDowngrade": true,
"invoiceImmediately": true
}
]
},
"chargebeePlan": {
"enabled": true,
"id": "Safe-Surfer-Test",
"priceID": "Safe-Surfer-Test-USD-Monthly",
"name": "Safe Surfer Test",
"channel": "web",
"price": {
"price": "$7.50 USD",
"period": "per month",
"currencySymbol": "$",
"currencyCode": "USD",
"numericPrice": 7.5
},
"trial": {
"trial": "7 day free trial",
"trialPeriod": 7,
"trialPeriodUnit": "day"
},
"anyPlanQuotas": {
"alert_emails": {
"maxValue": null,
"valueDelta": 10
}
},
"anyCbPlanQuotas": null,
"planQuotas": {
"client_devices": {
"maxValue": 1000,
"valueDelta": null
},
"devices": {
"maxValue": 10,
"valueDelta": null
}
},
"addons": [
{
"id": "Security-Alerts-Test",
"priceID": "Security-Alerts-Test-USD-Monthly",
"name": "Security Alerts Test",
"price": {
"pricingModel": "per_unit",
"priceAndPeriod": {
"price": "$1.00 USD",
"period": "per month",
"currencySymbol": "$",
"currencyCode": "USD",
"numericPrice": 1
},
"pricingTiers": null
},
"quotas": {
"security_alert_endpoints_per_hour": {
"maxValue": null,
"valueDelta": 1
}
},
"minQuantity": null,
"maxQuantity": null,
"prorateOnUpgrade": true,
"prorateOnDowngrade": true,
"invoiceImmediately": true
}
]
},
"nextChargebeeInvoice": {
"total": 12.5,
"totalDiscount": 0,
"items": [
{
"name": "Safe Surfer Test",
"cost": 7.5,
"discount": 0
},
{
"name": "Security Alerts Test",
"cost": 5,
"discount": 0
}
],
"discountValidOnce": false,
"discountValidUntil": null
},
"googlePlaySubscription": null
}
}
You can also use the API to obtain hosted pages for editing payment information or checkout.
E.g. for checkout:
$ curl "$SS_API_ADDR/v2/user/subscription/chargebee/redirect/checkout" -H "Authorization: Bearer $BILLING_AUTH_TOKEN" | jq
{
"meta": {
"timestamp": 1680130426,
"code": 200,
"errorCode": "",
"message": "OK"
},
"spec": {
"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"type": "checkout_existing",
"url": "https://example.chargebee.com/pages/v3/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/",
"state": "created",
"failure_reason": "",
"pass_thru_content": "",
"embed": false,
"created_at": 1680130426,
"expires_at": 1680141226,
"content": null,
"updated_at": 1680130426,
"resource_version": 1680130426500,
"checkout_info": null,
"business_entity_id": "",
"object": "hosted_page"
}
}
Or for payment methods:
$ curl "$SS_API_ADDR/v2/user/subscription/chargebee/redirect/payment-methods" -H "Authorization: Bearer $BILLING_AUTH_TOKEN" | jq
{
"meta": {
"timestamp": 1680130625,
"code": 200,
"errorCode": "",
"message": "OK"
},
"spec": {
"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"type": "manage_payment_sources",
"url": "https://example.chargebee.com/pages/v3/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/",
"state": "created",
"failure_reason": "",
"pass_thru_content": "",
"embed": false,
"created_at": 1680130625,
"expires_at": 1680566225,
"content": null,
"updated_at": 1680130625,
"resource_version": 1680130625081,
"checkout_info": null,
"business_entity_id": "",
"object": "hosted_page"
}
}
You can visit the url
directly to see how they look, or the whole spec
object can be passed to the chargebee.js library as the hostedPage
parameter to show it as a modal, which is what the default frontend does.
To allow the frontend to create subscriptions, we’ll need to tell it which plan and price to use. Overwrite the following in your values.yaml
:
api:
accounts:
chargebee:
defaultPlan: Safe-Surfer-Test
defaultPriceId: Safe-Surfer-Test-USD-Monthly
Then upgrade the release. Now we have a couple of choices:
Log in as the existing user:
Hit the trial button in the top right to get to the subscription menu. Then, hit “Change Plan”.
Now you can proceed to the Checkout screen
section below. By going through the checkout again, you’ll be testing a resubscribe. Resubscribe can be used to change plans, but in this case we’ll just use the same plan again.
To be able to create a new user, you’ll want to make sure managed accounts are disabled, and you may want to set the newAccountPolicy
to prevent free creation of accounts. If you’ll be using managed accounts, you can skip this guide section. The yaml is as follows:
api:
accounts:
managed: false
newAccountPolicy: RequireApproval
After upgrading the release, you should be able to create a new user from the frontend, then approve them from the admin app:
After completing either above section, you should see a checkout screen for the plan you added in the values.yaml
. To add different plan choices, or decorate this screen more, you can fork and build the user dashboard
Add the addon if you like:
Enter card details. Click the Card Number
field to autocomplete test cards. You will not be charged, since this is a chargebee test instance.
After completing the checkout, your subscription screen should look something like this:
You can modify the addon, cancel, edit payment methods, etc. Note that since we have set prorateOnUpgrade
and invoiceImmediately
in the addon config, adding or upgrading the Security Alerts
addon will result in a checkout appearing.
Whether this occurs while the subscription is in the trial or future state is controlled by the requireActive
parameter on the addon. If this is true
, the trial will be ended, or the subscription started, as part of the checkout. If this is false
(the default), the prorated charge will not be raised while the subscription is in the trial or future state.
To play around with this, you can end the trial for the subscription manually in chargebee:
Note If you have a
Future
subscription because you resubscribed in the above section, the relevant button will be calledEdit Start Date
instead.
Then, refresh the frontend and Edit
the Security Alerts addon to increase the quantity:
After hitting Confirm
, you should now see an immediate charge be raised:
This is because prorateOnUpgrade
and invoiceImmediately
are both true
for this addon. Configuring the addon this way ensures users can’t use it for free by removing it before the end of the current billing period or cancelling before the end of the current billing period. If the user later reduces the quantity, they will receive credits that will be applied to the next invoice, but they will always pay a fair amount for the duration they use the addon. For example, if we now reduce the addon quantity to lower than it was before, the user will receive credits towards their next invoice:
If invoiceImmediately
was set to false
, users will still receive a prorated charge/credits, but it will be added to their next invoice. Therefore users can dodge payment, but only by cancelling or refusing payment entirely.
If prorateOnUpgrade
was set to false
, users will get to use the addon for free until the next billing period. Users can dodge payment by removing the addon before the invoice is generated.
You’ll need to create a new user to try the webhook approach with. Use whichever method you prefer, like the instructions above.
Then, create a customer on chargebee with the same ID. You only need to fill out the Customer ID
field.
Then, create a subscription from the Subscriptions
menu and select your new customer and plan.
Now you can search for the user again on the admin app, and you should see the new subscription status and plan:
Note Webhooks are asynchronous and usually complete instantly, but there is no guarantee on speed provided by Chargebee. You may have to wait a couple of minutes. To ensure the subscription is added instantly, use the API method below.
You can also view the new subscription on the user dashboard. Sign in as your new user and navigate to Account Settings -> Subscription
to view it. It should look something like this:
Note Some dashboard subscription operations won’t work until you configure the plans and addons. See the above section.
The admin app has APIs to support changing the subscription status of users manually, or editing quotas.
You can manually grant a user an active subscription by using the “Courtesy Account” feature. In the GUI (or API), search for a user, then grant them a courtesy account:
Now you can use api.accounts.anyPlanConfig
or api.accounts.courtesyPlanConfig
to configure quotas for any users with an active subscription, or courtesy subscription specifically.
Search for any user in the admin app, select the dropdown, then select “View/Edit Quotas”. As an example, if you followed the guide above, a user with the relevant plan and addon would look something like this:
The user has several quotas defined by their plan. You can, however, use the API or GUI to the same effect by updating the value manually. For example, let’s allow 20 devices:
Now this will override the default or any plan
values:
You can set the API to return 403 on certain API endpoints if the requesting user doesn’t have an active subscription. Look up the endpoint ID of the endpoint you wish to restrict on the API docs, then add something like the following:
api:
routesConfig:
"172": # GET /v2/alerts
paywall: true