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.

FieldDescription
numberCard number (13–19 digits, Luhn-validated)
securityCodeCVV / CSC (3–4 digits)
expiryMonthExpiry month, MM
expiryYearExpiry year, YY
expiryDateCombined MM/YY expiry (use instead of expiryMonth + expiryYear)
nameOnCardCardholder 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.

  1. setFocusStyle() — styles applied while a field has focus.
  2. setHoverStyle() — styles applied while the cursor hovers a field.
  3. setPlaceholderStyle() — styles applied to the placeholder pseudo-element.
  4. setErrorStyle() — styles applied when a field is in the error state (set automatically on validation failure, or manually via setFieldState).
  5. setValidStyle() — styles applied when a field is in the valid state.
  6. setStyle() — generic setter that accepts any combination of the above states, plus default, in a single call.
  7. setFieldState() — programmatically toggle a field into the error or valid state (or pass null to 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.



What’s Next