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.

Render update payment method

The renderUpdatePaymentMethod function renders the UI for editing a payment method that already exists. The Root dashboard fetches and renders this HTML when a user opens the edit payment method view on a policy or an application.

Use this component when the details behind a payment method can change without the payment method being replaced, such as a re-tokenised card, a corrected account number, or a new mandate reference.

This component is optional. If you do not define it, the Root dashboard displays the payment method's module data and the user can still change the billing day.

The renderUpdatePaymentMethod function accepts a single params object.

ParameterTypeRequiredDescription
payment_methodObjectYesThe collection module payment method object being edited. Use payment_method.module to pre-populate the form.
policyObjectNoUsed when the payment method is being edited on a policy.
applicationObjectNoUsed when the payment method is being edited on an application.
policyholderObjectNoThe policyholder assigned to the payment method, when available.
ℹ️

The update component uses the flow-neutral helper names submitRenderPaymentMethod and completeRenderPaymentMethod. The create-specific names submitCreatePaymentMethod and completeCreatePaymentMethod have no effect here.

// Update content to be returned in Root Dashboard.
const renderUpdatePaymentMethod = (params: {
  payment_method: Record<string, any>;
  application?: Record<string, any>;
  policyholder?: Record<string, any>;
  policy?: Record<string, any>;
}) => {
  const { payment_method } = params;
  const { module } = payment_method;

  // Escape `</` in interpolated values so a payload containing markup cannot break out of the
  // element it is rendered into.
  const accountNumber = String(module.account_number || "").replace(/<\//g, "<\\/");

  return `<!DOCTYPE html>
<html lang="en">
  <head></head>
  <body>
    <form id="update-form">
      <label for="account-number">Account number</label>
      <input id="account-number" name="account_number" value="${accountNumber}" />
    </form>
    <script>
      const input = document.getElementById("account-number");

      setIsValid(Boolean(input.value));
      input.addEventListener("input", () => setIsValid(Boolean(input.value)));

      const submitRenderPaymentMethod = () => {
        completeRenderPaymentMethod({ account_number: input.value });
      };
    </script>
  </body>
</html>`;
};

Update payment method lifecycle

The update flow uses the same injected helpers as the create flow, and routes the result to the collection module's updatePaymentMethod hook instead of createPaymentMethod.

The update payment method flow is:

  1. Root renders the HTML returned by renderUpdatePaymentMethod.
  2. The user edits the payment method form.
  3. Root calls submitRenderPaymentMethod when the user saves.
  4. Your module validates the input, or submits it to the payment provider.
  5. Your module calls completeRenderPaymentMethod(data).
  6. Root invokes the collection module's updatePaymentMethod hook with that data.
  7. The updatePaymentMethod hook returns the payment method's new module payload, which Root persists.

Update payment method hook

The updatePaymentMethod hook receives the payment method being edited, its policyholder, and the data your iframe passed to completeRenderPaymentMethod.

ParameterTypeRequiredDescription
payment_methodObjectYesThe payment method as it is stored before the update.
policyholderObjectYesThe policyholder the payment method belongs to.
dataObjectYesThe object your iframe passed to completeRenderPaymentMethod.
⚠️

The module object you return replaces the stored payload; Root does not merge it for you. Spread the existing payment_method.module if you only mean to change some of its fields.

const updatePaymentMethod = (params: {
  payment_method: Record<string, any>;
  policyholder: Record<string, any>;
  data: Record<string, any>;
}) => {
  const { payment_method, data } = params;

  return {
    module: {
      ...payment_method.module,
      ...data,
    },
  };
};

Two constraints apply to updates:

  • A payment method on an archived collection module cannot be updated, because the update runs the module's code.
  • A production payment method cannot be updated against the draft version of a collection module. Publish the version, or test the change against a sandbox payment method.

What’s Next

Did this page help you?