Skip to main content

Subscription Platform

Getting Started

Current as of Jan 24, 2022.

Pre-Development

To begin working on the subscription platform in the FxA codebase, you will need access to a Stripe account for private and public API developer keys.

If you're a Mozilla employee, you can request access to the Stripe dev (and/or stage) account, created for the FxA Subscription Platform team to easily connect with fake products and plans. Otherwise, you can create your own Stripe account to use for testing that is not linked to any bank account information with your own products and plans. These keys should be taken from Stripe's test environment which you can verify by checking that the key includes the word test.

The fxa-payments-server needs the Stripe public key (pk) and communicates with the fxa-auth-server that requires a Stripe private or secret key (sk).¹ These can be found in the Stripe Dashboard, and configuration details can be found below.

¹ We have, in the past, given out restricted keys for use (rk). We may choose to do this again in the future or even use them in our dev environment.

Configuration

You will need to create the file fxa/packages/fxa-auth-server/config/secrets.json and specify subscriptions.stripeApiKey with the value of your private Stripe API key. Ensure the key begins with sk_test to guarantee you are using the secret key and testing in the correct environment.

Ex:

{
"subscriptions": {
"stripeApiKey": "sk_test_####"
}
}

Additionally create the file fxa/packages/fxa-payments-server/server/config/secrets.json and specify stripe.apiKey to override the default Mozilla Stripe public API key with your own public key:

{
"stripe": {
"apiKey": "pk_test_####"
}
}
note

Note that neither secrets.json files are tracked in Git, and they take precedence over each server's default configurations, should you need to make any additional local-only modifications.

Stripe Product/Plans

To see the available products or create a new one in the Stripe dashboard, navigate to Billing > Products and click into one of the products to see information including the product name, product ID, plan name, plan ID, metadata, logs, and events.

If you are using a new Stripe account, you will need to setup a product and its plan. The product should have additional metadata configured as needed.

note

Product Names are the canonical displayed name shown in Sub Plat UI. In some cases these may be paired with a plan's billing interval. Plan names are not displayed to users.

