Pay Session
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.
