Payments Data Platform | Modernbanc
Vault

Collect cards.

In this guide, we'll collect card numbers from your customers and put them into the vault for future use. (e.g. to either make a payment or reveal it to the customer.)

PCI Compliance

The most important thing is that you, your app, your company, or your employees don't see or touch that data, it is the only way you can reduce your PCI scope to the minimum, more on this here. Therefore we'll use our SDKs which provide isolated iFrames.

Don't store CVC, CVV or PIN for longer than needed to authorize transaction!

No one except for the card issuer is allowed to store CVCs, CVV, PIN, or other Card Authorization Data for longer than it’s required to make a payment.

Demo - What we'll be building

Requirements

  1. A workspace setup with Modernbanc.
  2. A valid API key with a role that has create secret permissions. This will be used in the SDK to turn collected data into Secrets.

Adding Modernbanc Inputs to your app.

To minimize PCI scope your app should never touch or access the card data. To achieve that you need to embed Modernbanc Input components to your screen/page.

They're isolated, headless, and flexible so you can build any kind of user journey and safely pass the data through to our Vault without exposing it to your app.

For this example we'll use our React SDK - but the same principles apply to our iOS and Android SDKs.

Wrap your app with Modernbanc context
import { ModernbancProvider } from '@modernbanc/react';
import View from './form/form.comp';
 
const App = () => (
  <ModernbancProvider api_key="YOUR_API_KEY">
    <View />
  </ModernbancProvider>
);
 
export default App;
Declare following variables to store modernbanc context and to keep track of valid, ready, loading and success states in View Component
// View Component
import { InputElement, useModernbancContext } from "@modernbanc/react";
import { useState } from "react";
 
const View = () => {
  const [loading, setLoading] = useState<boolean>(false);
  const [success, setSuccess] = useState<boolean>();
  const [valid_card_number, setValidCardNumber] = useState<boolean>(false);
  const [valid_expiry_date, setValidExpiryDate] = useState<boolean>(false);
  const [valid_cvc, setValidCvc] = useState<boolean>(false);
  const [number_input_is_ready, setNumberInputIsReady] = useState(false);
  const [expiry_date_input_is_ready, setExpiryDateInputIsReady] = useState(false);
  const [cvc_input_is_ready, setCvcInputIsReady] = useState(false);
  const all_inputs_are_ready = number_input_is_ready && expiry_date_input_is_ready && cvc_input_is_ready;
 
  const context = useModernbancContext();
  const show_form = all_inputs_are_ready && success === undefined && !loading;
  const can_submit = valid_card_number && valid_expiry_date && valid_cvc;
  const css = `padding: 5px; border: 1px solid #ccc; border-radius: 5px`;
 
  return (<></>);
};
Add 3 Input Elements (for the card number, expiry, and CVC)
// View Component
// rest of the code
return (
  <div
    style={{
      display: "flex",
      flexDirection: "column",
      gap: "10px",
      color: "gray",
      width: "400px",
      height: "500px",
    }}
  >
    {loading && <>Loading</>}
    {success && <>Success</>}
    <form
      style={{
        display: show_form ? "flex" : "none",
        flexDirection: "column",
        gap: "15px",
      }}
    >
      <InputElement
        id="number"
        placeholder="0000 0000 0000 0000"
        container={{ style: { height: "28px", width: "100%" } }}
        onChange={({ data }) => {
          const valid_card_number = data.matches?.valid_card_number;
          setValidCardNumber(!!valid_card_number);
        }}
        css={css}
        replacers={[{ type: "card_number" }]}
        matchers={[{ type: "valid_card_number" }]}
        onReady={() => {
          setNumberInputIsReady(true);
        }}
      />
      <InputElement
        id="exp-date"
        placeholder="MM/YY"
        container={{ style: { height: "28px", width: "100%" } }}
        replacers={[{ type: "expiry_date" }]}
        matchers={[{ type: "valid_expiry_date" }]}
        onChange={(data) => {
          setValidExpiryDate(!!data.data.matches?.["valid_expiry_date"]);
        }}
        css={css}
        onReady={() => setExpiryDateInputIsReady(true)}
      />
      <InputElement
        id="cvc"
        placeholder="CVC"
        container={{ style: { height: "28px", width: "100%" } }}
        onReady={() => setCvcInputIsReady(true)}
        matchers={[
          { type: "regex", pattern: "^[0-9]{3,4}$", name: "valid_cvc" },
        ]}
        onChange={(data) => {
          setValidCvc(!!data.data.matches?.["valid_cvc"]);
        }}
        css={css}
      />
      <button disabled={!can_submit} onClick={submit}>
        Submit
      </button>
    </form>
  </div>
);
Submit method - We call input elements createSecretValue() method to create secrets
// View Component
async function submit(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
  e.preventDefault();
  setLoading(true);
 
  // This will create a token from each input's value.
  const number_promise = context.modernbanc
    .getElement("input", "number")
    ?.createSecret();
  const exp_date_promise = context.modernbanc
    .getElement("input", "exp-date")
    ?.createSecret();
  const cvc_promise = context.modernbanc
    .getElement("input", "cvc")
    ?.createSecret();
 
  const promises = [number_promise, exp_date_promise, cvc_promise];
  const [number_result, exp_date_result, cvc_result] =
    await Promise.all(promises);
  console.log("Backend result", number_result);
 
  if (
    number_result?.is_error ||
    cvc_result?.is_error ||
    exp_date_result?.is_error
  ) {
    console.log("Backend error", number_result, exp_date_result, cvc_result);
    setLoading(false);
    return;
  }
 
  const number_secret_id = number_result?.secret_id;
  const exp_date_secret_id = exp_date_result?.secret_id;
  const cvc_secret_id = cvc_result?.secret_id;
  // Use these secret ids to make a payment or reveal the card number back to the user.
 
  setSuccess(true);
  setLoading(false);
}
Complete View Component
// View Component
// View Component
import { InputElement, useModernbancContext } from "@modernbanc/react";
import { useState } from "react";
 
