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.

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.