Product Metadata
KeyDescription
downloadURLDeprecated. This field has been replaced by successActionButtonURL.
product:privacyNoticeURLRequired. The URL for the webpage containing the Privacy Notice for the product offering.
product:termsOfServiceURLRequired. The URL for the webpage containing the Terms of Service for the product offering.
product:termsOfServiceDownloadURLRequired. The URL for a downloadable version of the Terms of Service for the product offering, used in emails. This must be a URL to the FxA CDN at https://accounts-static.cdn.mozilla.net. It can be either a) full, direct URL to a PDF (e.g. https://accounts-static.cdn.mozilla.net/legal/Mozilla_VPN_ToS/en-US.pdf), or, b) a URL without the language and file extension (e.g. https://accounts-static.cdn.mozilla.net/legal/mozilla_vpn_tos). See the "Legal Document Download URL" section for more information.
webIconURLRequired. Image URL for product icon in web content. This must be a URL to the FxA CDN at https://accounts-static.cdn.mozilla.net. Product icon will be resized to 64x64 pixels.
webIconBackgroundOptional. A valid css color, color name or gradient for display behind your product icon on the web. Defaults to #20123a
product:nameOptional. Title string override for the product name.
product:name:{locale}Optional. Localized title string override for the product name.
capabilitiesRequired if capabilities:{clientID} is not provided. Comma-separated list of capabilities enabled by this product for all Relying Parties.
capabilities:{clientID}Required if capabilities is not provided. Comma-separated list of capabilities enabled by this product for the Relying Party identified by {clientID}.
emailIconURLOptional. Image URL for product icon in email content. This must be a URL to the FxA CDN at https://accounts-static.cdn.mozilla.net.
appStoreLinkOptional. The App store download URL for the product.
playStoreLinkOptional. The google play store download URL for the product.
newsletterSlugOptional. Comma separated string array of slugs to send if a customer opts to signup for the newsletter. Valid values are mozilla-accounts, security-privacy-news, hubs, mdnplus, which can be combined into a comma separated string. Defaults to mozilla-accounts.
newsletterLabelTextCodeOptional. A code used to determine which of the predefined labels to add to the newsletter checkbox. Valid values are snp, hubs, mdnplus. If no code is provided, the default label text is displayed. Examples of each string can be found on Storybook here.
productSetRequired. A comma-separated list of arbitrary strings used to group products in a set of upgrades & downgrades. To prevent an IAP subscriber from double subscribing to your product on the web, ensure at least one productSet string matches between a configured IAP Stripe plan (i.e. a plan with appStoreProductIds or playSkuIds) and the other plans for your product.
productOrderOptional. A number used for sorting products in a set.
product:cancellationSurveyURLOptional. Override URL for the Cancellation Survey for the product offering. This parameter is used as a hyperlink in emails sent to the customer when their subscription is cancelled due, manual cancellation, FxA Account deletion, or failed payment.
product:details:{n}Optional. Bullet-point feature details for the product, where {n} is a number or ordering the points.
product:details:{n}:{locale}Optional. Localized string override for product:details:{n}, where {locale} is the locale (e.g. fr-FR, zh-CN, de, etc).
product:privacyNoticeURL:{locale}Optional. Localized override URL for the webpage containing the Privacy Notice for the product offering.
product:privacyNoticeDownloadURLOptional. The URL for a downloadable version of the Privacy Notice for the product offering. This has the same requirements as product:termsOfServiceDownloadURL.
product:termsOfServiceURL:{locale}Optional. Localized override URL for the webpage containing the Terms of Service for the product offering.
product:subtitleOptional. A subtitle for the product, usually displayed beneath the name in UI.
product:subtitle:{locale}Optional. Localized string override for product:subtitle, where {locale} is the locale (e.g. fr-FR, zh-CN, de, etc).
product:successActionButtonLabelOptional. An alternative label for the subscription success action button. The action is specified by successActionButtonURL.
product:successActionButtonLabel:{locale}Optional. Localized override for the alternative label for the subscription success action button.
promotionCodesOptional. A comma separated list of promotion codes that are valid for the product.
support:app:{x}Optional. An app or service for the support form. The form options will be in the same order as the metadata. These values shouldn't be too long as they are displayed in dropdown options of limited width. The {x} part of the key can be any string and will not be used anywhere; the value of the metadata is submitted to Zendesk.
successActionButtonURLRequired. The download or subscription success action URL for the product. (Replaces downloadURL)
upgradeCTAOptional. HTML content string describing available upgrades from this plan. By convention, should include a link back to a product lead page. That lead page links back to FxA's plan subscription pages.
Plan Metadata
KeyDescription
appStoreProductIdsOptional. Plan metadata only. Comma-separated list of Apple App Store productIds that map to this plan. There must only be one Stripe plan per App Store productId per environment (development/stage/production).
playSkuIdsOptional. Plan metadata only. Comma-separated list of Google Play product SKUs (now called product IDs) that map to this plan. There must only be one Stripe plan per Google Play product SKU per environment (development/stage/production).
Product Configuration Documents
info

This section is for an upcoming feature that is not yet in production. Please continue to use Stripe product metadata to configure your subscription products.

While the Stripe product and plan metadata has enable the Subscription Platform to quickly develop many features, it has multiple shortcomings.

  • There are limits on the number of entries and the size of the keys and values. The limit for the number of entries is rather low.
  • The strings only key-value pair format necessitates potentially confusing key formats and key parsing in code.
  • A reliance on Stripe metadata for subscription product configurations means the Subscription Platform need to define plans in Stripe for subscription plans that are not Stripe based (e.g. App Store subscriptions).

The overcome these limits, the team is moving to JSON document based product configurations. For readability, the configuration is described below in multiple sections. To see the overall format of the configuration documents, take a look at the following sample documents:

tip

A value in a plan configuration can be used to override the product configuration value. Another way to think of this is that the values in the product configuration are the defaults for the product's plans.

info

The Subscription Platform team is currently developing the process to update these configuration documents. Documentation for that is forthcoming.

Top Level Configuration Document Values

The configuration document is a JSON object. The top level keys for that object are below. A property is required unless otherwise noted.

