Mosaic products documentation: Concepts, API Reference, Technical articles, How-to, Downloads and tools

Implement Stripe as Custom Payment Connector

Payment connector backend

The Mosaic monetization solution integrates some payment providers like PayPal. Other payment providers can be integrated as custom payment connectors. In this example, we want to show how to integrate Stripe as a custom payment connector. We created an open-source project to orchestrate the purchase flow and Stripe events with the Mosaic Billing Service API. The project includes a very basic end user application to test the subscription flow via Stripe.

The project is based on the Billing Service Payment Connectors which describes the general integration logic and API usage.

This Stripe payment connector implements the following approach:

  • The Mosaic subscription is created first and this subscription ID is stored for later mapping purposes in the Stripe checkout session and subscription object. This way the subscription ID is always part of the checkout session and subscription event webhooks. Sadly this subscription ID cannot be set for invoices and charges so they require a call to the Stripe API to get the subscription object which contains the Mosaic subscription ID.

  • Even though Stripe has webhook support (the connector learns about activated subscriptions) the example still implements the redirect flow via the payment gateway. During this redirect, we check via the Stripe API if the subscription is activated and update the Billing Service subscription accordingly. This way the end user gets immediate feedback if everything went well.

The following sequence diagram shows the initial purchase flow and webhook calls that are handled. Recurring webhooks that we handle are basically the same as the initial ones. All properties with a name including “ID” are referring to Mosaic entities. Stripe entities and their IDs are referred to when they start with “stripe”:

End-UserEnd-UserClient AppClient AppBilling Service APIBilling Service APIBilling Service Mgmt APIBilling Service Mgmt APIPayment Connector APIPayment Connector APIPayment Gateway API/WebPayment Gateway API/Webopen page with availablesubscription plansand payment plansquery subscriptionPlans(including payment plans, prices &payment provider plan references)select payment plan andcustom payment connectormutation subscribe(paymentPlanId, JWT)mutation createSubscription(paymentPlanId,paymentProviderKey, ...)returns subscriptionId and endUserId)might return customeralt[Customer not found]stripe.customers.create(email, name,metadata: endUserId)returns customerstripe.checkout.sessions.create(mode: subscription, customer,stripePriceId, success_url,metadata: subscriptionId...)returns redirect URLreturn redirect URLredirectenters data into and submits the Stripe payment formalt[success]redirect to success URL with stripeSessionIdstripe.checkout.sessions.retrieve(stripeSessionId,{expand:subscription})returns session with subscription dataalt[Subscription status not incomplete anymore]mutation updateSubscription(subscriptionId, lifecycleStatus,periodEndDate, activationDate...)redirect to success (subscriptionId, identifier) or cancel/error page[cancelled]redirect to cancel URLWebhooks for both initial purchase and recurring payments (no guaranteed order)customer.subscription.updatedalt[previous status incomplete]mutation updateSubscription(subscriptionId, lifecycleStatus,periodEndDate, activationDate...)[already activated]mutation updateSubscription(subscriptionId, lifecycleStatus,periodEndDate...)HTTP 2xxinvoice.payment_succeededstripe.subscriptions.retrieve(stripeSubscriptionId)returns subscription with subscriptionIdmutation createSubscriptionTransaction(transactionType, subscriptionId, price,currency, paymentProviderReference, ...)HTTP 2xxinvoice.payment_failedstripe.subscriptions.retrieve(stripeSubscriptionId)returns subscription with subscriptionIdmutation createSubscriptionTransaction(transactionType, subscriptionId, description,currency, paymentProviderReference, ...)HTTP 2xxcharge.refundedstripe.invoices.retrieve(stripeInvoiceId,{expand:subscription})returns invoice with subscriptionIdmutation createSubscriptionTransaction(transactionType, subscriptionId, price,currency, paymentProviderReference, ...)HTTP 2xxalt[Cancel subscription (not part of the example implementation)]see current subscriptionquery subscriptions(only active ones)returns an active subscriptioncancel subscriptioncancel subscriptionstripe.subscriptions.update(  stripeSubscriptionId,  cancel_at_period_end: true);mutation updateSubscription(subscriptionId, lifecycleStatus)Figure 1. High-level sequence flow
Figure 1. High-level sequence flow

Notable points (for details check their detailed documentation):

  • Stripe holds customer data which is specific to your application. Customers make payments and create subscriptions. The used payment method is also stored for customers. This is different to PayPal for example where a customer has a single account and can use this account to purchase subscriptions and do payments for different stores. Therefore the payment connector will create new Stripe customers if they don’t exist and set as metadata the user ID from the Mosaic User Service. A Stripe customer can make purchases only for a single currency. If a Mosaic end user wants to purchase Stripe subscriptions with different currencies we have to create multiple Stripe customers.

  • Creating a subscription is part of a checkout session. When the session is created, we set the mode to “subscription” (others are “payment” for single payments and “setup” to store the customer’s payment details and charge later) and provide the ID of the price object the end user wants to subscribe to.

  • Stripe uses “invoices” and “charges”. Invoices are the modern approach that we will support. But for refunds, the charge object is still the only way to get notified.

  • The customer.subscription.updated event is used for all kinds of status updates. Possible values are incomplete, incomplete_expired, trialing, active, past_due, canceled, or unpaid. The following mapping will be used:

    • incomplete - maps to PENDING_ACTIVATION

    • incomplete_expired - maps to ENDED

    • trialing - not really supported currently but would be mapped to ACTIVE

    • active - maps to ACTIVE

    • past_due - this is ignored. Please configure Stripe to do smart retries. Until all smart retries are done the subscription stays in its current state. You can configure Stripe so that subscriptions go then into the canceled, unpaid, or leave it as past_due.

    • canceled - maps to CANCELLED

    • unpaid - maps to ON_HOLD

Stripe end user flow

The payment connector acts as the glue between the Stripe payment provider and the Mosaic Billing service. In order to allow end users to purchase a Stripe subscription, they must use some client application to do so. The client application might be a media streaming application that you are developing or some other kind of app that should offer subscriptions. The subscription flow to purchase a subscription via Stripe (or any other payment provider) must be part of this application.

This example project contains a super bare example application. It includes only the required parts to purchase a subscription and a link to the Stripe page for the customer. In your actual client application, you should include proper error handling, better tooling support, and consider the usage of libraries like Apollo Client and similar.

The initial page of the example application includes an input box where you can paste the authentication token (JWT) of an end user for whom you want to purchase a subscription. This token can be generated via your Mosaic User Service integration or via a node.js script that is part of the frontend-service project.

Once you added this token you can query and display all subscription plans with all their payment plans that support the stripe custom payment connector. Selecting one redirects the user to the stripe website where the end user can follow up with the purchase. If the purchase is successful, the end user is redirected to "/success-checkout" route of the Stripe payment connector. And from there to the "success.html" page of the frontend-service. If the end user cancells the purchase process he is redirected to the "cancelled.html" page.