PaymentElements Integration
Collect card and ACH credentials in Preczn-hosted iframes and exchange them for tokens, without sensitive data ever touching your web app.
Overview
Preczn PaymentElements is a client-side browser SDK that renders card and ACH inputs inside Preczn-origin iframes. Sensitive account data is captured, validated, and tokenized entirely within Preczn's infrastructure — your page only receives a token. This significantly reduces your PCI scope and qualifies eligible integrators for SAQ A.
PaymentElements is the recommended successor to PaymentFields when stronger PCI scope reduction is required. Each input field is hosted in its own iframe served by Preczn, and the SDK coordinates them on your behalf so you can style, observe, and tokenize them with a small JavaScript surface.
ℹ️ PaymentElements vs. PaymentFields
Use PaymentFields for a lightweight, drop-in script that intercepts your existing inputs. Use PaymentElements when you need iframed inputs for SAQ A eligibility or stronger isolation between your page and cardholder data.
Implementation Steps
Step 1: Include the Script
Add the secure JavaScript reference to your page's <head> with your public API key:
<script src="https://api.preczn.com/v1/clients/preczn.min.js?resources=paymentelements&publicApiKey=2482re32338cd9z5048v7bhmb5"></script>The script auto-bootstraps on DOMContentLoaded and exposes its API as window.Preczn.PaymentElements.
Step 2: Define Container Elements
Instead of <input> elements, PaymentElements mounts iframes into container <div> elements that you mark with data-preczn attributes. The SDK auto-discovers them on load and watches the DOM for containers added later.
Card form:
<div data-preczn="number" data-preczn-label="Card number"></div>
<div data-preczn="expiration" data-preczn-label="Expiration (MM/YY)"></div>
<div data-preczn="cvv" data-preczn-label="CVV"></div>ACH form:
<div data-preczn="accountNumber" data-preczn-label="Account number"></div>
<div data-preczn="routingNumber" data-preczn-label="Routing number"></div>
<div data-preczn="accountType" data-preczn-label="Account type"></div>
<div data-preczn="bankCountry" data-preczn-label="Bank country"></div>Supported data-preczn values:
| Value | Element rendered | Purpose |
|---|---|---|
number | Card number input | Card PAN entry |
expiration | Expiration input | MM/YY entry |
cvv | CVV input | 3–4 digit security code |
accountNumber | ACH input | Bank account number |
routingNumber | ACH input | Bank routing number |
accountType | Native picker | checking, savings, etc. |
bankCountry | Native picker | US, CA |
⚠️ Do not put
idornameattributes on these containers, and do not nest them inside a<form>element. PaymentElements iframes never expose values to the parent page; placing them in a form can cause browsers to intercept submission attempts.
The container's width and height determine the iframe's size — apply your own layout, padding, and border styles to the container, and use configure() (Step 3) to style the input inside the iframe.
Step 3: Configure Styles and Placeholders
Call configure() to style the inputs inside the iframes and set placeholder text. Styles are applied across all mounted fields and can be updated at any time.
Preczn.PaymentElements.configure({
style: {
base: {
color: '#1a1a1a',
fontFamily: 'system-ui, -apple-system, sans-serif',
fontSize: '16px',
fontWeight: '400',
lineHeight: '24px',
'::placeholder': {
color: '#9ca3af'
}
},
focus: { color: '#0f172a' },
invalid: { color: '#dc2626' },
complete: { color: '#16a34a' }
},
placeholders: {
number: '1234 5678 9012 3456',
expiration: 'MM / YY',
cvv: 'CVC'
}
});Pseudo-states: base, focus, invalid, complete, empty. The ::placeholder selector accepts color and fontStyle.
Allowed style properties: color, fontFamily, fontSize, fontWeight, fontStyle, fontSmoothing, lineHeight, letterSpacing, textAlign, padding, textTransform.
Constraints (enforced for security):
fontFamilymust be drawn from:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif,serif,monospace.fontSizemust be in the range8–48pxor0.5–3rem.- Properties not on the allowlist (e.g.,
background,marginTop,url(...)) are silently dropped.
ℹ️ Calling
configure()before any container is mounted is fine — the configuration is applied to each field as it mounts. You can callconfigure()again later to update styles or placeholders live.
Step 4: Listen for Field Events (optional)
Subscribe to per-field events to drive UI affordances (e.g., a brand icon, a "valid" checkmark, an error message under a field):
Preczn.PaymentElements.on('number', 'change', function (state) {
// state = { empty, complete, brand, error }
if (state.brand) {
document.getElementById('brand-icon').className = 'brand-' + state.brand.toLowerCase();
}
});
Preczn.PaymentElements.on('cvv', 'change', function (state) {
document.getElementById('cvv-error').textContent = state.error || '';
});Available events:
| Event | Payload | Fires when |
|---|---|---|
ready | {} | Field iframe has mounted and is usable |
change | { empty, complete, brand?, error? } | User input changes the field state |
focus | {} | Field gains focus |
blur | {} | Field loses focus |
Remove a handler with Preczn.PaymentElements.off(field, event, handler).
If you need to wait until every field is mounted before enabling a submit button:
Preczn.PaymentElements.ready().then(function () {
document.getElementById('submit').disabled = false;
});Step 5: Obtain a Token
When the customer submits the form, request a token. PaymentElements collects values from the iframes, validates them, posts them to Preczn's tokenization endpoint, and invokes your callback with the result.
Define a callback function:
var tokenCallback = function (result, errors) {
if (errors) {
console.error('Tokenization failed', errors);
return;
}
var precznToken = result.token;
// Send precznToken to your server to charge or store
};Card token:
Preczn.PaymentElements.getSingleUseToken(tokenCallback);ACH token:
Preczn.PaymentElements.getSingleUseAchToken(tokenCallback);Utility Functions
| Function | Returns |
|---|---|
getCardBrand() | Card brand string once a card number has been entered, otherwise null. Possible values: VISA, MASTERCARD, AMEX, DISCOVER/JCB, OTHER. |
isValidCardNumber() | true once the entered card number passes Luhn and length checks; false if entered but invalid; null if no input has been received yet. |
ready() | A Promise<void> that resolves once every registered field has mounted. |
if (Preczn.PaymentElements.isValidCardNumber()) {
console.log('Brand:', Preczn.PaymentElements.getCardBrand());
}Security Notes
- Always use public API keys (
priv_test…/priv_live_…); never use private/secret keys in browser code. - Sensitive values (PAN, CVV, account/routing numbers) never enter your page's JavaScript context, DOM, storage, or postMessage channels — they only exist inside Preczn-served iframes.
- The SDK coordinates with iframes via authenticated cross-frame channels and requires that messages originate from the SDK it boots; spoofed
postMessagetraffic from other origins is ignored. - Your page's Content Security Policy must allow:
script-src https://api.preczn.comframe-src https://api.preczn.comconnect-src https://api.preczn.com
- Do not use
id,name, or wrap PaymentElements containers in a<form>. Mixing PaymentElements containers with PaymentFields-style inputs in the same form is unsupported and will log a console warning. - For a full PCI Shared Responsibility Matrix and SAQ A attestation package, contact your Preczn account team.
Updated about 8 hours ago