KeyDescription
idOptional. An identifier for the system storing the document. It's not intended for manual edits.
productConfigIdPlan configuration only. The identifier of the product configuration document from which the plan configuration inherits its default values. It's not intended for manual edits.
stripeProductIdOptional. Product configuration only. The id of the Stripe product for which this document is providing configuration.
stripePriceIdOptional. Plan configuration only. The id of the Stripe price for which this document is providing configuration.
activeBoolean. A status to indicate whether the product or plan is accepting new subscriptions.
capabilitiesObject. See Capabilities Configuration below.
productSetA comma-separated list of arbitrary strings used to group products in a set of upgrades & downgrades. To prevent an IAP subscriber from double subscribing to your product on the web, ensure at least one string matches between a configured IAP Stripe plan (i.e. a plan with appStoreProductIds or playSkuIds) and the other plans for your product.
productOrderOptional. Plan configuration only. A number used to determine a subscription change is an upgrade or a downgrade within the productSet.
appStoreProductIdsOptional. Plan configuration only. Comma-separated list of Apple App Store productIds that map to this plan.
playSkuIdsOptional. Plan configuration only. Comma-separated list of Google Play product SKUs (now called product IDs) that map to this plan.
promotionCodesOptional. Array of strings. A list of Stripe promotion codes that are valid for the product or plan.
stylesObject. Currently the only key in this object is webIconBackground, and the value must be a valid CSS color, color name or gradient. The background color is displayed behind the product icon from the URLs configuration. Defaults to #20123a.
supportObject. Currently the only key in this object is app. The value is an array of strings that are apps and services for the support form. The form options will be in the same order the configuration. These values shouldn't be too long as they are displayed in dropdown options of limited width. The value is submitted to Zendesk.
urlsObject. See URLs Configuration below.
uiContentObject. See UI Content Configuration below.
localesObject. See Locales Configuration.
Capabilities Configuration

See Capabilities for an overview.

This is an object where the keys are: * or a relying party client ID. The values are arrays of capabilities. At least one key must be present.

KeyDescription
*Array. Capabilities enabled by this product for all Relying Parties.
{clientID}Array. Capabilities enabled by this product for the Relying Party identified by {clientID}.
URLs Configuration

An object where the values are URLs used in the subscription experience.

KeyDescription
appStoreOptional. The App Store download URL for the product.
playStoreOptional. The Google Play store download URL for the product.
webIconImage URL for the product icon in web content. This must be a URL to the FxA CDN at https://accounts-static.cdn.mozilla.net.
emailIconOptional. Image URL for the product icon in email content. This must be a URL to the FxA CDN at https://accounts-static.cdn.mozilla.net.
successActionButtonThe download or subscription success action URL for the product after a successful subscription sign-up.
privacyNoticeThe URL for the webpage containing the Privacy Notice for the product offering.
privacyNoticeDownloadOptional. The URL for a downloadable version of the Privacy Notice for the product offering. This has the same requirements as termsOfServiceDownload (see below).
termsOfServiceThe URL for the webpage containing the Terms of Service for the product offering.
termsOfServiceDownloadThe URL for a downloadable version of the Terms of Service for the product offering, used in emails. This must be a URL to the FxA CDN at https://accounts-static.cdn.mozilla.net. It can be either a) full, direct URL to a PDF (e.g. https://accounts-static.cdn.mozilla.net/legal/Mozilla_VPN_ToS/en-US.pdf), or, b) a URL without the language and file extension (e.g. https://accounts-static.cdn.mozilla.net/legal/mozilla_vpn_tos). See the "Legal Document Download URL" section for more information.
cancellationSurveyOptional. Override URL for the Cancellation Survey for the product offering. It is used as a link in the email sent to the customer when their subscription is cancelled.
UI Content Configuration

An object with configuration values used as web UI content.

KeyDescription
subtitleOptional. A subtitle for the product, usually displayed beneath the name in UI.
detailsOptional. An array of bullet-point feature details for the product. The list items will be displayed in the order they appear in the array.
successActionButtonLabelOptional. An alternative label for the subscription success action button. The action is specified by successActionButton in the URLs.
upgradeCTAOptional. HTML content string describing available upgrades from this plan. By convention, it should include a link back to a product lead page. That lead page links back to FxA's plan subscription pages.
Locales Configuration

