Collection module UI components

How to add UI components for collecting and managing payment details.

Add custom collection module UI components to create, display, and summarise payment methods in Root Dashboard and Root Embed.

These components let your collection module return HTML that Root renders inside an iframe. Use them to collect payment provider data, show payment method details, and display a compact payment method summary.


Render create payment method

The renderCreatePaymentMethod function renders the UI for creating a new payment method. When a customer in Embed or a user in Root Dashboard selects the collection module payment method, Root fetches and renders the HTML returned by this function.

Use this component to collect the payment provider data required to create a payment method. The content is rendered inside an iframe and communicates with Root using the exposed functions described below.


The renderCreatePaymentMethod function accepts a single params object.

ParameterTypeRequiredDescription
policyObjectNoUsed when the payment method is being created for a policy.
applicationObjectNoUsed when the payment method is being created for an application.
policyholderObjectNoThe policyholder the payment method is being created for, when available. Root Dashboard and Embed usually pass this value in current flows, but the hook contract allows it to be undefined.

Render functions can return the HTML string directly or return a promise that resolves to the HTML string.

// Create content to be returned in Root Dashboard and Embed.
const renderCreatePaymentMethod = (params: {
  application?: Record<string, any>;
  policyholder?: Record<string, any>;
  policy?: Record<string, any>;
}) => {
  return `<!DOCTYPE html>
<html lang="en">
  <head></head>
  <body>
    <div id="payment-element"></div>
    <script></script>
  </body>
</html>`;
};

Create payment method lifecycle

Root exposes helper functions inside the iframe so your collection module can submit the payment method form and pass the result back to Root.

You implement submitCreatePaymentMethod. Root injects completeCreatePaymentMethod, setIsLoading, and setIsValid into the iframe. Call these injected functions, but do not redefine them.

The create payment method flow is:

  1. Root renders the HTML returned by renderCreatePaymentMethod.
  2. The user completes the payment method form.
  3. Root calls submitCreatePaymentMethod when the user submits the form.
  4. Your module validates or submits the payment data to the payment provider.
  5. Your module calls completeCreatePaymentMethod(result).
  6. Root invokes the collection module's createPaymentMethod hook with the submitted result.
  7. The createPaymentMethod hook returns the persisted payment method shape.

The examples below are partial examples. They assume you have already initialized your payment provider SDK and mounted the provider payment element. The examples use Stripe, but the same pattern works with any payment provider.

The examples name the submitCreatePaymentMethod argument params, but they do not depend on it. Use the payment provider state available inside your iframe, such as the mounted provider element.

Submit create payment method

The submitCreatePaymentMethod function is called when the user selects the submit button in Root Dashboard or Embed. Use it to run the payment provider submission code in your collection module.

const renderCreatePaymentMethod = (params: {
  application?: Record<string, any>;
  policyholder?: Record<string, any>;
  policy?: Record<string, any>;
}) => {
  return `<!DOCTYPE html>
<html lang="en">
  <head></head>
  <body>
    <div id="payment-element"></div>
    <script>
      const submitCreatePaymentMethod = (params) => {
        return stripe.confirmSetup({
          elements,
          redirect: "if_required",
        });
      };
    </script>
  </body>
</html>`;
};

Complete create payment method

Within submitCreatePaymentMethod, call completeCreatePaymentMethod to pass the result from your payment provider code back to Root. Root then invokes the collection module's createPaymentMethod hook to finalise payment method creation.

Do not override completeCreatePaymentMethod. Doing so will break the payment process.

const renderCreatePaymentMethod = (params: {
  application?: Record<string, any>;
  policyholder?: Record<string, any>;
  policy?: Record<string, any>;
}) => {
  return `<!DOCTYPE html>
<html lang="en">
  <head></head>
  <body>
    <div id="payment-element"></div>
    <script>
      const submitCreatePaymentMethod = (params) => {
        return stripe
          .confirmSetup({
            elements,
            redirect: "if_required",
          })
          .then(function (result) {
            // Pass the provider result to Root so it can call the createPaymentMethod hook.
            return completeCreatePaymentMethod(result);
          });
      };
    </script>
  </body>
</html>`;
};

Set loading state

The setIsLoading function controls the loading state while submitCreatePaymentMethod runs. Use it to reset the Root loading state after an error or when the provider submission flow completes.

Do not override setIsLoading. Doing so will break the loading functionality.

const renderCreatePaymentMethod = (params: {
  application?: Record<string, any>;
  policyholder?: Record<string, any>;
  policy?: Record<string, any>;
}) => {
  return `<!DOCTYPE html>
<html lang="en">
  <head></head>
  <body>
    <div id="payment-element"></div>
    <script>
      const submitCreatePaymentMethod = (params) => {
        return stripe
          .confirmSetup({
            elements,
            redirect: "if_required",
          })
          .then(function (result) {
            if (result.error) {
              setIsLoading(false);
            }

            return completeCreatePaymentMethod(result);
          });
      };
    </script>
  </body>
</html>`;
};

Set validation state

The setIsValid function controls whether the submit button is enabled. Call setIsValid(false) while the form is incomplete, then call setIsValid(true) when the user can submit the payment method.

Do not override setIsValid. Doing so will break the validation functionality.

