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 thecardId
and design parameters to display. ThecardId
is the Pliant UUIDv4. The details of the widget config can be found here.
<iframe
src="{host}/card-details/widget?traceId={traceId}¶ms={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¶ms=ewogICJ0aXRsZSI6ICJDYXJkIERldGFpbHMiLAogICJ0b2tlbiI6ICJ7cmVxdWVzdGVkIE9UUH0iLAogICJjYXJkSWQiOiAie1BsaWFudCdzIGNhcmRJZH0iLAogICJvdmVybGF5VGV4dCI6ICJTaG93IGNhcmQgZGV0YWlscyIsCiAgImNvcHlMYWJlbCI6ICJDb3B5IiwKICAiZnJhbWVJZCI6ICJjYXJkLWRldGFpbHMtd2lkZ2V0LWZyYW1lIiwKICAiY3NzQ2xhc3MiOiAid2lkZ2V0LWNvbnRhaW5lciIsCiAgInN0eWxlVXJsIjogIi4vd2lkZ2V0L3N0YXRpYy9wY2ktd2lkZ2V0LmNzcyIsCiAgInBhbiI6IHsKICAgICJsYWJlbCI6ICJDYXJkIG51bWJlciIsCiAgICAiZGlzcGxheSI6IHRydWUsCiAgICAiY2xpY2tUb0NvcHkiOiB0cnVlLAogICAgInRleHRPbkNvcHkiOiAiQ29weSBjYXJkIG51bWJlciIKICB9LAogICJjYXJkaG9sZGVyTmFtZSI6IHsKICAgICJsYWJlbCI6ICJDYXJkaG9sZGVyIG5hbWUiLAogICAgImRpc3BsYXkiOiB0cnVlLAogICAgImNsaWNrVG9Db3B5IjogdHJ1ZSwKICAgICJ0ZXh0T25Db3B5IjogIkNvcHkgY2FyZGhvbGRlciBuYW1lIgogIH0sCiAgImV4cGlyeURhdGUiOiB7CiAgICAibGFiZWwiOiAiRXhwIiwKICAgICJkaXNwbGF5IjogdHJ1ZSwKICAgICJjbGlja1RvQ29weSI6IHRydWUsCiAgICAidGV4dE9uQ29weSI6ICJDb3B5IGV4cGlyeSBkYXRlIgogIH0sCiAgImN2diI6IHsKICAgICJsYWJlbCI6ICJDVlYiLAogICAgImRpc3BsYXkiOiB0cnVlLAogICAgImNsaWNrVG9Db3B5IjogdHJ1ZSwKICAgICJ0ZXh0T25Db3B5IjogIkNvcHkgQ1ZWIgogIH0KfSA="
allow="clipboard-read; clipboard-write">
</iframe>
Data Flow
- The cardholder authenticates on the partners frontend.
- The cardholder clicks a button to reveal sensitive card data.
- 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}'
- 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.
- 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.
- 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 aGET
parameter in the iframe loading later on aswidgetConfig
. - Create and load the iframe with
src="{host}/card-details/widget?traceId={traceId}¶ms={widgetConfig}"
wheretraceId
is the same trace id used in the request to obtain the OTP. - 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.
- The sensitive card data will be requested using the public RSA key and the defined widget config.
- 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.
- 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.
- The encrypted card data will be decrypted within the iframe itself with the private RSA key and then temporarily displayed in the iframe.
- 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.
- 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 witheventType
CARD_DATA_LOADING_FAILED
will be sent to the parent page.
Environments
Environment | Host URL |
---|---|
PROD | https://pci-api.getpliant.com/ |
SANDBOX | https://pci-sandbox.partner-api.getpliant.com/ |
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=
Parameter | Type | Description |
---|---|---|
token (mandatory) | string | OTP obtained through Pliant's endpoint |
cardId (mandatory) | uuid | Pliant's card id |
title | string | Iframe page title |
styleUrl | string | URL to CSS styles for the iframe content |
{fieldName}.label | string | Overwrite field names for card details fields. Defaults:pan → Card numbercardholderName → Cardholder nameexpiryDate → Expcvv → CVV |
{fieldName}.clickToCopy | boolean | Enable click to copy block for a particular field. Default: true |
{fieldName}.display | boolean | Load field block into view. Default: true |
{fieldName}.textOnCopy | string | Title of the copy element in the HTML DOM for a particular field. Defaults:pan → Copy card numbercardholderName → Copy cardholder nameexpiryDate → Copy expiry datecvv → Copy CVV |
overlayText | string | Text displayed after sensitive card data is destroyed permanently. Default: Show card details |
copyLabel | string | Label for the copy button. Default: Copy |
cssClass | string | div[@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 |
frameId | string | Iframe 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 displayedCARD_DATA_CLEARED
- card data display timed outCARD_DATA_LOADING_SUCCESS
- card data displayedCARD_DATA_PAN_COPIED
- PAN copied to clipboardCARD_DATA_CARDHOLDER_NAME_COPIED
- Cardholder name copied to clipboardCARD_DATA_EXP_COPIED
- Expiry date copied to clipboardCARD_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].
Updated about 1 month ago