An optional object used for localisation. It uses language tags as keys (e.g. en, en-CA, es) and the value is an object with the keys support, uiContent, and urls.

KeyDescription
{language-tag}Object with the follow key to value mapping: support - the support configuration object, uiContent - the UI content configuration, urls - the URLs configuration object.

For the legal document download URL configurations values, they can be in the form of an incomplete URL, as they will be handled by a redirect endpoint that tries to best match the user's locale to a localized version of the document. For example, if the value of the Terms of Service URL is https://accounts-static.cdn.mozilla.net/legal/mozilla_vpn_tos and the user's locale is de, then the endpoint will redirect the user to https://accounts-static.cdn.mozilla.net/legal/mozilla_vpn_tos.de.pdf.

Subscription Metadata
KeyValue
previous_plan_idThe value of the previous plan that the user had been subscribed to.
plan_change_dateUnix timestamp of the date the plan was changed.
cancelled_for_customer_atUnix timestamp of the date when the subscription was cancelled for the customer through FxA UI

Once your API keys are set, restart the affected servers (auth or payments) if needed.

Reference the workflow section of the FxA docs to sign up for and verify an account. You should now be able to access the payment flow at:

http://localhost:3030/subscriptions/products/{productId}?plan={planId}

The productId should match the ID from a product taken from the Stripe dashboard. The plan parameter is optional, unless you want to specify a plan. Otherwise, if the product has multiple plans, the first one in the list as returned by Stripe is used. If you are running the entire FxA stack and are using the keys from the Stripe FxA dev account, you can navigate to 123done on port :8080 to click on the link beginning with "Subscribe" to reach the form with a prepopulated product.

Enter any name, valid expiration date, CVC number, and any card number from the Stripe test cards docs to successfully create a test subscription.

Navigate back to http://localhost:3030/subscriptions to manage your subscriptions.

Understanding Subscription Status

Stripe defines the valid states a subscription status can be in their API docs. Since incomplete and incomplete_expired are subscriptions that have never been paid, FxA ignores them except for the following condition: if a user with a subscription in an incomplete state successfully enters valid payment information, the incomplete subscription will be paid and activated.

FxA's Stripe account is configured to not allow subscriptions to become unpaid and will cancel the subscription instead.

The last 4 states are active, trialing, past_due, and cancelled. The first three of these are considered active for the purposes of allowing the user access to the capabilities provided by the subscription, while cancelled subscriptions grant none.

Stripe Radar and Payment Blocking

We use Stripe Radar to block payments from both potentially abusive sources as well as from potential subscribers in currently unsupported regions. Our production radar rules are documented in Mana.

Interactions with Stripe

Forwarding Stripe Webhooks

To forward Stripe webhooks to your local, you can use the Stripe CLI. You'll need to stripe login to authenticate before you can start forwarding.

To start webhook forwarding, run:

stripe listen --forward-to localhost:9000/v1/oauth/subscriptions/stripe/event

The first time you run this, you'll need to grab the Stripe webhook secret displayed there, and add that to your secrets.json as described in secrets.

Payments Server

The payments server is an isolated service that serves all subscription related pages that utilize the Stripe Javascript SDK. It's isolated from the primary FxA domain to comply with constraints on 3rd party Javascript on pages handling FxA authentication.

When a subscription page is loaded, the React application served by the payment server:

  1. Loads the Stripe Javascript SDK (for tokenizing credit cards)
  2. Makes direct OAuth authenticated API calls to account/subscription endpoints on the Auth Server as needed

The payments server handles the payment flow as well as serving pages for managing a user's subscription that are linked from the Settings page.

Auth Server

FxA's Auth Server makes Stripe API calls for authenticated FxA users via its subscription endpoints. Stripe updates are sent back to the Auth Server via Stripe webhooks when a users subscription has been created/updated/deleted.

Some Stripe webhooks will trigger emails. These emails are behind a feature flag. If you wish to send emails in your environment, set the auth server configuration