const renderCreatePaymentMethod = (params: {
  application?: Record<string, any>;
  policyholder?: Record<string, any>;
  policy?: Record<string, any>;
}) => {
  return `<!DOCTYPE html>
<html lang="en">
  <head></head>
  <body>
    <div id="payment-element"></div>
    <script>
      paymentElement.on("change", (event) => {
        setIsValid(event.complete);
      });

      const submitCreatePaymentMethod = (params) => {
        return stripe
          .confirmSetup({
            elements,
            redirect: "if_required",
          })
          .then(function (result) {
            if (result.error) {
              setIsLoading(false);
            }

            return completeCreatePaymentMethod(result);
          });
      };
    </script>
  </body>
</html>`;
};

Render payment method details

The renderViewPaymentMethod function creates a custom view for the payment method created through renderCreatePaymentMethod. Root displays this view on policy and application pages.

This component is optional. If you do not define it, Root displays the module data inside the payment method.


The renderViewPaymentMethod function accepts a single params object.

ParameterTypeRequiredDescription
policyObjectNoUsed when the payment method view is for a policy object.
applicationObjectNoUsed when the payment method view is for an application object.
policyholderObjectNoThe policyholder assigned to the payment method, when available. Root Dashboard and Embed usually pass this value in current flows, but the hook contract allows it to be undefined.
payment_methodObjectYesThe collection module payment method object that the view displays.
// Payment method view content to be returned in Root Dashboard.
const renderViewPaymentMethod = (params: {
  application?: Record<string, any>;
  policyholder?: Record<string, any>;
  policy?: Record<string, any>;
  payment_method: Record<string, any>;
}) => {
  const { payment_method, policy } = params;

  return `<!DOCTYPE html>
<html lang="en">
  <head></head>
  <body>
    <table id="payment-details">
      <tr>
        <th>Type</th>
        <td>Collection module</td>
      </tr>
      <tr>
        <th class="no-background">Key</th>
        <td class="key">${payment_method.collection_module_key}</td>
      </tr>
      <tr>
        <th>Id</th>
        <td>${payment_method.module.id}</td>
      </tr>
      <tr>
        <th>Payment method</th>
        <td>${payment_method.module.payment_method}</td>
      </tr>
      <tr>
        <th>Billing day</th>
        <td>${policy && policy.billing_day}</td>
      </tr>
      <tr>
        <th>Livemode</th>
        <td>${payment_method.module.livemode}</td>
      </tr>
      <tr>
        <th>Status</th>
        <td>${payment_method.module.status}</td>
      </tr>
      <tr>
        <th>Usage</th>
        <td>${payment_method.module.usage}</td>
      </tr>
    </table>
    <img class="stripe-logo" />
  </body>
</html>`;
};

Render payment method summary

The renderViewPaymentMethodSummary function creates a compact view for the payment method created through renderCreatePaymentMethod. Root displays this summary when a user opens the edit payment method view or creates a new payment method in Root Dashboard.

Use this component to show the existing payment method for the policyholder in a simplified card.


The renderViewPaymentMethodSummary function accepts a single params object.

ParameterTypeRequiredDescription
policyObjectNoUsed when the payment method summary is for a policy.
applicationObjectNoUsed when the payment method summary is for an application.
policyholderObjectNoThe policyholder assigned to the payment method, when available. Root Dashboard and Embed usually pass this value in current flows, but the hook contract allows it to be undefined.
payment_methodObjectYesThe collection module payment method object that the summary displays.
// Payment method summary content to be returned in Root Dashboard.
const renderViewPaymentMethodSummary = (params: {
  application?: Record<string, any>;
  policyholder?: Record<string, any>;
  policy?: Record<string, any>;
  payment_method: Record<string, any>;
}) => {
  const { application, payment_method, policy, policyholder } = params;

  return `<!DOCTYPE html>
<html lang="en">
  <head></head>
   <body>
      <div id="payment-details">
          <h3>Stripe payment method</h3>
          <div id="card-details">
              Card: ${payment_method.module.card?.brand || 'Unknown'} **** **** **** ${
    payment_method.module.card?.last4 || 'Unknown'
  }, Expires: ${payment_method.module.card?.exp_month || 'Unknown'}/${payment_method.module.card?.exp_year || 'Unknown'}
          </div>
            <div class="api-attributes-wrapper">
              <div class="api-attributes" id="policy-id">Policy ID: ${policy && policy.policy_id}</div>
              <div class="api-attributes" id="application-id">Application ID: ${
                application && application.application_id
              }</div>
              <div class="api-attributes" id="policyholder-id">Policyholder ID: ${
                policyholder && policyholder.policyholder_id
              }</div>
          </div>
          </div>
      </body>
</html>`;
};

Troubleshooting

Submit button stays disabled

Confirm that your payment provider element calls setIsValid(true) when the form is complete. If setIsValid(false) is called and never reset, Root keeps the submit button disabled.

Loading state does not reset

Call setIsLoading(false) when the payment provider returns an error or when your submission flow ends without creating a payment method. Do not override Root's setIsLoading function.

Payment method is not created

Confirm that submitCreatePaymentMethod calls completeCreatePaymentMethod(result) with the provider result. Root uses this call to invoke the collection module's createPaymentMethod hook.

Iframe content does not render

Verify that each render function returns a complete HTML string with <html>, <head>, and <body> tags. Also check that the returned string does not contain syntax errors in the embedded script.


What’s Next