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

Implement the PayPal purchase flow

Introduction

With the Mosaic Monetization and Billing Service you can integrate subscription purchases via PayPal to your frontend application.

For a full description of the PayPal integration see the PayPal Integration and the Manage PayPal Subscriptions how-to guide.

PayPal offers a purchase flow where the browser redirects the end-user to the PayPal website. And a popup based approach where the PayPal payment flow opens in a browser popup.

Prerequisites

This guide assumes that you have a frontend application that can authenticate end-users with the Mosaic User Service. You can use basic browser fetch logic or the Apollo Client if you use a React app: https://www.apollographql.com/docs/react/

You followed the integration guide PayPal Integration to enable PayPal as a payment provider in the Mosaic Environment administration and configured all the required PayPal settings. Then you created at least one subscription plan with a payment plan that has PayPal enabled and configured for this plan. The plan was successfully published from the Monetization Service and is now available in the Billing Service.

Show available subscription plans

First, you need to call the Billing Service GraphQL API to get a list of all available subscription plans with their details. The following is an example query which returns all (active) subscription plans. The response data includes the id, title, description, and the absolute URL path to the cover image.

The subscription plans include also all the associated active payment plans. Every payment plan has an ID, title, and description. The periodUnit field defines the type of unit (e.g. MONTH) and the quantity (e.g. 1) for a monthly recurring subscription.

The prices are in general country specific and you should select the price for the current country of the user. But as PayPal does not support country specific price differences this filter might not be needed in your project.

Example query for all active subscription plans
query Plans {
  subscriptionPlans(filter: {isActive: {equalTo: true}}) {
    nodes {
      id
      title
      description
      coverImagePath
      paymentPlans(filter: {isActive: {equalTo: true}}) {
        nodes {
          id
          title
          description
          periodUnit
          periodQuantity
          prices(filter: {country: {equalTo: "DE"}}) {
            nodes {
              price
              currency
            }
          }
          providerConfigs {
            nodes {
              paymentProvider {
                key
                title
              }
            }
          }
        }
      }
    }
  }
}
Note
You need to provide the end-user JWT from the Mosaic User Service in every request to the Billing Service GraphQL API as an HTTP header ("Authorization": "Bearer eyJYSJ9lYZOVr7s…​"). Please check the corresponding User Service documentation on how to register and log in end-users.

PayPal integration via redirects

In the redirect based purchase process, the end-user starts on your website and selects the payment plan that he wants to purchase. The end-user is then redirected in the browser to the PayPal website where he can follow the purchase flow. Once everything is fine, PayPal redirects the end-user to our Mosaic Billing Service. From there the end-user is then redirected back to the success (or error/cancel URL) that you configured in the Billing Service settings.

The following steps describe, which GraphQL API calls, redirects, etc. you should make in order to implement the PayPal subscription flow.

When the end-user selects one of the payment plans, you can call the paypalSubscribe mutation to start the purchase process. Use the ID of the selected payment plan for the paymentPlanId and for the purchase flow purchaseFlow parameter select the value REDIRECT. You can optionally provide a ISO 3166-1 alpha-2 country code via the country parameter (e.g. US for the United States of America or DE for Germany). The Billing Service will store this country code for the created subscription and will validate, that a price is configured for the given country.

Mutation to prepare the PayPal redirect purchase flow.
mutation subscribe {
  paypalSubscribe(
    input: {
      paymentPlanId: "58e11028-4a70-45a2-9690-9fc92a7f6d1a"
      purchaseFlow: REDIRECT
      country: DE
    }
  ) {
    approveUrl
    subscriptionId
  }
}

This query can be done from the client side or from the backend of your website. Once you acquired the approveUrl, redirect the user to that given URL. The end-user can then follow the PayPal purchase flowto subscribe to the selected payment and subscription plan.

PayPal will then redirect the end-user to the Billing Service API with some parameters about the purchase process. The Billing Service will then validate the subscription details with the PayPal API. Finally, the Billing Service will redirect the end-user to your configured success (or cancel) URL. In success case the Billing Service appends a GET parameter to your URL that contains the UUID of the created subscription as subscriptionId parameter:

Your application can now request information about the subscription and display this information. The query could request information about the lifecycle status, the end date of the current billing period, information about the used payment provider and the title and description of the subscription plan and payment plan:

Query to get subscription details.
query sub {
  subscription(id: "960f5cd7-968c-4f68-bbf6-85ed9ae48eec") {
    lifecycleStatus
    activationDate
    periodEndDate
    paymentProvider {
      title
    }
    subscriptionPlan {
      title
      description
    }
    paymentPlan {
      title
      description
    }
  }
}

PayPal integration via popups

The second option that is offered by PayPal is to use the popup-based subscribe workflow. This process uses the PayPal JavaScript SDK to render a PayPal subscribe button on your application.

