PCI Widget Web

Introduction to the PCI Widget for Web

Part of our embedded scope is the access to sensitive card data. To access such card data, like PAN and CVV, it is mandatory to use the PCI widget. The reason being that this type of card data needs special protection according to PCI-DSS guidelines. If you are yourself PCI-DSS certified, you may also use the API to access sensitive card data.

There are several options to adjust the PCI widget to your UI/UX needs.

The PCI widget is embedded into your frontend via an iframe, which shows the card data for about 45 seconds. After that, the card details are hidden again automatically, for security reasons.

The two needed parameters traceId and widgetConfig are:

  • traceId: a random, unique UUIDv4, generated by you on making the request. It is used for overall tracing of the request in all involved systems. It has to be the same throughout one PCI widget request. I.e. the same in the iFrame URL as in the request of the OTP (see endpoint).
  • widgetConfig: the overall configuration of the widget, containing for instance the cardId and design parameters to display. The cardId is the Pliant UUIDv4. The details of the widget config can be found here.
<iframe 
        src="{host}/card-details/widget?traceId={traceId}&params={widgetConfig}" 
        allow="clipboard-read; clipboard-write">
</iframe>

Completed with parameters, the iframe might look like this:

<iframe 
        src="https://pci-api.getpliant.com/card-details/widget?traceId=5c68c142-50a0-46e1-8a27-736a7908cc59&params=ewogICJ0aXRsZSI6ICJDYXJkIERldGFpbHMiLAogICJ0b2tlbiI6ICJ7cmVxdWVzdGVkIE9UUH0iLAogICJjYXJkSWQiOiAie1BsaWFudCdzIGNhcmRJZH0iLAogICJvdmVybGF5VGV4dCI6ICJTaG93IGNhcmQgZGV0YWlscyIsCiAgImNvcHlMYWJlbCI6ICJDb3B5IiwKICAiZnJhbWVJZCI6ICJjYXJkLWRldGFpbHMtd2lkZ2V0LWZyYW1lIiwKICAiY3NzQ2xhc3MiOiAid2lkZ2V0LWNvbnRhaW5lciIsCiAgInN0eWxlVXJsIjogIi4vd2lkZ2V0L3N0YXRpYy9wY2ktd2lkZ2V0LmNzcyIsCiAgInBhbiI6IHsKICAgICJsYWJlbCI6ICJDYXJkIG51bWJlciIsCiAgICAiZGlzcGxheSI6IHRydWUsCiAgICAiY2xpY2tUb0NvcHkiOiB0cnVlLAogICAgInRleHRPbkNvcHkiOiAiQ29weSBjYXJkIG51bWJlciIKICB9LAogICJjYXJkaG9sZGVyTmFtZSI6IHsKICAgICJsYWJlbCI6ICJDYXJkaG9sZGVyIG5hbWUiLAogICAgImRpc3BsYXkiOiB0cnVlLAogICAgImNsaWNrVG9Db3B5IjogdHJ1ZSwKICAgICJ0ZXh0T25Db3B5IjogIkNvcHkgY2FyZGhvbGRlciBuYW1lIgogIH0sCiAgImV4cGlyeURhdGUiOiB7CiAgICAibGFiZWwiOiAiRXhwIiwKICAgICJkaXNwbGF5IjogdHJ1ZSwKICAgICJjbGlja1RvQ29weSI6IHRydWUsCiAgICAidGV4dE9uQ29weSI6ICJDb3B5IGV4cGlyeSBkYXRlIgogIH0sCiAgImN2diI6IHsKICAgICJsYWJlbCI6ICJDVlYiLAogICAgImRpc3BsYXkiOiB0cnVlLAogICAgImNsaWNrVG9Db3B5IjogdHJ1ZSwKICAgICJ0ZXh0T25Db3B5IjogIkNvcHkgQ1ZWIgogIH0KfSA=" 
        allow="clipboard-read; clipboard-write">
</iframe>