const View = () => {
  const [loading, setLoading] = useState<boolean>(false);
  const [success, setSuccess] = useState<boolean>();
  const [valid_card_number, setValidCardNumber] = useState<boolean>(false);
  const [valid_expiry_date, setValidExpiryDate] = useState<boolean>(false);
  const [valid_cvc, setValidCvc] = useState<boolean>(false);
  const [number_input_is_ready, setNumberInputIsReady] = useState(false);
  const [expiry_date_input_is_ready, setExpiryDateInputIsReady] = useState(false);
  const [cvc_input_is_ready, setCvcInputIsReady] = useState(false);
  const all_inputs_are_ready = number_input_is_ready && expiry_date_input_is_ready && cvc_input_is_ready;
 
  const context = useModernbancContext();
  const show_form = all_inputs_are_ready && success === undefined && !loading;
  const can_submit = valid_card_number && valid_expiry_date && valid_cvc;
  const css = `padding: 5px; border: 1px solid #ccc; border-radius: 5px`;
 
  // View Component
  async function submit(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
    e.preventDefault();
    setLoading(true);
 
    // This will create token from each input's value.
    const number_promise = context.modernbanc
      .getElement("input", "number")
      ?.createSecret();
    const exp_date_promise = context.modernbanc
      .getElement("input", "exp-date")
      ?.createSecret();
    const cvc_promise = context.modernbanc
      .getElement("input", "cvc")
      ?.createSecret();
 
    const promises = [number_promise, exp_date_promise, cvc_promise];
    const [number_result, exp_date_result, cvc_result] =
      await Promise.all(promises);
    console.log("Backend result", number_result);
 
    if (
      number_result?.is_error ||
      cvc_result?.is_error ||
      exp_date_result?.is_error
    ) {
      console.log("Backend error", number_result, exp_date_result, cvc_result);
      setLoading(false);
      return;
    }
 
    const number_secret_id = number_result?.secret_id;
    const exp_date_secret_id = exp_date_result?.secret_id;
    const cvc_secret_id = cvc_result?.secret_id;
    // Use these secret ids to make a payment or reveal the card number back to the user.
 
    setSuccess(true);
    setLoading(false);
  }
 
  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        gap: "10px",
        color: "gray",
        width: "400px",
        height: "500px",
      }}
    >
      {loading && <>Loading</>}
      {success && <>Success</>}
      <form
        style={{
          display: show_form ? "flex" : "none",
          flexDirection: "column",
          gap: "15px",
        }}
      >
        <InputElement
          id="number"
          placeholder="0000 0000 0000 0000"
          container={{ style: { height: "28px", width: "100%" } }}
          onChange={({ data }) => {
            const valid_card_number = data.matches?.valid_card_number;
            setValidCardNumber(!!valid_card_number);
          }}
          css={css}
          replacers={[{ type: "card_number" }]}
          matchers={[{ type: "valid_card_number" }]}
          onReady={() => {
            setNumberInputIsReady(true);
          }}
        />
        <InputElement
          id="exp-date"
          placeholder="MM/YY"
          container={{ style: { height: "28px", width: "100%" } }}
          replacers={[{ type: "expiry_date" }]}
          matchers={[{ type: "valid_expiry_date" }]}
          onChange={(data) => {
            setValidExpiryDate(!!data.data.matches?.["valid_expiry_date"]);
          }}
          css={css}
          onReady={() => setExpiryDateInputIsReady(true)}
        />
        <InputElement
          id="cvc"
          placeholder="CVC"
          container={{ style: { height: "28px", width: "100%" } }}
          onReady={() => setCvcInputIsReady(true)}
          matchers={[
            { type: "regex", pattern: "^[0-9]{3,4}$", name: "valid_cvc" },
          ]}
          onChange={(data) => {
            setValidCvc(!!data.data.matches?.["valid_cvc"]);
          }}
          css={css}
        />
        <button disabled={!can_submit} onClick={submit}>
          Submit
        </button>
      </form>
    </div>
  );
};
 
export default View;
Don't dismount your inputs too early!

If you want to show a loading indicator during the secret creation don't dismount the inputs, hide them instead (e.g opacity: 0). if you dismount them the underlying iFrame will close and you won't get a response message back.

Demo Source Code

That's it!

Now when user types in their card number and clicks the submit button our SDK will upload it to the Vault and give you back an id of the secret in Modernbanc Vault.

In the next guide, we'll make use of that secret to either make a payment or reveal it to the customer.