Client-side tokenization for card payments
For card payments, Pomelo offers a secure way to capture card details and return a token you can use to create charges. Card data is sent directly from the customer's browser to a secure endpoint — it never touches your server. The returned token can then be safely used in server-side operations.
This is the recommended pattern for card payments: it keeps your servers out of PCI scope while still letting you build a fully custom checkout UI.
Using the PomeloJS drop-in client
PomeloJS is a client-side library that handles tokenization. It exposes an easy-to-use API for integrating card capture into your web app, with events for success and error states.
You'll need your public API key from the app you've created on the merchant dashboard.
Setup
Include the library:
<script src="https://js.pomelopay.com/pomelo-2.0.0.min.js"></script>
Then initialise it. In a browser environment the library exposes a global PomeloJS constructor:
const pomelo = new PomeloJS('your-public-key', 'transaction-id', { debug: false });
Usage
Vanilla JavaScript example:
// Create a transaction id from your backend using your private API key,
// then pass it to the client.
const transactionId = 'transaction-id';
const client = new PomeloJS('pk_production_1234ABCDEFhbvl', transactionId);
document.body.innerHTML = `
<pp-card-payment></pp-card-payment>
`;
document.addEventListener('DOMContentLoaded', () => {
const $cardPayment = document.querySelector('pp-card-payment');
$cardPayment.addEventListener('paymentTransactionSuccess', (event) => {
console.log('Payment success:', event.detail);
});
$cardPayment.addEventListener('error', (event) => {
console.error('Payment error:', event.detail);
});
$cardPayment.addEventListener('formChanged', (event) => {
console.log('Form changed:', event.detail);
});
});
API
PomeloJS instance methods
updateOptions(opts)
Updates the component's options using a deep merge and applies the new state.
Parameters:
opts: Partial<Options>— options to update.targetElement: HTMLElement— optional. If omitted, options are updated for all elements.
client.updateOptions({
transactionId: '<transaction id>',
optionalFields: ['cardCvv'],
});
createElement(tagName, options)
Creates a new element with the given tag and options. Throws if the tag isn't allowed. The created element is added to the DOM.
Parameters:
tagName: string— must be a supported tag name (see below).options: Record<string, string>— optional. Attributes to set on the created element.
try {
const el = client.createElement('pp-card-payment', {
'payment-flow': 'rate',
});
document.body.appendChild(el);
} catch (error) {
console.error(error.message);
}
destroyElement($el)
Removes the element from the internal store and the DOM.
const el = document.querySelector('pp-card-payment');
if (el) client.destroyElement(el);
Options
| Field | Type | Description |
|---|---|---|
fieldMaxLengths | Record<keyof FieldIDs, number> | Maximum length per card-payment field. |
optionalFields | Array<keyof FieldIDs> | Fields that are visible but not required. |
publicKey | string | Public API key for client-side tokenization. |
transactionId | string | Unique identifier for the transaction. |
debug | boolean | Toggle verbose logging. |
demo | boolean | Toggle demo mode. |
texts | Record<string, string> | Custom text strings for UI elements (see below). |
allowedCardSchemes | Array<PaymentCardType> | Card schemes accepted for payment. |
excludedFields | Array<keyof FieldIDs> | Fields excluded from the form. |
cardSchemeLogos | Array<PaymentCardType | string> | Card-scheme logos to display (data-URI or URL). |
Note: Excluding essential card fields (number, expiry) will cause transaction failures. Configure carefully.
Customisable text strings
| Key | Default | Format placeholders |
|---|---|---|
submitButton | Pay | |
cardName | Name on card | |
cardNumber | Card Number | |
cardExpiry | MM/YY | |
cardCvv | CVV | |
cvvInfo | "Where do I find my CVV2?" with platform-specific guidance | |
requiredErr | This field is required | |
allowedCards | Our platform only supports payments from {cards} | {cards} |
invalidCardNumberErr | Invalid card number | |
invalidCvvErr | Invalid CVV | |
invalidCardExpiryErr | Invalid expiry date | |
invalidCardNameErr | Invalid name | |
ccyRateExchange | Rate: {counterCurrency} 1 = {currency} {rate} | {counterCurrency}, {currency}, {rate} |
rateOptionInfo | The exchange rate and any fees will be set by your issuing bank | |
invalidInputErr | Invalid input |
Allowed card schemes
american-express, diners-club, discover, elo, hiper, hipercard, jcb, maestro, mastercard, mir, unionpay, visa.
Card payment element
The <pp-card-payment> custom element renders the actual card form.
Attributes
payment-flow
The current flow of the payment process.
- Type:
string - One of:
card_entry|rate|moto - Default:
card_entry
<pp-card-payment payment-flow="rate"></pp-card-payment>
options
A JSON string parsed into an options object. Overrides the constructor options.
- Type:
string(JSON)
<pp-card-payment options='{"transactionId":"","allowedCardSchemes":["visa","mastercard"]}'></pp-card-payment>
invalid
Whether the form is currently invalid.
- Type:
boolean - Default:
false
<pp-card-payment invalid></pp-card-payment>
Events
The element fires CustomEvents you can listen to with addEventListener. All examples assume const $el = document.querySelector('pp-card-payment').
| Event | When | event.detail |
|---|---|---|
rateOptionsLoaded | Rate options finish loading | RateOption[] |
rateOptionChange | A rate option is selected | RateOption |
formChanged | The form changes | FormEventDetail |
paymentTransactionSuccess | A payment transaction succeeds | { paymentFlow } |
formSubmitted | The form is submitted | FormEventDetail |
error | A payment error occurs | { error } |
cardPaymentStateChange | The card-payment state changes | full state object |
cardPaymentFlowChange | The flow changes | new flow value |
threeDsClose | The 3D-Secure modal closes | { success: boolean } |
$el.addEventListener('paymentTransactionSuccess', (event) => {
console.log('Payment transaction successful:', event.detail);
});
$el.addEventListener('error', (event) => {
console.error('Payment error:', event.detail);
});
$el.addEventListener('threeDsClose', (event) => {
console.log('3D Secure modal closed:', event.detail);
});
Styling
The card-payment element is styled via CSS variables. Three groups:
Base styles
pp-card-payment {
--pp-button-background-color: #f00;
--pp-font-family: serif;
--pp-input-font-size: 32px;
--pp-input-font-weight: 900;
--pp-button-font-family: cursive;
--pp-background-color: #fff;
}
Input styles
pp-card-payment {
--pp-input-width: 100%;
--pp-input-background: #fff;
--pp-input-border-color: #d1d1d1;
--pp-input-font-size: 16px;
--pp-input-margin: 0;
--pp-input-padding: 8px;
--pp-input-height: 40px;
--pp-input-error-border-color: #ff4d4f;
--pp-input-error-border-radius: 4px;
--pp-input-border: 1px solid #d1d1d1;
--pp-input-font-family: cursive;
--pp-input-font-weight: 400;
--pp-input-text-indent: 0;
}
Button styles
pp-card-payment {
--pp-button-background-color: #007bff;
--pp-button-border-radius: 4px;
--pp-button-color: #fff;
--pp-button-font-size: 16px;
--pp-button-font-weight: 400;
--pp-button-height: 40px;
--pp-button-letter-spacing: 0.5px;
--pp-button-padding: 0 12px;
}
Deprecated v1 keys
cardInputInvalid
Types
Options
interface Options {
fieldMaxLengths: Record<keyof FieldIDs, number>;
optionalFields: Array<keyof FieldIDs>;
publicKey: string;
transactionId: string;
debug: boolean;
demo: boolean;
texts: Record<string, string>;
allowedCardSchemes: Array<PaymentCardType>;
excludedFields: Array<keyof FieldIDs>;
cardSchemeLogos: Array<PaymentCardType>;
}
FieldIDs
interface FieldIDs {
cardCvv: string;
cardExpiry: string;
cardNumber: string;
cardName: string;
}
PaymentCardType
type PaymentCardType =
| 'visa'
| 'mastercard'
| 'american-express'
| 'amex'
| 'diners-club'
| 'discover'
| 'jcb'
| 'unionpay'
| 'maestro'
| 'elo'
| 'mir'
| 'hiper'
| 'hipercard';
FormEventDetail
interface FormEventDetail {
values: Partial<Record<keyof FieldIDs, string>>;
cardDetails: PaymentCard;
errors: Partial<Record<keyof FieldIDs, string>>;
touched: Partial<Record<keyof FieldIDs, boolean>>;
invalid: boolean;
}
PaymentCard
interface PaymentCard {
number: string;
name: string;
expiryMonth: string;
expiryYear: string;
cvv: string;
}
RateOption
interface RateOption {
pair: string;
currency: string;
counterCurrency: string;
rate: number;
payAmount: number;
amountBeforeDiscount: number;
bankPayAmount: number;
fxSavings: number;
country: string;
}
BinLookup
interface BinLookup {
tokenId: string;
_id: string;
bin8: string;
issuer: string;
brand: string;
type: string;
country: string;
expiredAt: Date;
createdAt: Date;
updatedAt: Date;
}
PaymentFlow
type PaymentFlow = 'card_entry' | 'rate' | 'motopay';
More examples
Styling the card form
Customise the appearance of the card form via CSS variables:
.payment-form {
--pp-button-background-color: #f00;
--pp-font-family: serif;
--pp-font-size: 16px;
--pp-input-font-weight: 900;
--pp-button-font-family: cursive;
}
<head>
<script src="https://js.pomelopay.com/pomelo-2.0.0.min.js"></script>
<script>
const transactionId = 'transaction-id';
const client = new PomeloJS('pk_production_1234ABCDEFhbvl', transactionId);
</script>
</head>
<body>
<pp-card-payment class="payment-form"></pp-card-payment>
</body>
Making fields optional
Specify field ids in optionalFields:
const client = new PomeloJS(_PUB_KEY, transactionId, {
optionalFields: ['cardName'],
});
The card-name field becomes optional in the rendered form.
Restricting card schemes
Limit which schemes the form accepts via allowedCardSchemes. You can also specify which logos to display via cardSchemeLogos (in display order).
The card schemes must also be configured on your merchant account — restricting client-side won't enable a scheme that isn't enabled server-side.
const allowedCards = ['mastercard', 'visa', 'unionpay', 'jcb'];
const cardSchemeLogos = [
'mastercard',
'visa',
'unionpay',
'jcb',
'data:image/png;base64,…', // additional branding logo (data URI or valid image URL)
];
const client = new PomeloJS(_PUB_KEY, transactionId, {
allowedCardSchemes: allowedCards,
cardSchemeLogos,
});
License
PomeloJS is MIT licensed.