Pay Session
Pay Session is the industry standard for iframe payments embedded into your website. It allows you to securely collect card details without handling sensitive data on your servers.
The Payment Steps
flowchart TB
1[Create Pay Session]
2[Load JS Library]
3[Create Payment Fields]
4[Customer Enters Payment Details]
5[Customer Submits Payment]
6[Receive Confirmation]
1-->2-->3-->4-->5-->6
What is Pay Session
Secure iframe-based card input fields that embed directly into your custom checkout form. You design the page, we provide the secure fields.
Key Characteristics
- Your branding, layout, and styling
- Iframe fields isolate card data from your servers
- Seamless user experience without redirects
- Moderate PCI compliance requirements
Best For
- Branded checkout experiences matching your website
- Maintaining control over the complete user flow
- Balance between customization and security
Here is the full payment flow for Pay Session.
sequenceDiagram
participant Client
participant Server
participant Prahsys
Client->>Server: Step 1. Request to create payment session
Server->>Prahsys: Step 2. Initialize payment session
Prahsys-->>Server: Step 3. Return session ID
Server-->>Client: Step 4. Return session ID
Client->>Client: Step 5. Load payment fields using session ID
Client->>Server: Step 6. Submit payment with session ID
Server->>Prahsys: Step 7. Process payment with session ID
Prahsys-->>Server: Step 8. Payment result
Server-->>Client: Step 9. Payment confirmation
Loading the Script
After creating a session server-side, load the Pay Session script in your checkout page. The script URL embeds the merchant ID and session ID, so each script tag is unique per session.
<script src="https://gateway.prahsys.com/n1/merchant/{merchantId}/session/{sessionId}/script.js"></script>Once loaded, the script registers window.PaymentSession, which is the object you use to configure fields, collect input, and style the iframes.
When working with a SANDBOX merchant and loading PaySession or PayPortal script, you must use your test API key to create the session (sk_test_123...)
Supported Fields
Pay Session currently supports card payments. Each entry below is a key under fields.card in your configure() call and maps a CSS selector in your page to a secure iframe.
| Field | Description |
|---|---|
number | Card number (13–19 digits, Luhn-validated) |
securityCode | CVV / CSC (3–4 digits) |
expiryMonth | Expiry month, MM |
expiryYear | Expiry year, YY |
expiryDate | Combined MM/YY expiry (use instead of expiryMonth + expiryYear) |
nameOnCard | Cardholder name |
Use the combined expiryDate field if you want a single input instead of separate month/year fields. This is recommended since it works better with Google's autofill in Chrome browsers.
TypeScript Interface for PaymentSession
declare global {
interface Window {
PaymentSession: PaymentSession;
}
}
interface PaymentSession {
/**
* Initialize the session: replace your input elements with secure iframes
* and wire up callbacks.
*/
configure(config: PaymentSessionConfig): void;
/**
* Collect values from all configured fields and submit them to the
* session. The formSessionUpdate callback fires with the result.
* Resolves on success, rejects on system error.
*/
updateSessionFromForm(formType: "card"): Promise<void>;
/**
* Generic style setter — accepts any combination of states.
*/
setStyle(fields: string[], styles: StyleConfig): void;
/** Convenience: style applied on focus. */
setFocusStyle(fields: string[], styles: FieldStyles): void;
/** Convenience: style applied on hover. */
setHoverStyle(fields: string[], styles: FieldStyles): void;
/** Convenience: style applied to the placeholder pseudo-element. */
setPlaceholderStyle(fields: string[], styles: FieldStyles): void;
/** Convenience: style applied when a field is in the error state. */
setErrorStyle(fields: string[], styles: FieldStyles): void;
/** Convenience: style applied when a field is in the valid state. */
setValidStyle(fields: string[], styles: FieldStyles): void;
/**
* Programmatically set the visual state of one or more fields.
* Pass null to clear the state.
*/
setFieldState(fields: string[], state: "error" | "valid" | null): void;
}
interface PaymentSessionConfig {
/**
* Optional. The session ID is already embedded in the script URL; if
* provided here it must match.
*/
session?: string;
fields: {
card?: {
number?: string;
securityCode?: string;
expiryMonth?: string;
expiryYear?: string;
expiryDate?: string;
nameOnCard?: string;
};
};
/** Clickjacking mitigation strategies. */
frameEmbeddingMitigation?: Array<"javascript" | "x-frame-options" | "csp">;
callbacks: {
/** Fires once all configured iframes are mounted and ready. */
initialized: (response: InitializedResponse) => void;
/** Fires with the result of updateSessionFromForm(). */
formSessionUpdate: (response: FormSessionUpdateResponse) => void;
};
}
interface InitializedResponse {
status: "ok" | "error";
message?: string;
}
interface FormSessionUpdateResponse {
status: "ok" | "fields_in_error" | "system_error";
session?: {
id: string;
version?: number;
};
/**
* Field-level validation errors. Keys are field names; values are
* short error codes: "required" or "invalid".
*/
errors?: {
number?: string;
securityCode?: string;
expiryMonth?: string;
expiryYear?: string;
expiryDate?: string;
nameOnCard?: string;
};
}
interface StyleConfig {
default?: FieldStyles;
hover?: FieldStyles;
focus?: FieldStyles;
error?: FieldStyles;
valid?: FieldStyles;
placeholder?: FieldStyles;
}
interface FieldStyles {
[cssProperty: string]: string;
}Configuring a Session
A minimal configure() call wires field selectors to iframes and registers your callbacks.
PaymentSession.configure({
fields: {
card: {
number: "#card-number",
securityCode: "#security-code",
expiryMonth: "#expiry-month",
expiryYear: "#expiry-year",
nameOnCard: "#cardholder-name",
},
},
frameEmbeddingMitigation: ["javascript"],
callbacks: {
initialized: function (response) {
if (response.status === "ok") {
// Fields are mounted — reveal your form.
}
},
formSessionUpdate: function (response) {
if (response.status === "ok") {
// response.session.id is the updated session — send to your server.
} else if (response.status === "fields_in_error") {
// response.errors contains per-field codes; see "Handling Validation Errors".
} else {
// system_error — network failure or server error. Show a generic message.
}
},
},
});When the customer is ready to pay, call updateSessionFromForm("card"). The cached field values are validated together and the formSessionUpdate callback fires with the result.
PaymentSession.updateSessionFromForm("card");Styling Payment Fields
Pay Session exposes per-state styling methods so the iframes blend into your design.
setFocusStyle()— styles applied while a field has focus.setHoverStyle()— styles applied while the cursor hovers a field.setPlaceholderStyle()— styles applied to the placeholder pseudo-element.setErrorStyle()— styles applied when a field is in the error state (set automatically on validation failure, or manually viasetFieldState).setValidStyle()— styles applied when a field is in the valid state.setStyle()— generic setter that accepts any combination of the above states, plusdefault, in a single call.setFieldState()— programmatically toggle a field into the error or valid state (or passnullto clear).
Style properties are split internally between the iframe container and the input inside it. Border, box-shadow, and outline properties apply to the iframe element itself. Everything else — typography, padding, color, background — applies to the input inside. This split happens automatically; you pass styles as a single flat object.
PaymentSession.setFocusStyle(["card.number", "card.securityCode"], {
borderColor: "#3b82f6",
borderWidth: "2px",
});
PaymentSession.setHoverStyle(["card.number", "card.securityCode"], {
borderColor: "#6366f1",
});
PaymentSession.setPlaceholderStyle(["card.number", "card.nameOnCard"], {
color: "#9ca3af",
fontWeight: "400",
});
PaymentSession.setErrorStyle(["card.number", "card.securityCode"], {
borderColor: "#ef4444",
borderWidth: "2px",
boxShadow: "0 0 0 3px rgba(239, 68, 68, 0.1)",
});
PaymentSession.setValidStyle(["card.number"], {
borderColor: "#10b981",
});const fields = [
"card.number",
"card.securityCode",
"card.expiryMonth",
"card.expiryYear",
];
// Set default styles (applied immediately, not tied to a state).
// Use this to set fonts, padding, and background to match your form.
PaymentSession.setStyle(fields, {
default: {
fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
fontSize: "14px",
color: "#111827",
backgroundColor: "#ffffff",
paddingTop: "8px",
paddingBottom: "8px",
paddingLeft: "12px",
paddingRight: "12px",
},
});
// Or set multiple states in a single call.
PaymentSession.setStyle(fields, {
default: { borderColor: "#d1d5db", borderWidth: "1px" },
focus: { borderColor: "#3b82f6", borderWidth: "2px" },
error: { borderColor: "#ef4444", borderWidth: "2px" },
valid: { borderColor: "#10b981" },
});Handling Validation Errors
When updateSessionFromForm() finds problems with the submitted values, formSessionUpdate fires with status: "fields_in_error" and an errors object keyed by field name. The error value is a short code — typically "required" or "invalid" — that you map to a user-friendly message.
const errorMessages = {
required: "This field is required",
invalid: "Please enter a valid value",
};
function showFieldErrors(errors) {
// Note: error styles are applied automatically — setFieldState is not required here.
// Call it manually only if you need to mark additional fields as errors.
Object.entries(errors).forEach(([fieldName, code]) => {
const messageEl = document.getElementById(`${fieldName}-error`);
if (messageEl) {
messageEl.textContent = errorMessages[code] || code;
messageEl.classList.remove("hidden");
}
});
}
PaymentSession.configure({
fields: {
card: {
number: "#card-number",
securityCode: "#security-code",
expiryMonth: "#expiry-month",
expiryYear: "#expiry-year",
nameOnCard: "#cardholder-name",
},
},
callbacks: {
initialized: () => {},
formSessionUpdate: (response) => {
if (response.status === "ok") {
// Forward response.session.id to your server to charge the card
} else if (response.status === "fields_in_error" && response.errors) {
showFieldErrors(response.errors);
}
},
},
});When the customer corrects the input and resubmits, the previous error states are cleared automatically before the new submission is validated.
Updated 2 days ago