When the end-user selects one of the payment plans, you can call the paypalSubscribe mutation to get the required parameters to start the popup-based purchase process. Use the ID of the selected payment plan for the paymentPlanId and for the purchase flow purchaseFlow parameter select the value POPUP. You can optionally provide a ISO 3166-1 alpha-2 country code via the country parameter (e.g. US for the United States of America or DE for Germany). The Billing Service will store this country code for the created subscription and will validate, that a price is configured for the given country.

Mutation to prepare the PayPal popup purchase flow.
mutation subscribe {
  paypalSubscribe(
    input: {
      paymentPlanId: "58e11028-4a70-45a2-9690-9fc92a7f6d1a"
      purchaseFlow: POPUP
      country: DE
    }
  ) {
    subscriptionId
    paypalPlanId
    customId
  }
}

This will compose a customId that you have to provide to the PayPal button code.

Note
The following code is a sample on how you can integrate the button into your application. Please confirm with your used PayPal SDK version to make sure the example works for your version.

First, you need to include the PayPal SDK into your application. you have to replace the text {{your-client-ID}} with the client ID of the PayPal REST API application that you configured in the Monetization Service.

<script
  src="https://www.paypal.com/sdk/js?client-id={{your-client-ID}}&vault=true&intent=subscription"
  data-sdk-integration-source="button-factory"
></script>

Add a DIV element into your website where the button should appear:

<div id="paypal-button-container"></div>

Then you need to configure the button and render it. Please consult the PayPal documentation for a description on all the available configuration options and callbacks.

The example below shows a simplified implementation using the basic fetch functionality. For a production-grade implementation, you may consider using more sophisticated libraries like Apollo Client and correctly handle error scenarios.

paypal
  .Buttons({
    style: {
      shape: "rect",
      color: "blue",
      layout: "vertical",
      label: "subscribe",
    },
    createSubscription: function (data, actions) {
      return fetch('https://billing.service.eu.axinom.net/graphql', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer {{end-user-JWT}}'
        },
        body: JSON.stringify({
          query: `
            mutation sub($input: PaypalSubscribeInput!) {
              paypalSubscribe(input: $input) {
                paypalPlanId
                customId
              }
            }
            `,
          variables: {
            "input": {
              "paymentPlanId": "{{payment-plan-id}}",
              "purchaseFlow": "POPUP"
            }
          },
        }),
      })
        .then((res) => res.json())
        .then((result) => {
          return actions.subscription.create({
            plan_id: result.data.paypalSubscribe.paypalPlanId,
            custom_id: result.data.paypalSubscribe.customId,
          });
        })
    },
    onApprove: function (data, actions) {
      return fetch('https://billing.service.eu.axinom.net/graphql', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer {{end-user-JWT}}'
        },
        body: JSON.stringify({
          query: `
            mutation activate($input: PaypalActivateSubscriptionInput!) {
              paypalActivateSubscription(input: $input) {
                subscription {
                  id
                  lifecycleStatus
                }
              }
            }
            `,
          variables: {
            "input": {
              "paypalSubscriptionId": data.subscriptionID
            }
          },
        }),
      })
        .then((res) => res.json())
        .then((result) => {
          alert('The subscription with ID ' +
            result.data.paypalActivateSubscription.subscription.id +
            ' is now in the ' +
            result.data.paypalActivateSubscription.subscription.lifecycleStatus +
            ' state.');
        })
    },
  })
  .render("#paypal-button-container");

You can use the style property to define the look and feel of the PayPal button. The PayPal Buttons style guide describes the available settings.

In the createSubscription callback you need to return the actions.subscription.create call. As parameters, you need to provide the PayPal plan ID. The Mosaic Billing Service needs to match the PayPal subscription to your end-user and your environment instance. Therefor you have to set a custom ID so the Mosaic Billing Service can match the PayPal subscription to the Billing Service subscription.

The Billing Service GraphQL mutation paypalSubscribe provides you with both parameters. The above example uses the fetch call with promises to get this information. You need to replace the text {{end-user-JWT}} with the actual JWT of the end-user. And you need to replace {{payment-plan-id}} with the ID of the desired Billing Service payment plan.

If the end-user clicks on the rendered PayPal button a popup appears that is loaded from the PayPal website. The end-user can follow the process and complete the payment process.

Once the process is done, the popup closes and the onApprove callback from the above code is executed. Withink that callback you can call the paypalActivateSubscription mutation on the Billing Service GraphQL API to activate the subscription. The data parameter contains a property subscriptionID which refers to the PayPal subscription in their system. You have to set this as an input parameter to the paypalActivateSubscription mutation. When this GraphQL mutation is executed, the Billing Service will verify via the PayPal API that the subscription was correctly activated. In the above example, the response includes the Billing Service subscription ID and the lifecycle status of the subscription. You can then show the result to the end-user. In the example above this is simply done as an alert.