Data Flow

  1. The cardholder authenticates on the partners frontend.
  2. The cardholder clicks a button to reveal sensitive card data.
  3. The partners frontend requests a one time password (OTP) to show sensitive card data. This GET requests needs to go first to the partners backend which forwards it to Pliant's PCI API. See this endpoint.
    curl 'https://{host}/card-details/widget/{cardId}/otp' \
    --header 'Pliant-Trace-Id: {random UUIDv4, newly generated per request}' \
    --header 'Authorization: Bearer {token}'
    
    1. The partner's backend is responsible for validating the request from the partner's frontend to ensure that the requesting person has the necessary permissions to access sensitive card data for the specified card.
    2. The OTP request must contain a Pliant-Trace-Id header to guarantee a unique request that can be traced through the logging systems of all parties involved. This trace id has to be unique for each request, meaning no two requests can have the same id.
  4. After receiving the OTP, the frontend compiles the PCI widget configuration according to the schema provided below. The JSON configuration must be base64 encoded to be used as a GET parameter in the iframe loading later on as widgetConfig.
  5. Create and load the iframe with src="{host}/card-details/widget?traceId={traceId}&params={widgetConfig}" where traceId is the same trace id used in the request to obtain the OTP.
  6. While loading the iframe, a RSA key pair will be generated. It is used for subsequent encryption of the data and only valid for a short amount of time.
  7. The sensitive card data will be requested using the public RSA key and the defined widget config.
  8. The browsers user agent will be verified against a list of blacklisted user agents. Only actual browser requests are allowed. Clients like Postman or command line tools or similar are not allowed.
  9. The public RSA key will be used to encrypt the card data response containing the sensitive information. The PCI widget config will be used to configure which fields exactly are going to be returned by the API.
  10. The encrypted card data will be decrypted within the iframe itself with the private RSA key and then temporarily displayed in the iframe.
  11. After a set amount of time (45 seconds) the content of the iframe will be replaced by a static placeholder. And all sensitive card data will be permanently deleted from the page and browser clipboard.
  12. In addition an event with the message {eventType: CARD_DATA_CLEARED, frameId: <frameId>} will be sent to the loading parent page. The parent page is supposed to have an event handler that will destroy the iframe on receiving this event. In case of any error an event with the same structure but with eventType CARD_DATA_LOADING_FAILED will be sent to the parent page.

Environments

Widget Config

The actual widget config which is needed to show the specific sensitive card details is transmitted as a base64 encoded JSON payload to the iframe. You can find examples here.

{
  "title": "Card Details",
  "token": "{requested OTP}",
  "cardId": "{Pliant's cardId (36 chars UUIDv4)}",
  "overlayText": "Show card details",
  "copyLabel": "Copy",
  "frameId": "card-details-widget-frame",
  "cssClass": "widget-container",
  "styleUrl": "./widget/static/pci-widget.css",
  "pan": {
    "label": "Card number",
    "display": true,
    "clickToCopy": true,
    "textOnCopy": "Copy card number"
  },
  "cardholderName": {
    "label": "Cardholder name",
    "display": true,
    "clickToCopy": true,
    "textOnCopy": "Copy cardholder name"
  },
  "expiryDate": {
    "label": "Exp",
    "display": true,
    "clickToCopy": true,
    "textOnCopy": "Copy expiry date"
  },
  "cvv": {
    "label": "CVV",
    "display": true,
    "clickToCopy": true,
    "textOnCopy": "Copy CVV"
  }
} 

Which will then be attached to the URL as an base64 encoded string:

ewogICJ0aXRsZSI6ICJDYXJkIERldGFpbHMiLAogICJ0b2tlbiI6ICJ7cmVxdWVzdGVkIE9UUH0iLAogICJjYXJkSWQiOiAie1BsaWFudCdzIGNhcmRJZH0iLAogICJvdmVybGF5VGV4dCI6ICJTaG93IGNhcmQgZGV0YWlscyIsCiAgImNvcHlMYWJlbCI6ICJDb3B5IiwKICAiZnJhbWVJZCI6ICJjYXJkLWRldGFpbHMtd2lkZ2V0LWZyYW1lIiwKICAiY3NzQ2xhc3MiOiAid2lkZ2V0LWNvbnRhaW5lciIsCiAgInN0eWxlVXJsIjogIi4vd2lkZ2V0L3N0YXRpYy9wY2ktd2lkZ2V0LmNzcyIsCiAgInBhbiI6IHsKICAgICJsYWJlbCI6ICJDYXJkIG51bWJlciIsCiAgICAiZGlzcGxheSI6IHRydWUsCiAgICAiY2xpY2tUb0NvcHkiOiB0cnVlLAogICAgInRleHRPbkNvcHkiOiAiQ29weSBjYXJkIG51bWJlciIKICB9LAogICJjYXJkaG9sZGVyTmFtZSI6IHsKICAgICJsYWJlbCI6ICJDYXJkaG9sZGVyIG5hbWUiLAogICAgImRpc3BsYXkiOiB0cnVlLAogICAgImNsaWNrVG9Db3B5IjogdHJ1ZSwKICAgICJ0ZXh0T25Db3B5IjogIkNvcHkgY2FyZGhvbGRlciBuYW1lIgogIH0sCiAgImV4cGlyeURhdGUiOiB7CiAgICAibGFiZWwiOiAiRXhwIiwKICAgICJkaXNwbGF5IjogdHJ1ZSwKICAgICJjbGlja1RvQ29weSI6IHRydWUsCiAgICAidGV4dE9uQ29weSI6ICJDb3B5IGV4cGlyeSBkYXRlIgogIH0sCiAgImN2diI6IHsKICAgICJsYWJlbCI6ICJDVlYiLAogICAgImRpc3BsYXkiOiB0cnVlLAogICAgImNsaWNrVG9Db3B5IjogdHJ1ZSwKICAgICJ0ZXh0T25Db3B5IjogIkNvcHkgQ1ZWIgogIH0KfSA=
ParameterTypeDescription
token (mandatory)stringOTP obtained through Pliant's endpoint
cardId (mandatory)uuidPliant's card id
titlestringIframe page title
styleUrlstringURL to CSS styles for the iframe content
{fieldName}.labelstringOverwrite field names for card details fields. Defaults:
pan → Card number
cardholderName → Cardholder name
expiryDate → Exp
cvv → CVV
{fieldName}.clickToCopybooleanEnable click to copy block for a particular field. Default: true
{fieldName}.displaybooleanLoad field block into view. Default: true
{fieldName}.textOnCopystringTitle of the copy element in the HTML DOM for a particular field. Defaults:
pan → Copy card number
cardholderName → Copy cardholder name
expiryDate → Copy expiry date
cvv → Copy CVV
overlayTextstringText displayed after sensitive card data is destroyed permanently. Default: Show card details
copyLabelstringLabel for the copy button. Default: Copy
cssClassstringdiv[@id=app] will have a class equal to pliant-pci-widget {cssClass}, which is pliant-pci-widget widget-container by default.
This helps to customize the CSS on a more refined level. For example, if you have multiple colour cards, you can have two styles defined
.card-colour-1 .a {color: #AAAAAA} .card-colour-2 .a {color: #BBBBBB}
If you pass cssClass=card-colour-2 the second CSS block will be applied. Default: widget-container
frameIdstringIframe id for which a destruction event will be triggered. Default: card-details-widget-frame

Event Details

Event format: {eventType: {eventType}, frameId: {frameId}}
Event example: {eventType: CARD_DATA_CLEARED, frameId: myFrame123}

Available event types:

  • CARD_DATA_LOADING_FAILED - any error occured, card data could not be displayed
  • CARD_DATA_CLEARED - card data display timed out
  • CARD_DATA_LOADING_SUCCESS - card data displayed
  • CARD_DATA_PAN_COPIED - PAN copied to clipboard
  • CARD_DATA_CARDHOLDER_NAME_COPIED - Cardholder name copied to clipboard
  • CARD_DATA_EXP_COPIED - Expiry date copied to clipboard
  • CARD_DATA_CVV_COPIED - CVV copied to clipboard

iFrame HTML Structure

Structure in Default State

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
   <head>
      <title>Card Details</title>
      <script crossorigin="" defer="" src="./widget/static/pci-widget.js" type="application/javascript"></script>
      <link crossorigin="" href="./widget/static/pci-widget.css" rel="stylesheet" type="text/css">
   </head>
   <body>
      <div class="wrapper">
         <div app-frame-id="card-details-widget-frame" aria-label="Card Details" class="pliant-pci-widget widget-container"
            id="app"
            role="region">
            <div aria-label="Show card details" class="pliant-preloaded-data-overlay" role="button" style="display: none;"
               tabindex="0">Show card details
            </div>
            <div class="pliant-pci-widget-container">
               <div aria-label="Card number" class="pliant-data pliant-pan-result" role="group">
                  <div class="pliant-field-name pliant-field-pan-name">Card number</div>
                  <div class="pliant-field-value pliant-field-pan-value">
                     <span class="mono pliant-field-pan-value-text" data-field="pan" title="4444 5555 6666 7777">4444 5555 6666 7777</span>
                     <a aria-label="Copy card number" class="pliant-copy-btn" data-target="pliant-field-pan-value-text" href="#"
                        role="button"
                        tabindex="0">
                        <svg class="pliant-copy-btn-icon" fill="none" height="20" viewBox="0 0 32 32" width="20"
                           xmlns="http://www.w3.org/2000/svg">
                           <path
                              d="M23 8H5C4.73478 8 4.48043 8.10536 4.29289 8.29289C4.10536 8.48043 4 8.73478 4 9V27C4 27.2652 4.10536 27.5196 4.29289 27.7071C4.48043 27.8946 4.73478 28 5 28H23C23.2652 28 23.5196 27.8946 23.7071 27.7071C23.8946 27.5196 24 27.2652 24 27V9C24 8.73478 23.8946 8.48043 23.7071 8.29289C23.5196 8.10536 23.2652 8 23 8ZM22 26H6V10H22V26ZM28 5V23C28 23.2652 27.8946 23.5196 27.7071 23.7071C27.5196 23.8946 27.2652 24 27 24C26.7348 24 26.4804 23.8946 26.2929 23.7071C26.1054 23.5196 26 23.2652 26 23V6H9C8.73478 6 8.48043 5.89464 8.29289 5.70711C8.10536 5.51957 8 5.26522 8 5C8 4.73478 8.10536 4.48043 8.29289 4.29289C8.48043 4.10536 8.73478 4 9 4H27C27.2652 4 27.5196 4.10536 27.7071 4.29289C27.8946 4.48043 28 4.73478 28 5Z"></path>
                        </svg>
                        <span class="pliant-copy-btn-text">Copy</span>
                     </a>
                  </div>
               </div>
               <div aria-label="Cardholder name" class="pliant-data pliant-cardholder-result" role="group">
                  <div class="pliant-field-name pliant-field-cardholder-name">Cardholder name</div>
                  <div class="pliant-field-value pliant-field-cardholder-value">
                     <span class="pliant-field-cardholder-value-text" data-field="cardholderName"
                        title="Jane Dow">Jane Dow</span>
                     <a aria-label="Copy cardholder name" class="pliant-copy-btn" data-target="pliant-field-cardholder-value-text"
                        href="#"
                        role="button" tabindex="0">
                        <svg class="pliant-copy-btn-icon" fill="none" height="20" viewBox="0 0 32 32" width="20"
                           xmlns="http://www.w3.org/2000/svg">
                           <path
                              d="M23 8H5C4.73478 8 4.48043 8.10536 4.29289 8.29289C4.10536 8.48043 4 8.73478 4 9V27C4 27.2652 4.10536 27.5196 4.29289 27.7071C4.48043 27.8946 4.73478 28 5 28H23C23.2652 28 23.5196 27.8946 23.7071 27.7071C23.8946 27.5196 24 27.2652 24 27V9C24 8.73478 23.8946 8.48043 23.7071 8.29289C23.5196 8.10536 23.2652 8 23 8ZM22 26H6V10H22V26ZM28 5V23C28 23.2652 27.8946 23.5196 27.7071 23.7071C27.5196 23.8946 27.2652 24 27 24C26.7348 24 26.4804 23.8946 26.2929 23.7071C26.1054 23.5196 26 23.2652 26 23V6H9C8.73478 6 8.48043 5.89464 8.29289 5.70711C8.10536 5.51957 8 5.26522 8 5C8 4.73478 8.10536 4.48043 8.29289 4.29289C8.48043 4.10536 8.73478 4 9 4H27C27.2652 4 27.5196 4.10536 27.7071 4.29289C27.8946 4.48043 28 4.73478 28 5Z"></path>
                        </svg>
                        <span class="pliant-copy-btn-text">Copy</span>
                     </a>
                  </div>
               </div>
               <div aria-label="Exp, CVV" class="pliant-data pliant-expiry-cvv-result" role="group">
                  <div class="pliant-expiry-result">
                     <div class="pliant-field-name pliant-field-expiry-name">Exp</div>
                     <div class="pliant-field-value pliant-field-expiry-value">
                        <span class="pliant-field-expiry-value-text" data-field="expiryDate" title="10/26">10/26</span>
                        <a aria-label="Copy expiry date" class="pliant-copy-btn" data-target="pliant-field-expiry-value-text"
                           href="#" role="button"
                           tabindex="0">
                           <svg class="pliant-copy-btn-icon" fill="none" height="20" viewBox="0 0 32 32" width="20"
                              xmlns="http://www.w3.org/2000/svg">
                              <path
                                 d="M23 8H5C4.73478 8 4.48043 8.10536 4.29289 8.29289C4.10536 8.48043 4 8.73478 4 9V27C4 27.2652 4.10536 27.5196 4.29289 27.7071C4.48043 27.8946 4.73478 28 5 28H23C23.2652 28 23.5196 27.8946 23.7071 27.7071C23.8946 27.5196 24 27.2652 24 27V9C24 8.73478 23.8946 8.48043 23.7071 8.29289C23.5196 8.10536 23.2652 8 23 8ZM22 26H6V10H22V26ZM28 5V23C28 23.2652 27.8946 23.5196 27.7071 23.7071C27.5196 23.8946 27.2652 24 27 24C26.7348 24 26.4804 23.8946 26.2929 23.7071C26.1054 23.5196 26 23.2652 26 23V6H9C8.73478 6 8.48043 5.89464 8.29289 5.70711C8.10536 5.51957 8 5.26522 8 5C8 4.73478 8.10536 4.48043 8.29289 4.29289C8.48043 4.10536 8.73478 4 9 4H27C27.2652 4 27.5196 4.10536 27.7071 4.29289C27.8946 4.48043 28 4.73478 28 5Z"></path>
                           </svg>
                           <span class="pliant-copy-btn-text">Copy</span>
                        </a>
                     </div>
                  </div>
                  <div class="pliant-cvv-result">
                     <div class="pliant-field-name pliant-field-cvv-name">CVV</div>
                     <div class="pliant-field-value pliant-field-cvv-value">
                        <span class="pliant-field-cvv-value-text" data-field="cvv" title="123">123</span>
                        <a aria-label="Copy CVV" class="pliant-copy-btn" data-target="pliant-field-cvv-value-text" href="#"
                           role="button"
                           tabindex="0">
                           <svg class="pliant-copy-btn-icon" fill="none" height="20" viewBox="0 0 32 32" width="20"
                              xmlns="http://www.w3.org/2000/svg">
                              <path
                                 d="M23 8H5C4.73478 8 4.48043 8.10536 4.29289 8.29289C4.10536 8.48043 4 8.73478 4 9V27C4 27.2652 4.10536 27.5196 4.29289 27.7071C4.48043 27.8946 4.73478 28 5 28H23C23.2652 28 23.5196 27.8946 23.7071 27.7071C23.8946 27.5196 24 27.2652 24 27V9C24 8.73478 23.8946 8.48043 23.7071 8.29289C23.5196 8.10536 23.2652 8 23 8ZM22 26H6V10H22V26ZM28 5V23C28 23.2652 27.8946 23.5196 27.7071 23.7071C27.5196 23.8946 27.2652 24 27 24C26.7348 24 26.4804 23.8946 26.2929 23.7071C26.1054 23.5196 26 23.2652 26 23V6H9C8.73478 6 8.48043 5.89464 8.29289 5.70711C8.10536 5.51957 8 5.26522 8 5C8 4.73478 8.10536 4.48043 8.29289 4.29289C8.48043 4.10536 8.73478 4 9 4H27C27.2652 4 27.5196 4.10536 27.7071 4.29289C27.8946 4.48043 28 4.73478 28 5Z"></path>
                           </svg>
                           <span class="pliant-copy-btn-text">Copy</span>
                        </a>
                     </div>
                  </div>
               </div>
            </div>
         </div>
      </div>
   </body>
</html>

pliant-pan-result will no be rendered if pan.display = false in the widget config.
pliant-cardholder-result will no be rendered if cardholderName.display = false in the widget config.
pliant-expiry-result will no be rendered if expiryDate.display = false in the widget config.
pliant-pan-result will no be rendered if cvv.display = false in the widget config.

Structure in Destroyed State

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
   <head>
      <title>Card Details</title>
      <script crossorigin="" src="./widget/static/pci-widget.js" defer="true"></script>
      <link rel="stylesheet" href="./widget/static/pci-widget.css">
   </head>
   <body>
      <div class="wrapper">
         <div id="app" class="pliant-pci-widget" role="region" aria-label="Card Details" app-frame-id="cardholder-name-frame">
            <div class="pliant-preloaded-data-overlay" role="button" tabindex="0" aria-label="Show card details" style="display: block;">Show card details</div>
         </div>
      </div>
   </body>
</html>

Further Information

For more information on this topic contact [email protected].