{
"subscriptions": {
"transactionalEmails": {
"enabled": true
}
}
}

or the environment variable SUBSCRIPTIONS_TRANSACTIONAL_EMAILS_ENABLED to "true". In order to receive Stripe webhook events in your local development, you need to use the Stripe CLI's event forwarding feature. (For how to view these and other FxA emails, see the FxA README section on MailDev.)

Enabling Stripe Tax

To enable Stripe Tax in the Auth server you can: enable stripeAutomaticTax in your secrets.json or through your environment variables with SUBSCRIPTIONS_STRIPE_AUTOMATIC_TAX

Example using secrets.json:

{
"subscriptions": {
"stripeAutomaticTax": {
"enabled": true
}
}
}

or with a .env file and dotenv

SUBSCRIPTIONS_STRIPE_AUTOMATIC_TAX=true

Overriding Geolocation With Stripe Tax

When running the FxA stack locally, our geodb service needs an override to resolve a location. This override object takes the form of:

{
"location": {
"countryCode": <2 letter country code string>,
"postalCode": <corresponding postal code string>
}
}

and can be passed in either through your secrets.json or through your environment variables.

Example using secrets.json:

  "geodb": {
"locationOverride": {
"location": {
"countryCode": "US",
"postalCode": "98332"
}
}
},

or with a .env file using dotenv

GEODB_LOCATION_OVERRIDE= { "location": { "countryCode": "US", "postalCode": "85001"} }

PayPal Integration

PayPal can be configured as an additional payment provider in the Subscription Platform. PayPal's Express Checkout Reference Transactions is the feature that enables the Subscription Platform to use PayPal as a payment provider for recurring subscriptions. The customer's PayPal Billing Agreement ID is saved for future subscription invoices. See the diagram below for details on this process.

The PayPal paid subscriptions are still driven by Stripe's subscription and invoicing model. The key difference is that PayPal paid subscriptions have a collection method of send_invoice. The PayPal processor is used to pay the invoices with the customer's billing agreement ID.

PayPal Accounts

You need three types of PayPal accounts for development.

  • PayPal Developer Account: allows you to access the PayPal Developer Dashboard
  • Sandbox PayPal Personal Account: used for testing as the customer
  • Sandbox PayPal Business Account: used for testing

To create a PayPal developer account, sign up at developer.paypal.com. Note that if you are a Mozilla employee, you should contact the Mozilla PayPal admin in Finance to set up a developer account. Additionally, you should enable 2FA for the developer account.

