Skip to content

Set up Custom Billing Provider

Abu Sakib edited this page Apr 4, 2025 · 2 revisions

This document describes how to set up a custom billing provider with Moesif Developer Portal.

Overview

Setting up a custom billing provider with Moesif Developer Portal involves the following steps in general:

  • Creating a plan and price definining the subscription of your product.
  • Implementing a checkout process.
  • For a successful checkout, implement the backend logic that performs the following tasks:
    • Verifies the purchase and subscription details.
    • Create subscription object.
    • Send the data to moesif.
    • Provision required services.

Set the Environment Variables

Set the following environment variables to custom:

my-dev-portal.env.REACT_APP_PAYMENT_PROVIDER=custom
my-dev-portal-api.env.APP_PAYMENT_PROVIDER=custom

Define Your Product Subscription

Use Moesif's Product Catalog feature to create the Plan and Price for your product subscription. Make sure you create a custom plan by selecting Custom in the Billing Provider dropdown. After successfully, creating the plans and prices, the developer portal shows the available plans in the Plans page.

Plans page in the Developer Portal showing a custom plan

Implement the Checkout Process Form

Now you need to implement the checkout process form for your custom plan as the CustomCheckForm React component:

import React from "react";
import { Link } from "react-router-dom";

function CustomCheckoutForm({ planId, priceId, user, idToken }) {
  return (
    <div id="checkout">
      <div>
        <h3>
          For custom billing provider, please implement your custom check out
          flow here.
        </h3>
        <ul>
          <li>
            Typically, the flow involves redirecting to a payment gateway.
          </li>
          <li>
            Upon successful payment, it should come back to{" "}
            <code>
              http://127.0.0.1/return?price_id={encodeURIComponent(priceId)}
              &plan_id={encodeURIComponent(planId)}
            </code>
            , with some sort of success code or identifier
          </li>
          <li>
            On return page, it would make API call to backend:
            <ul>
              <li>to verify the payment is successful.</li>
              <li>to create the subscription.</li>
              <li>to provision any services.</li>
            </ul>
          </li>
          <li>
            For now: to simulate success return from payment provider, continue
            to:{" "}
            <strong>
              <Link
                to={`/return?price_id=${encodeURIComponent(
                  priceId
                )}&plan_id=${encodeURIComponent(planId)}`}
              >
                <button>return page</button>
              </Link>
            </strong>
          </li>
        </ul>
      </div>
    </div>
  );
}

export default CustomCheckoutForm;

Implement Successful Checkout

After you perform checkout through the checkout form you build in the preceding step, the following flow of events must happen:

  • The backend verifies the purchase and subscription details
  • The backend creates the subscription
  • The backend provisions the necessary services, if any

Initiate Successful Checkout

The registerPurchaseCustom function function sends the purchase and subscription details to the backend for verification:

function registerPurchaseCustom({
  planId,
  priceId,
  sessionId,
  idToken,
  user,
  setCustomerEmail,
  setStatus,
  setLoading,
  setProvisionError,
}) {
  setLoading(true);

  fetch(`${import.meta.env.REACT_APP_DEV_PORTAL_API_SERVER}/register/custom`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${idToken}`,
    },
    body: JSON.stringify({
      plan_id: planId,
      price_id: priceId,
      // session_id:
      // you may have a some sort of session id or checkout id from your payment provider that you can
      // use to verify purchase on the backend.
    }),
  })
    .then(async (res) => {
      if (!res.ok) {
        const errorBody = await res.json();
        throw new Error(
          `Failed provision: ${res.status}, body: ${JSON.stringify(errorBody)}`
        );
      }
      return res.json();
    })
    .then((data) => {
      setStatus("complete");
      setCustomerEmail(user?.email);
    })
    .catch((err) => {
      setProvisionError(err);
    })
    .finally(() => {
      setLoading(false);
      moesifIdentifyUserFrontEndIfPossible(idToken, user);
    });
}

It sends the purchase and subscription details, like plan and price IDs, to the /register/custom endpoint. The next step shows how you you can verify those details.

Verify Successful Purchase

The verification involves these steps:

  1. Implement the BillingProvider class containing the verification logic. It must work with your custom billing provider to verify that a purchase is successful and return the subscription details accordingly. For example, the following assumes a successful purchase, creates a hard-coded subscription object, and returns it:

    class BillingProvider {
      verifyPurchaseAndCreateSubscription(req, data) {
        // use the request info to verify the purchase after checkout
        // but generally your billing provider should verify the subscription
        // and return the subscription object.
        // below is a fake subscription generated on the fly.
    
        const subscription = {
          id: generateGUID(),
          plan_id: data?.plan_id,
          price_id: data?.price_id,
          ...data,
          current_period_start: new Date().toISOString(),
          current_period_end: getOneMonthFromNowISO(),
        };
    
        return {
          subscription,
        };
        // throw new Error('Verify Purchase Not Implemented.');
      }
    }
  2. Write the handler function for HTTP POST request to /register/custom endpoint that uses the BillingProvider implementation to verify purchase and create subscription. For example:

    app.post(
      "/register/custom",
      authMiddleware,
      jsonParser,
      async function (req, res) {
        const customerId = await getUnifiedCustomerId(req.user);
        const email = req.user?.email;
        // verify plans and subscription using your custom billing provider.
        try {
          const { subscription } =
            await customBillingProvider.verifyPurchaseAndCreateSubscription(req, {
              user: req.user,
              ...req.body,
            });
    
          console.log("custom subscription created", subscription);
    
          syncToMoesif({
            companyId: customerId,
            userId: customerId,
            email: email,
          });
    
          sendSubscriptionToMoesif({
            companyId: customerId,
            subscriptionId: subscription.id,
            planId: subscription.plan_id,
            priceId: subscription.price_id,
            currentPeriodStart: subscription.current_period_start,
            currentPeriodEnd: subscription.current_period_end,
            metadata: {
              // additional metadata you might want add.
            },
          });
    
          const user = await provisioningService.provisionUser(
            customerId,
            email,
            subscription.id
          );
          res.status(201).json({ status: "provisioned" });
        } catch (err) {
          console.error("Error registering user", err);
          res.status(500).json({
            message: "Failed to provision user. " + err.toString(),
          });
        }
      }
    );

    Here, the callback function extracts the subscription object from your implementation of BillingProvider for a successful purchase. It then sends the subscription data to Moesif and provisions the user services.

Clone this wiki locally