Once you are in the PayPal developer dashboard, navigate to "Accounts" under the Sandbox section of the menu. Here you can create a pair of personal and business sandbox accounts. (To easily create multiple accounts for testing, there's a bulk account creation feature.)

Once you've added your account pair, navigate to the bussines account by selecting "View/edit account". Now click on the "API Credentials" tab. You'll need the "NVP/SOAP Sandbox API Credentials" for the next section.

Configuration

Auth Server

In order to enable and use PayPal in the auth server, set the following configuration options

{
"subscriptions": {
"paypalNvpSigCredentials": {
"enabled": true,
"sandbox": true,
"user": "your PayPal NVP API User name",
"pwd": "your PayPal NVP API password",
"signature": "your PayPal NVP API signature"
}
}
}

The environment variables equivalent would be

SUBSCRIPTIONS_PAYPAL_ENABLED=true \
PAYPAL_SANDBOX=true \
PAYPAL_NVP_USER='your PayPal NVP API User name' \
PAYPAL_NVP_PWD='your PayPal NVP API password' \
PAYPAL_NVP_SIGNATURE='your PayPal NVP API signature'
note

Nx will always pull from .env files over others (e.g., secrets.json) by default - see Nx documentation for more information.

Payments Server

The Payments frontend also does not offer PayPal as payment provider by default. To enable the feature, set the following configuration options

{
"paypal": {
"clientId": "sb",
"apiUrl": "https://www.sandbox.paypal.com",
"scriptUrl": "https://www.paypal.com"
}
}

Or use the environment variables

PAYPAL_CLIENT_ID='sb' \
PAYPAL_API_URL='https://www.sandbox.paypal.com' \
PAYPAL_SCRIPT_URL='https://www.paypal.com'

The paypal*/PAYPAL_* values are the defaults in the repo. For local development, you do not need to change them.

The PayPal Button

The Payments frontend uses the PayPal JavaScript SDK to add a button to the checkout process to integrate with PayPal's NVP/SOAP API. This button is loaded and displayed in an iFrame by PayPal.

Instant Payment Notification (IPN)

PayPal IPN is PayPal's equivalent of Stripe's webhook feature. We do rely on IPNs in the Subscription Platform. Unlike Stripe, however, PayPal does not offer any tool that would forward the events to your local environment. Our team use ngrok for that purpose.

Once you have the services running locally, start ngrok with ngrok 9000 and note the public URL. Using your sandbox business account and the public URL fron ngrok, complete these steps to set up IPNs.

PayPal Processor

After the initial payment during subscription creation, the recurring payments for future invoices are handled by the PayPal processor script. To simulate or debug charging additional invoices paid by PayPal, you need to run this script. It is located at packages/fxa-auth-server/scripts/paypal-process.ts.

The script will make up to a configurable number of attempts to pay an invoice before cancelling the subscription. This attempts count is saved to the invoice itself as metadata. The invoice's metadata is also used to prevent sending multiple failed payment emails per invoice from the PayPal payment attempts.

Contentful Integration

Environments

The Subscription Platform makes use of three Contentful environment aliases. The environment aliases are set up to match the current supported Stripe environments, and are listed below.

  • Prod (master)
    • Production environment
    • Stripe environment: Subscription Platform
    • Refers to the Contentful “master” environment alias.
  • Stage
    • Stage and QA environment
    • Stripe environment: SUB_PLAT_STAGE
  • Dev
    • Development
    • Stripe environment: SUB_PLAT_DEV

Configuration

The Contentful GraphQL Content API provides an interface to consume both published and non-published content from Contentful. To use the GraphQL Content API and run codegen, the following environment variables need to be configured in your .env file:

KeyDescription
CONTENTFUL_CDN_API_URLAPI Base URL for Content Delivery API.
CONTENTFUL_GRAPHQL_API_URLURI path for GraphQL Content API.
CONTENTFUL_GRAPHQL_API_KEYToken providing read-only access to fetch RP-provided content.
CONTENTFUL_GRAPHQL_SPACE_IDAlphanumeric id referencing the space holding the desired content type(s) and content. Used for instantiating the ContentfulClient.
CONTENTFUL_GRAPHQL_ENVIRONMENTContainer entity within a space where development and testing can occur in isolation from other environments. Used for instantiating the ContentfulClient, it is recommended to use the alias instead of the environment id to de-risk releases and instantly rollback if necessary.

You should now be able to retrieve data from:

    {CONTENTFUL_GRAPHQL_API_URL}/spaces/{CONTENTFUL_GRAPHQL_SPACE_ID}/environments/{CONTENTFUL_GRAPHQL_ENVIRONMENT}?access_token={CONTENTFUL_GRAPHQL_API_KEY}

Deployments

Deployments will be handled manually and will make use of the Merge app by Contentful, following the steps discussed in the tutorial: Merge content type changes with Merge app.

  • For each merge, ensure you complete the optional step "Export migration script" and store the migration scripts in the FxA monorepo.
warning

Changes made in the Appearance tab of a field of a content type, are not merged when using the Merge app, and therefore also not included in the migration script.

Typical changes

Changes are made in the Dev environment and follow the deployment path as shown below.

For a typical change and deployment, the following steps should be taken.

  1. Make changes in the Dev environment, and store the migration script in the FxA monorepo mentioned above.
  2. Check that the environmental variables listed below are in your .env, and run nx codegen shared-contentful.
    • CONTENTFUL_GRAPHQL_API_KEY
    • CONTENTFUL_GRAPHQL_API_URL
    • CONTENTFUL_GRAPHQL_ENVIRONMENT
    • CONTENTFUL_GRAPHQL_SPACE_ID
  3. Submit a pull request with the changes made in the previous steps.
    • If there were files updated from Step 2, announce that changes were made in the team channel once the PR has been merged, so the team can update their branches, if necessary. (Optionally: add a note in the Deploy doc with a comment that no action is required.)
  4. After a regular FxA release has been deployed to Stage, using the Merge app, merge changes from the Dev environment alias to the Stage environment alias.
  5. Similarly, after a regular FxA release has been deployed to Production, using the Merge app, merge changes from the Stage environment alias to the Prod environment alias.

Sensitive or large content model changes

In cases where many changes or a technically sensitive change that could introduce breaking changes are made, it is recommended to make the changes in a new environment, for example Feature, before merging those changes to Dev.

The resulting deployment path would be as follows.

For a sensitive or large content model change and deployment, the following steps should be taken.

  1. (Optional) Delete the existing Feature environment. (Check with Subscription Platform team first before doing this.)
  2. Create new a Feature environment from the Dev environment alias.
  3. Make changes in the Feature environment.
  4. Merge changes to the Dev environment alias and follow typical deployment path.

Once the changes in the Feature environment have been merged into the Dev environment alias, the Feature environment can be deleted.

Hotfixes

Hotfixes should follow either of the release paths discussed above, however merges to Stage and Prod do not need to follow the FxA release schedule.

Google IAP Integration

The Subscription Platform supports RP integrations with Google IAP (In-App Purchases).

Unlike subscriptions created through the payments server website (referred to internally as "web subscriptions"), Google IAP subscriptions are managed and processed entirely outside of Stripe as required by Google. Consequently, while SubPlat maintains an awareness of subscription state in Firestore, we rely on RPs to register a Google IAP subscription for a particular FxA user when it's created, and we rely on Real-Time Developer Notifications to inform us of state changes, which we broadcast to RPs via the fxa-event-broker.

Configuration

The Google IAP integration is behind a feature flag. Set subscriptions.playApiServiceAccount.enabled in ./config/secrets.json (see below) or the environment variable SUBSCRIPTIONS_PLAY_API_ENABLED to true before starting the auth server.

In fxa-auth-server/config/secrets.json, set the following config values under subscriptions:

{
// ...
"subscriptions": {
// ...
"playApiServiceAccount": {
"enabled": true,
"keyFilename": "",
"projectId": ""
}
}
}

You can stop here if you're only going to use the mock IAP script for your local development and do not need a real connection to the Play API. This is not required for most Google IAP development, so consider skipping the following section.

Connecting to a Play API Service Account

  1. Get access to Mozilla's firefox.gcp.mozilla.com organization.
  2. Create a new GCP project with Firestore
  3. Link the GCP project's Firestore instance to the auth server in fxa-auth-server/config/secrets.json.
    • Add the below configuration key/value pairs in playApiServiceAccount.
    • Replace the value for keyFilename with the absolute path to the keyfile created in step 2.
    • Replace the value for projectId with the project ID for the GCP project created in step 2.

Apple IAP Integration

The Subscription Platform supports RP integrations with Apple IAP (In-App Purchases).

Unlike subscriptions created through the payments server website (referred to internally as "web subscriptions"), Apple IAP subscriptions are managed and processed entirely outside of Stripe as required by Apple. Consequently, while SubPlat maintains an awareness of subscription state in Firestore, we rely on RPs to register an Apple IAP subscription for a particular FxA user when it's created, and we rely on App Store Server notifications to inform us of state changes, which we broadcast to RPs via the fxa-event-broker.

Terminology and Stripe analogs

At the time of writing, the overwhelming majority of subscriptions (> 95%) are web subscriptions mediated all or in part by Stripe. Given that, it may be helpful to draw analogies between Stripe and Apple identifiers to better understand how Apple IAP subscriptions are processed.

Stripe IDApple ID
productIdbundleId
planIdproductId
subscriptionIdoriginalTransactionId

Configuration

The Apple IAP integration is behind a feature flag. Set subscriptions.appStore.enabled in fxa-auth-server/config/secrets.json or the environment variable SUBSCRIPTIONS_APP_STORE_API_ENABLED to true before starting the auth server.

In fxa-auth-server/config/secrets.json:

{
// ...
"subscriptions": {
// ...
"appStore": {
"credentials": {
// one set of credentials for each RP iOS app
"org_mozilla_ios_FirefoxVPN": {
"issuerId": "",
"serverApiKey": "",
"serverApiKeyId": ""
}
},
"enabled": true,
"sandbox": true
}
}
}

Replace the value for issuerId, serverApiKey and serverApiKeyId with credentials from the respective app's App Store Connect account.

note
  • These credentials are only needed for making API calls to the App Store Server API. Consider omitting or stubbing them if your work does not require it.
  • Each key under credentials is the App Store bundleId for an iOS app with the . replaced with _ due to a node-convict bug. The bundleId can be found in App Store Connect for the given iOS app.

To obtain these credentials for a given iOS app, file a bug in the App Stores product and App Store Access component in Bugzilla (example bug). You will need someone with an existing Admin or similar role in App Store Connect to vouch for you.

Notifications

We use V2 App Store Server Notifications which are compatible with StoreKit 1 and StoreKit 2 iOS apps.

Unfortunately, unlike Stripe webhooks, Apple does not store their server notifications anywhere (even temporarily) for debugging. Further, Sandbox subscriptions can only be made with an iOS device via TestFlight, so it can be difficult as a developer to generate test notifications.

Debugging sandbox notifications

warning
  • If your local FxA is not running in a VM or Docker container, consider the security implications of this temporary setup before proceeding.
  • Only use this approach for Sandbox notifications, as the payloads are not encrypted at rest.

Before you begin, make sure you have App Store API credentials set up in the auth server config. These are needed to decode and process notifications (see "Configuration" above).

  1. Set up a reverse proxy with ngrok.
    • The ngrok URL is a public URL, so try not to leave this running for more than a couple of hours.
  2. Temporarily forward V2 Sandbox App Store Server notifications to your local FxA using the ngrok URL from #1 as the base URL (i.e. ${ngrok_base_URL}/v1/oauth/subscriptions/iap/app-store-notification).
    • Let the team know that you are temporarily changing the Sandbox notification URL, as this will affect any Apple IAP testing in Stage.
  3. Ask QA to trigger the desired scenarios using TestFlight.
  4. Restore the Sandbox notification URL in App Store Connect.

Testing IAP subscriptions locally

The test subscriptions described in this section are not true IAP subscriptions (i.e. there is no record of them in Apple or Google's databases), but rather, they are representations in SubPlat's Firestore database, which we use internally. As such, this approach can only be used to test code that assumes the IAP subscription is already cached in our database.

Configure the auth server and create mock IAP subscriptions

  1. Configure the fxa-auth-server for IAP
    • Enable the IAP integration. See Google IAP Integration for Google IAP and Apple IAP Integration for Apple IAP.
    • Note: For most development, setting enabled for both relevant configs is enough without setting up credentials.
  2. Setup a price for IAP
    • Create a price within Stripe.
    • Configure the price to have metadata fields appStoreProductId and playSkuId equal to some unique value (either in Stripe metadata or Firestore, depending on the state of useFirestoreProductConfigs). See Plan Metadata for these fields.
  3. Create a local fxa user
    • Grab the id for the user. We'll use this user id for creating mock subscriptions attached to that user.
  4. Run the mock IAP subscription creation script
    • Within auth-server, run yarn run create-mock-iap --uid USER_ID --appStoreProductId SKU --playSkuId SKU replacing USER_ID with the value from step 3, and SKU with the values from step 2.

Verify it worked

There are many ways this can be verified locally after following the steps above once logged in as the user with the test IAP subscription.

The easiest method is to go directly to the Subscription Management page: localhost:${PORT}/subscriptions, where PORT is defined in fxa-payments-server/pm2.config.js.

If things were set up correctly, you'll see the IAP subscription listed.

Ladder Diagrams of Payment Interactions

PayPal Checkout

Conditions for this flow:

  • User has no payment source on file, or is a new customer.
  • User clicks the displayed PayPal Smart Button to pay with PayPal.

This diagram represents the activity after the PayPal button is clicked.