# Qore Payments Payment Gateway Documentation > API documentation for Qore Payments Gateway — card payments, 3D Secure, Google Pay, payouts, webhooks, and more. This file contains the complete documentation for AI consumption. --- ## Source: Overview URL: https://docs.qorepayments.com/index.html # Getting Started Integration must be done within our integration environment first. When this process is finished and approved by our staff, you may go live and start processing with real money. !!! tip "Building with AI?" This documentation is available in machine-readable format for AI coding tools. See [AI-Assisted Integration](getting-started/ai-assisted-integration.md) for `llms.txt` and `llms-full.txt` URLs you can paste directly into Claude, Cursor, GitHub Copilot, or any other AI assistant. To start integrating with Payments Platform you will need: | Property | Description | Where to find it? | | -------------- | ------------------------------------------------------------------------ | --------------------------------------------------------- | | client\_id | OIDC client\_id | Merchant details / Keycloak / client\_id | | client\_secret | OIDC client\_secret, keep this private | Merchant details / Keycloak / client\_secret on dashboard | | terminal\_id | Terminal against which the transaction session is created and authorized | Terminal / ID | --- ## Source: Environments URL: https://docs.qorepayments.com/getting-started/environments.html # Environments The table below outlines commonly used configuration values for integrating API. These values will be referenced throughout the document. | Value Type | Environment | Value | | ---------- | ----------- | -------------------------------------------------------------------------------- | | OIDC URL | uat | [https://uat-auth.qorepayments.com](https://uat-auth.qorepayments.com) | | OIDC Realm | uat | uat | | OIDC URL | prod | [https://auth.qorepayments.com](https://auth.qorepayments.com) | | OIDC Realm | prod | production | | API URL | uat | [https://uat-api.qorepayments.com](https://uat-api.qorepayments.com) | | API URL | prod | [https://api.qorepayments.com](https://api.qorepayments.com) | --- ## Source: Authentication URL: https://docs.qorepayments.com/getting-started/authentication/ # Authentication When authorizing against the Payments Platform one must provide a standard HTTP `Authorization` header in the following format: ``` Authorization: Bearer ``` Bearer token is to be obtained through OIDC client credentials flow. Sample of the flow is available in next section. `client_id` and `client_secret` values can be obtained through dashboard under Merchant details. --- ## Source: Obtain/Refresh Access Token URL: https://docs.qorepayments.com/getting-started/authentication/obtain-access-token.html # Obtain/Refresh Access Token [OAD(../../assets/openapi/keycloak.yaml)] --- ## Source: AI-Assisted Integration URL: https://docs.qorepayments.com/getting-started/ai-assisted-integration.html # AI-Assisted Integration This documentation site is optimized for AI-assisted development. Machine-readable versions of the full documentation are available at the following URLs, compatible with [llms.txt](https://llmstxt.org/) standard: | File | URL | Description | |------|-----|-------------| | `llms.txt` | [https://docs.qorepayments.com/llms.txt](https://docs.qorepayments.com/llms.txt) | Structured index of all documentation pages — ideal for giving your AI assistant a quick overview | | `llms-full.txt` | [https://docs.qorepayments.com/llms-full.txt](https://docs.qorepayments.com/llms-full.txt) | Complete documentation in a single plain-text file — use this for deep context when building integrations | ## How to use with AI coding tools ### Using llms.txt (recommended starting point) `llms.txt` contains a structured, concise index of all documentation pages with short descriptions. Paste the URL directly into your AI assistant or coding tool: ``` Please read the API documentation index at: llms.txt Then help me integrate the card payment authorization endpoint. ``` ### Using llms-full.txt (complete context) `llms-full.txt` contains the complete documentation in a single plain-text file. Use this when your AI tool needs full context — for example, when implementing a complex flow like 3DS card payments or webhook handling: ``` Please read the full API documentation at: llms-full.txt Then help me implement a complete 3DS card payment flow. ``` ### Supported AI tools These files are compatible with all major AI coding tools and assistants that can fetch URLs or accept text context: - **Claude** (claude.ai, Claude Code) - **GitHub Copilot** - **Cursor** - **ChatGPT** - Any tool supporting the [llms.txt](https://llmstxt.org/) standard ## Discovery The URLs for `llms.txt` and `llms-full.txt` are also embedded as `` tags in every page of this documentation site, allowing AI agents and crawlers to discover them automatically from any page. --- ## Source: Overview URL: https://docs.qorepayments.com/references/payments/ # Payments *** ## Payment Method A **Payment** transaction allows merchants to collect funds from a customer using **alternative payment methods** such as Open Banking, UPI, wallets, bank transfers, or QR codes. Unlike card-based transactions that use the `/api/transactions/authorize` endpoint, payments are initiated via a dedicated `/api/transactions/payment` endpoint with a simplified request structure. Payments are processed asynchronously — the transaction may initially return a `PENDING` status or a redirect URL, with the final result delivered via [webhook](../webhooks/index.md). *** ### Key Characteristics 1. **Alternative Payment Methods** * Payments support non-card payment methods: **Open Banking**, **UPI**, **Wallets**, **Bank Transfer**, and **QR Code**. * The payment method is specified as a simple string in the `method` field — no structured payment data object is required. 2. **Fixed Transaction Type** * The transaction type is always `PAYMENT`. Unlike the authorize endpoint, there is no `transaction_type` field to set. 3. **No Card Data or Encryption** * Payment requests do not include card data. No encryption is required. * The `browser_info` field is **not required**. 4. **Redirect-Based Flow** * Most payment methods involve a redirect to a third-party provider (bank, wallet app, QR page) where the customer completes the payment. * The API response will typically include a `redirect` object with the URL to redirect the customer. 5. **Terminal ID Specification** * The `terminal_id` must be specified in the request. The terminal must be configured to support payment transactions (feature: `TRX_TYPE_PAYMENT`). 6. **Asynchronous Processing** * Payments are typically processed asynchronously. The initial API response may return a `PENDING` status or a redirect. * The final transaction status is delivered via a [Transaction Processed Webhook](../webhooks/transaction-processed-webhook.md). * Always verify the final status using the [Transaction Status API](../../transaction-api/transaction-status-api.md). *** ### Supported Payment Methods | Method | Value | Description | |--------|-------|-------------| | Open Banking | `OPEN_BANKING` | Bank-initiated payment via Open Banking APIs | | UPI | `UPI` | Unified Payments Interface (India) | | Wallets | `WALLETS` | Digital wallet payments | | Bank Transfer | `BANK_TRANSFER` | Direct bank transfer | | QR Code | `QR_CODE` | QR code-based payment | *** ### Request Requirements | Field | Required | Notes | |-------|----------|-------| | `terminal_id` | Yes | Must support payment transactions (`TRX_TYPE_PAYMENT`) | | `reference` | Yes | Unique payment reference per terminal (max 80 chars). Idempotent — duplicate references with the same amount/currency will return the existing transaction. | | `method` | Yes | Payment method type: `OPEN_BANKING`, `UPI`, `WALLETS`, `BANK_TRANSFER`, or `QR_CODE` | | `currency` | Yes | ISO 4217 currency code (e.g., `EUR`, `USD`, `THB`) | | `amount` | Yes | Amount in minor units (e.g., `10000` = 100.00 EUR) | | `description` | No | Human-readable payment description (max 100 chars) | | `customer` | No | Customer details (name, email, address, etc.) | | `country` | No | ISO 3166-1 alpha-2 country code (e.g., `TH`, `ID`, `IN`) | | `customer_ip` | No | Customer IP address (defaults to request IP) | | `metadata` | No | Arbitrary key-value metadata | | `return_url` | No | URL to redirect after payment completes (approved, declined, canceled, or error) | | `options` | No | Provider-specific or payment-type-specific options | *** ### Processing Flow ```mermaid sequenceDiagram participant Customer participant Merchant participant Gateway participant Provider Customer->>Merchant: Select payment method Merchant->>Gateway: POST /api/transactions/payment Gateway->>Gateway: Validate request & terminal Gateway->>Gateway: Resolve payment provider Gateway->>Provider: Initiate payment alt Redirect Required Provider-->>Gateway: Redirect URL Gateway-->>Merchant: Response with redirect Merchant->>Customer: Redirect to provider Customer->>Provider: Complete payment (bank login, wallet, QR scan) Provider->>Gateway: Callback with result else Direct Response Provider-->>Gateway: Processing result end Gateway-->>Merchant: Transaction response Gateway->>Merchant: Webhook notification (APPROVED/DECLINED) ``` *** ### Supported Transactions | Transaction Type | Supported | Description | | ---------------- | --------- | ----------- | | PAYMENT | Yes | Collect funds via alternative payment method | | REFUND | Depends | Depends on payment provider and method | | CAPTURE | No | Not applicable (single-step flow) | | VOID | No | Not applicable | *** ### Transaction Statuses | Status | Description | Final State | | -------- | ----------- | ----------- | | CREATED | Transaction created but not yet processed | No | | PENDING | Payment submitted and awaiting customer action or provider confirmation | No | | APPROVED | Payment successfully completed | Yes | | DECLINED | Payment rejected by the provider or bank | Yes | | CANCELED | Payment canceled by the customer or system | Yes | | INVALID | Transaction marked as invalid due to validation errors | Yes | | SESSION_EXPIRED | Payment session expired before completion | Yes | For the full transaction lifecycle, see [Supported Transaction Statuses](../../transaction-api/supported-transaction-statuses.md). *** ### Redirect Handling After the customer completes (or abandons) the payment on the provider's page, they are redirected back to the merchant: | Transaction Status | Redirect Destination | | ------------------ | -------------------- | | APPROVED | Merchant's `return_url` | | DECLINED / CANCELED | Merchant's `return_url` | | PENDING | Awaits callback resolution | > Always verify the final transaction status via the [Transaction Status API](../../transaction-api/transaction-status-api.md) regardless of the redirect outcome. *** ### Important Notes * The `reference` field is idempotent per terminal. Sending the same reference with the same amount and currency returns the existing transaction. Different amount/currency values will result in a validation error. * No card data or encryption is required — the `method` field specifies the payment type as a simple string. * The `browser_info` field is not required for payment transactions. * Always subscribe to [webhooks](../webhooks/index.md) to receive the final payment status, as payments are typically processed asynchronously. * The `country` field may be required by certain providers to determine the correct payment flow or bank selection. * Provider-specific configuration can be passed via the `options` field. --- ## Source: Direct API Integration URL: https://docs.qorepayments.com/references/payments/direct-api-integration.html # Direct API Integration ## Payments – API Integration This page provides guidance for integrating **Payments** using the **Direct API approach**. Payments allow you to collect funds from a customer using alternative payment methods such as Open Banking, UPI, wallets, bank transfers, or QR codes. Your platform is responsible for: * Presenting the available payment methods to the customer * Initiating the payment request with the correct `method` value * Handling redirects to the payment provider * Handling asynchronous webhook callbacks for final transaction status * Verifying the transaction status via the [Transaction Status API](../../transaction-api/transaction-status-api.md) *** ### Payment Method When initiating a payment request, specify the payment method in the `method` field: | Method | Description | |--------|-------------| | `OPEN_BANKING` | Bank-initiated payment via Open Banking APIs | | `UPI` | Unified Payments Interface (India) | | `WALLETS` | Digital wallet payments | | `BANK_TRANSFER` | Direct bank transfer | | `QR_CODE` | QR code-based payment | *** ### Request Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `terminal_id` | string | Yes | Terminal identifier configured for payment transactions | | `reference` | string | Yes | Unique payment reference per terminal (1–80 chars) | | `method` | string | Yes | Payment method type (e.g., `OPEN_BANKING`, `UPI`, `WALLETS`, `BANK_TRANSFER`, `QR_CODE`) | | `currency` | string | Yes | ISO 4217 currency code (e.g., `EUR`, `THB`, `INR`) | | `amount` | integer | Yes | Amount in minor units (e.g., `10000` = 100.00) | | `description` | string | No | Human-readable description (max 100 chars) | | `customer` | object | No | Customer details | | `country` | string | No | ISO 3166-1 alpha-2 country code (e.g., `TH`, `IN`) | | `customer_ip` | string | No | Customer IP address (defaults to request IP) | | `metadata` | object | No | Arbitrary key-value metadata | | `return_url` | string | No | Redirect URL after payment completes (approved, declined, canceled, or error) | | `options` | object | No | Provider-specific options | *** ### Example Request ```json { "terminal_id": "TERM001", "reference": "PAY-20260331-001", "description": "Order payment via Open Banking", "currency": "EUR", "amount": 10000, "method": "OPEN_BANKING", "customer": { "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "phone": "+1234567890", "address": "123 Example Street", "city": "Amsterdam", "country": "NL", "postal_code": "1012AB" }, "country": "NL", "customer_ip": "192.168.1.1", "metadata": { "order_id": "ORD-12345" }, "return_url": "https://merchant.example.com/return" } ``` > The `amount` is in **minor units**. For EUR, `10000` represents **100.00 EUR**. *** ### Example Response (Redirect) Most payment methods require the customer to be redirected to the provider's page (e.g., bank login, wallet confirmation): ```json { "result": null, "action": null, "redirect": { "transaction_id": "PyTxAbCdEfGhIjKlMnOp", "session_id": "PyTxAbCdEfGhIjKlMnOp", "url": "https://provider.example.com/pay?session=abc123" }, "form_submit": null } ``` When the response contains a `redirect`, the merchant must redirect the customer to the provided `url`. After the customer completes or cancels the payment, they are redirected back to the merchant's `return_url`. *** ### Example Response (Pending) ```json { "result": { "id": "PyTxAbCdEfGhIjKlMnOp", "merchant_id": "0000000000000fpg-dev", "order_id": "ORD_PAY_20260331_001", "terminal_id": "TERM001", "reference": "PAY-20260331-001", "description": "Order payment via Open Banking", "currency": "EUR", "amount": 10000, "customer": { "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com" }, "processing_result": { "payment_provider_id": "PP_OB001", "payment_provider_account_id": "PPACCT_EUR001" }, "approved": false, "pending": true, "channel": "ecommerce", "transaction_type": "PAYMENT", "status": "PENDING", "payment_method": { "method": "OPEN_BANKING", "type": "payment_open_banking" }, "normalized_amount": 10000, "errors": [] }, "action": null, "redirect": null, "form_submit": null } ``` *** ### Example Response (Approved) ```json { "result": { "id": "PyTxAbCdEfGhIjKlMnOp", "merchant_id": "0000000000000fpg-dev", "order_id": "ORD_PAY_20260331_001", "terminal_id": "TERM001", "reference": "PAY-20260331-001", "description": "Order payment via Open Banking", "currency": "EUR", "amount": 10000, "customer": { "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com" }, "processing_result": { "payment_provider_id": "PP_OB001", "payment_provider_account_id": "PPACCT_EUR001", "approval_code": "AUTH789", "reference_number": "REF_PAY_001" }, "approved": true, "pending": false, "channel": "ecommerce", "transaction_type": "PAYMENT", "status": "APPROVED", "payment_method": { "method": "OPEN_BANKING", "type": "payment_open_banking" }, "normalized_amount": 10000, "errors": [] }, "action": null, "redirect": null, "form_submit": null } ``` *** ### Processing Codes The `processing_code` field in the response indicates the result of the transaction: | Code | Name | Description | |------|------|-------------| | `0000` | Approved | Transaction approved | | `1000` | Declined | Transaction declined | | `1001` | Failed | Transaction failed | | `1002` | Pending | Transaction pending | | `1003` | Canceled | Transaction canceled | | `1004` | Insufficient Funds | Declined due to insufficient funds | | `1005` | Session Expired | Transaction session expired | | `2000` | Invalid Request | General validation error | | `2013` | Terminal Not Found | Terminal does not exist | | `2014` | Terminal Inactive | Terminal is not active | | `2015` | Terminal Not Accepting | Terminal is not accepting payments | | `2017` | Merchant Inactive | Merchant is not active | *** [OAD(docs/assets/openapi/openapi-payment.yaml)] --- ## Source: Overview URL: https://docs.qorepayments.com/references/cards/ # Cards ### Introduction Card authorization is a critical process in the payment gateway ecosystem that ensures secure transactions between customers and merchants. This documentation provides an overview of the card authorization process, including its purpose, how it works, and the key components involved. Proper understanding of card authorization is essential for maintaining compliance with industry standards and enhancing user experiences during transactions. ### Key Components 1. **Data Encryption** * Sensitive data is encrypted using the merchant's public key to ensure that personal and financial information remains secure throughout the transaction process. * Encryption example is available under [encryption-example.md](encryption-example.md "mention") 2. **Terminal ID Specification** * It is essential to specify the `terminal_id` during the transaction setup. This unique identifier represents the point of sale or the terminal that is processing the transaction. * Merchant can configure different payment methods for different `terminal_id` 3. **Transaction Execution** * The transaction is executed against the specified terminal. This ensures that the payment is processed at the correct location, allowing for accurate tracking and reporting of transactions. *** ## Simulator The card simulator allows you to test your integration without processing real transactions. When your merchant account is configured with the **card-simulator** payment provider, you can control the transaction outcome by passing specific fields in the `metadata` object of your authorization request. ### Controlling the Response By default, the simulator returns an **approved** response. You can override the response by including any of the following fields in the `metadata` object: | Metadata Field | Type | Default | Description | |---|---|---|---| | `response_code` | `string` | `TRX_STATUS_APPROVED` | The processing code to return — see [Response Codes](#response-codes) below | | `response_message` | `string` | `"approved"` | The response message | | `approval_code` | `string` | *(random)* | The approval code returned in the response | | `reference_number` | `string` | *(random)* | The provider reference number | | `payment_provider_status` | `string` | `"approved"` | The payment provider status field | | `payment_provider_response_message` | `string` | `"approved"` | The payment provider response message | | `payment_provider_response_code` | `string` | `"approved"` | The payment provider response code | #### Example: Approved Transaction ```json { "terminal_id": "your-terminal-id", "payment_method": { "type": "card", "card": { "encrypted_data": "..." } }, "metadata": {} } ``` With no metadata fields set, the simulator returns `TRX_STATUS_APPROVED`. #### Example: Declined Transaction ```json { "terminal_id": "your-terminal-id", "payment_method": { "type": "card", "card": { "encrypted_data": "..." } }, "metadata": { "response_code": "TRX_STATUS_DECLINED", "response_message": "insufficient funds" } } ``` #### Example: Pending Transaction ```json { "metadata": { "response_code": "TRX_STATUS_PENDING", "response_message": "pending review" } } ``` ### Simulating Redirects To simulate a redirect response (e.g., for 3DS1 or alternative payment methods that redirect the customer): ```json { "metadata": { "simulate_redirect": "https://example.com/redirect-target" } } ``` The simulator will return a `redirect` response with the provided URL instead of a direct transaction result. ### Simulating Form Submit To simulate a form submit response: ```json { "metadata": { "simulate_form_submit": "{\"url\":\"https://example.com/form-target\",\"data\":{\"PaReq\":\"test-value\",\"MD\":\"test-md\"}}" } } ``` The simulator will return a `form_submit` response with the provided URL and form data fields. ### Simulating Action Required To simulate an action required response (e.g., 3DS authentication prompt): ```json { "metadata": { "simulate_authentication_required": "{\"payment_data\":\"test-payment-data\",\"type\":\"THREE_DS_2_FINGERPRINT\",\"token\":\"test-token\"}" } } ``` The simulator will return an `action` response with the provided payment data, type, and token. ### Response Codes The following `response_code` values can be used in the metadata to simulate different outcomes: | Response Code | Status | Code | |---|---|---| | `TRX_STATUS_APPROVED` | APPROVED | 0000 | | `TRX_STATUS_DECLINED` | DECLINED | 1000 | | `TRX_STATUS_FAILED` | DECLINED | 1001 | | `TRX_STATUS_PENDING` | PENDING | 1002 | | `TRX_STATUS_CANCELED` | CANCELED | 1003 | | `TRX_STATUS_DECLINED_INSUFFICIENT_FUNDS` | DECLINED | 1004 | | `TRX_STATUS_SESSION_EXPIRED` | SESSION_EXPIRED | 1005 | | `AUTHENTICATION_NOT_AUTHENTICATED` | INVALID | 3000 | | `RISK_FAILED_BLACKLISTED` | DECLINED | 4100 | | `RISK_FAILED_FRAUD` | DECLINED | 4101 | | `RISK_FAILED_VELOCITY_CHECK` | DECLINED | 4200 | !!! note "3DS Authentication Failure" If the card's 3DS authentication result has `trans_status: "N"` (not authenticated), the simulator automatically returns a **declined** response regardless of the metadata fields. This simulates the real-world behavior where a failed 3DS authentication prevents the transaction from being authorized. --- ## Source: Authorize URL: https://docs.qorepayments.com/references/cards/authorize.html # Authorize [OAD(../../assets/openapi/openapi-transaction-authorize.yaml)] --- ## Source: Encryption Example URL: https://docs.qorepayments.com/references/cards/encryption-example.html # Encryption Example All sensitive card data must be encrypted using your merchant **RSA public key** with **RSA-OAEP (SHA-256)** padding before sending to the API. Each field is encrypted individually and the result is Base64-encoded. Fields requiring encryption: **card number**, **CVV**, **expiration month**, **expiration year**. Your RSA public key is available in your merchant dashboard under **Merchant details**. *** === "Shell (OpenSSL)" ```bash encrypt_field() { echo -n "$1" | openssl pkeyutl -encrypt \ -pubin -inkey <(echo "$PUBLIC_KEY") \ -pkeyopt rsa_padding_mode:oaep \ -pkeyopt rsa_oaep_md:sha256 \ -pkeyopt rsa_mgf1_md:sha256 | base64 } PUBLIC_KEY="-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... -----END PUBLIC KEY-----" ENCRYPTED_CARD_NUMBER=$(encrypt_field "4111111111111111") ENCRYPTED_CVV=$(encrypt_field "123") ENCRYPTED_EXP_MONTH=$(encrypt_field "12") ENCRYPTED_EXP_YEAR=$(encrypt_field "2027") ``` === "Node.js" Save as `encrypt-card.mjs` and run with `node encrypt-card.mjs`: ```javascript // encrypt-card.mjs import { publicEncrypt, constants } from 'node:crypto'; const PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... -----END PUBLIC KEY-----`; function encryptField(plaintext) { const encrypted = publicEncrypt( { key: PUBLIC_KEY, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256', }, Buffer.from(plaintext, 'utf-8') ); return encrypted.toString('base64'); } console.log('encrypted_card_number:', encryptField('4111111111111111')); console.log('encrypted_cvv:', encryptField('123')); console.log('encrypted_expiration_month:', encryptField('12')); console.log('encrypted_expiration_year:', encryptField('2027')); ``` === "Java" Save as `EncryptCard.java` and run with `java EncryptCard.java` (Java 11+): ```java // EncryptCard.java import javax.crypto.Cipher; import javax.crypto.spec.OAEPParameterSpec; import javax.crypto.spec.PSource; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.PublicKey; import java.security.spec.MGF1ParameterSpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; public class EncryptCard { // Paste the Base64 body of your PEM key here (without BEGIN/END markers) private static final String PUBLIC_KEY_BASE64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."; public static String encryptField(String plaintext) throws Exception { byte[] keyBytes = Base64.getDecoder().decode( PUBLIC_KEY_BASE64.replaceAll("\\s", "") ); PublicKey publicKey = KeyFactory.getInstance("RSA") .generatePublic(new X509EncodedKeySpec(keyBytes)); Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding"); cipher.init(Cipher.ENCRYPT_MODE, publicKey, new OAEPParameterSpec( "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT )); byte[] encrypted = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(encrypted); } public static void main(String[] args) throws Exception { System.out.println("encrypted_card_number: " + encryptField("4111111111111111")); System.out.println("encrypted_cvv: " + encryptField("123")); System.out.println("encrypted_expiration_month: " + encryptField("12")); System.out.println("encrypted_expiration_year: " + encryptField("2027")); } } ``` *** ## Using the Encrypted Values Include the encrypted fields in your authorize request: ```json { "payment_method": { "type": "card", "data": { "encrypted_card_number": "", "encrypted_cvv": "", "encrypted_expiration_month": "", "encrypted_expiration_year": "" } } } ``` !!! warning "Server-side only" Card encryption must happen on your server. Never expose the RSA public key or raw card data in the browser. --- ## Source: Overview URL: https://docs.qorepayments.com/references/oct/ # OCT (Original Credit Transaction) ### Introduction An **Original Credit Transaction (OCT)** is a payment transaction type that allows merchants to send funds directly to a cardholder's card account. Unlike standard purchase or authorization transactions that pull funds from a card, an OCT pushes funds to the card. Common use cases include payouts, disbursements, refunds to a different card, and marketplace seller payments. !!! warning "Integration Type Limitation" OCT transactions are only supported via the **Direct API** integration. **Hosted Payment Page (HPP)** and **Embedded Fields (EF)** integration types are **not supported** for OCT transactions. ### Key Characteristics 1. **Push Payment** * OCT sends funds **to** a cardholder's card, rather than collecting funds from it. * This is an initial transaction type — it does not require a parent transaction. 2. **Simplified Payment Data** * Only the `encrypted_card_number` is required in the payment method data. CVV, expiration month, and expiration year are **not required**. * Card data must still be encrypted using the merchant's public key. * Encryption example is available under [encryption-example](../cards/encryption-example.md "mention") 3. **No 3D Secure Authentication** * OCT transactions do not require 3D Secure authentication. Authentication is handled implicitly by the gateway. * As a result, `browser_info` is **not required** in the request. 4. **Terminal ID Specification** * It is essential to specify the `terminal_id` during the transaction setup. This unique identifier represents the point of sale or the terminal that is processing the transaction. * The terminal must be configured to support OCT transactions. ### Request Requirements | Field | Required | Notes | |-------|----------|-------| | `terminal_id` | Yes | Must support OCT | | `reference` | Yes | Unique transaction reference (max 40 chars) | | `description` | Yes | Transaction description | | `currency` | Yes | ISO 4217 currency code | | `amount` | Yes | Amount in minor units (e.g., cents) | | `transaction_type` | Yes | Must be `OCT` | | `payment_method.type` | Yes | Must be `card` | | `payment_method.data.encrypted_card_number` | Yes | Encrypted using merchant public key | | `customer` | Yes | Customer details | | `customer_ip` | No | Customer IP address | | `metadata` | No | Optional key-value pairs | ### Processing Flow ```mermaid sequenceDiagram participant Merchant participant Gateway participant PSP Merchant->>Gateway: POST /api/transactions/authorize
(transaction_type: OCT) Gateway->>Gateway: Decrypt & validate card number Gateway->>Gateway: Extract BIN, determine card brand Gateway->>Gateway: Route to payment provider Gateway->>PSP: Process OCT PSP-->>Gateway: Processing result Gateway-->>Merchant: Transaction response ``` ### Important Notes * The transaction is processed as an initial transaction — no parent transaction reference is needed. * Supported payment method type is **card only**. * **Hosted Payment Page (HPP) and Embedded Fields (EF) integration types are not supported for OCT transactions.** OCT must be processed via the [API integration](../../integration-types/api-integration.md) only. --- ## Source: Authorize URL: https://docs.qorepayments.com/references/oct/authorize.html # OCT Authorize [OAD(../../assets/openapi/openapi-oct-authorize.yaml)] --- ## Source: Overview URL: https://docs.qorepayments.com/references/3ds/ # 3D Secure (3DS) ## Introduction 3D Secure (3DS) is an authentication protocol designed to reduce fraud in online card payments. It adds an extra verification step where the cardholder's bank (issuer) authenticates the customer before the payment is authorized. The Payment Gateway supports **3D Secure 2.x**, which provides a better user experience through risk-based authentication — many transactions are approved without customer interaction (frictionless flow), while higher-risk transactions require the cardholder to complete a challenge (e.g., entering an OTP or confirming in a banking app). 3DS authentication is a **regulatory requirement** under PSD2 Strong Customer Authentication (SCA) in the European Economic Area and is widely adopted by card networks globally. ### When is 3DS Triggered? | Payment Method | 3DS Behavior | |---|---| | **Card** (`card`) | 3DS is always initiated | | **Saved Card** (`saved_card`) | 3DS is always initiated | | **Google Pay** (`google-pay`) | 3DS is initiated for `PAN_ONLY` tokens; skipped for `CRYPTOGRAM_3DS` tokens | | **Network Token** (`network-token`) | 3DS is skipped when a valid cryptogram and ECI are provided | | **OCT** | No 3DS — Original Credit Transactions do not require authentication | | **Payout** | No 3DS — Payouts do not require authentication | *** ## 3DS Flow Overview When a card transaction requires 3DS authentication, the response from the `/api/transactions/authorize` endpoint will include additional fields that guide the merchant through the authentication process. The flow involves up to two steps: **device fingerprinting** and **challenge authentication**. ```mermaid sequenceDiagram participant Customer participant Merchant participant Gateway as Payment Gateway participant ACS as Issuer ACS Merchant ->> Gateway: POST /api/transactions/authorize Gateway -->> Merchant: Response with action (THREE_DS_2_FINGERPRINT) Note over Merchant: Step 1 — Device Fingerprint Merchant ->> Merchant: Decode token from action response Merchant ->> ACS: POST threeDSMethodData (hidden iframe) ACS -->> Gateway: Notification (via three_ds_method_notification_url) Merchant ->> Gateway: POST /api/transactions/three-ds/{id}/request-authentication Gateway -->> Merchant: Response (approved, or challenge required) alt Frictionless Flow Note over Merchant: Transaction approved without customer interaction else Challenge Required Note over Merchant: Step 2 — Challenge Merchant ->> Merchant: Decode token, build CReq Merchant ->> ACS: POST creq (full-page redirect to ACS) Customer ->> ACS: Complete authentication (OTP, biometric, etc.) ACS -->> Gateway: Authentication result (CRes) Gateway ->> Gateway: Process authentication & authorize transaction Gateway -->> Customer: Redirect to merchant return_url Note over Merchant: Customer returned — verify status via API or webhook end ``` *** ## Response Handling After calling `/api/transactions/authorize`, the response contains four top-level fields. Which fields are populated determines the next step: | Response Scenario | `result` | `action` | `redirect` | `form_submit` | Next Step | |---|---|---|---|---|---| | **Approved (no 3DS needed)** | Transaction data | `null` | `null` | `null` | Done — transaction is approved | | **Declined** | Transaction data | `null` | `null` | `null` | Done — transaction is declined | | **Fingerprint required** | `null` | Action details | `null` | `null` | Perform [device fingerprinting](#step-1-device-fingerprint) | | **Redirect required** | `null` | `null` | Redirect URL | `null` | [Redirect](#redirect-flow) customer to URL | | **Form submit required** | `null` | `null` | `null` | Form details | [Auto-submit form](#form-submit-flow) to URL | *** ## Step 1 — Device Fingerprint When the response contains an `action` object with `type: "THREE_DS_2_FINGERPRINT"`, you must collect the customer's device fingerprint by submitting a hidden form to the issuer's Access Control Server (ACS). ### Action Response Structure ```json { "result": null, "action": { "transaction_id": "vvIVFmPuwYosePLsoDsW", "session_id": "abc123-session-id", "type": "THREE_DS_2_FINGERPRINT", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6...", "payment_data": "encrypted_payment_data_string" }, "redirect": null, "form_submit": null } ``` ### Action Fields | Field | Type | Description | |---|---|---| | `transaction_id` | `string` | The transaction identifier | | `session_id` | `string` | The session identifier for this 3DS flow | | `type` | `string` | The action type — see [Action Type Values](#action-type-values) below | | `token` | `string` | Base64-encoded JSON containing ACS URLs and 3DS server transaction data | | `payment_data` | `string` | Encrypted payment data — must be sent back when completing the transaction | ### Action Type Values The `type` field uses the following values: | Value | Meaning | Next Step | |---|---|---| | `THREE_DS_2_FINGERPRINT` | Device fingerprint collection required | Perform [device fingerprinting](#implementation) | | `THREE_DS_2_CHALLENGE` | 3DS challenge authentication required | Perform [challenge authentication](#step-2-challenge) | !!! warning "Match on action type correctly" Use the exact values above when checking the `type` field. For example, use `action.type === "THREE_DS_2_FINGERPRINT"` and `action.type === "THREE_DS_2_CHALLENGE"` in your implementation. Do **not** check for shortened values like `"fingerprint"` or `"challenge"`. ### Decoded Token Structure The `token` field is a Base64-encoded JSON object with the following structure: ```json { "acs_url": "https://acs.issuer.com/challenge", "message_version": "2.2.0", "three_ds_server_trans_id": "abc123-server-trans-id", "three_ds_method_notification_url": "https://gateway.example.com/three-ds/notification", "three_ds_method_url": "https://acs.issuer.com/fingerprint", "acs_trans_id": "xyz789-acs-trans-id", "challenge_window_size": "05" } ``` | Field | Description | |---|---| | `three_ds_method_url` | URL to submit the fingerprint form to | | `three_ds_server_trans_id` | 3DS server transaction ID — used in subsequent API calls | | `three_ds_method_notification_url` | URL where the ACS will notify the gateway of fingerprint completion | | `acs_url` | ACS URL for challenge step (used in Step 2 if needed) | | `acs_trans_id` | ACS transaction ID (used in Step 2 if needed) | | `message_version` | 3DS protocol version | | `challenge_window_size` | Challenge window size code (used in Step 2 if needed) | ### Implementation **1. Decode the token:** ```javascript const decodedToken = JSON.parse(atob(action.token)); ``` **2. Build the `threeDSMethodData`:** Construct a JSON object containing the server transaction ID and notification URL, then Base64 URL-encode it: ```javascript const fingerprintData = { threeDSServerTransID: decodedToken.three_ds_server_trans_id, threeDSMethodNotificationURL: decodedToken.three_ds_method_notification_url }; const threeDSMethodData = btoa(JSON.stringify(fingerprintData)); ``` **3. Create and auto-submit the hidden form:** ```html
``` **4. Wait for the fingerprint to complete** (timeout: ~5 seconds), then proceed to request authentication. ### Requesting Authentication After the fingerprint step, request authentication by calling the 3DS authentication endpoint: ```http POST /api/transactions/three-ds/{threeDsServerTransId}/request-authentication Authorization: Bearer Content-Type: application/json ``` ```json { "fingerprint_result": "", "payment_data": "" } ``` The response uses the same structure as the initial `/api/transactions/authorize` response. Check the top-level fields to determine the next step: - If `result` is present — the transaction is finalized (frictionless flow: approved or declined) - If `action` is present with `type: "THREE_DS_2_CHALLENGE"` — proceed to [challenge authentication](#step-2-challenge) - If `redirect` is present — [redirect](#redirect-flow) the customer - If `form_submit` is present — [auto-submit the form](#form-submit-flow) *** ## Step 2 — Challenge If the issuer requires additional customer authentication after the fingerprint step, the response will indicate a challenge. This can be returned as an `action` with `type: "THREE_DS_2_CHALLENGE"`, a `redirect`, or a `form_submit`. ### Action with Challenge Type When the response contains an `action` with `type: "THREE_DS_2_CHALLENGE"`: ```json { "result": null, "action": { "transaction_id": "vvIVFmPuwYosePLsoDsW", "session_id": "abc123-session-id", "type": "THREE_DS_2_CHALLENGE", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6...", "payment_data": "encrypted_payment_data_string" }, "redirect": null, "form_submit": null } ``` **1. Decode the token** (same structure as the fingerprint token): ```javascript const decodedToken = JSON.parse(atob(action.token)); ``` **2. Build the Challenge Request (CReq):** ```javascript const creq = { threeDSServerTransID: decodedToken.three_ds_server_trans_id, acsTransID: decodedToken.acs_trans_id, messageVersion: decodedToken.message_version, messageType: "CReq", challengeWindowSize: decodedToken.challenge_window_size }; const encodedCReq = btoa(JSON.stringify(creq)); ``` **3. Submit the CReq to the ACS:** You can submit the CReq using either a **full-page redirect** or an **iframe**. Choose the approach that fits your integration: === "Full-Page Redirect (Recommended)" Redirect the customer's browser to the ACS for authentication. This is the simplest approach — the gateway handles the authentication result and redirects the customer back to your `return_url` with the processed transaction result. ```html
``` After the customer completes the challenge, the gateway processes the authentication, authorizes the transaction, and redirects the customer to your `return_url`. See [After Authentication](#after-authentication) for details on verifying the transaction status. !!! info "No server-side callback needed" With the full-page redirect approach, the gateway handles the `/authentication-completed` step internally. You do **not** need to call this endpoint yourself — the transaction result is delivered directly to your `return_url`. === "Iframe" Display the challenge UI within your page using an iframe. This approach keeps the customer on your page and gives you more control over the UX, but requires handling the challenge response client-side. ```html
``` The customer will see the issuer's authentication UI inside the iframe (e.g., OTP input, biometric prompt). **4. Listen for the Challenge Response (CRes):** The ACS sends the challenge result back via a `window.message` event: ```javascript const acsOrigin = new URL(decodedToken.acs_url).origin; window.addEventListener('message', function(event) { if (event.origin !== acsOrigin) return; // Ignore messages from unexpected origins const data = JSON.parse(event.data); if (data.type === 'CHALLENGE_RESPONSE') { const transStatus = data.trans_status; // Proceed to submit the result to the gateway } }); ``` **5. Submit the authentication result to the gateway:** ```http POST /api/transactions/three-ds/authentication-completed Authorization: Bearer Content-Type: application/json ``` ```json { "payment_data": "", "session_id": "", "trans_status": "" } ``` The response follows the same structure as the initial `/api/transactions/authorize` response — check the `result`, `action`, `redirect`, and `form_submit` fields to determine the next step. !!! warning "Additional authentication may be required" The `/authentication-completed` endpoint does **not** always return a final transaction result. If the issuer determines that further authentication is needed, the response will contain a new `action`, `redirect`, or `form_submit` — just like the initial authorization response. When this happens, handle the response exactly as you would after the initial `/api/transactions/authorize` call: - If `action` is present with `type: "THREE_DS_2_FINGERPRINT"` — perform [device fingerprinting](#step-1-device-fingerprint) again - If `action` is present with `type: "THREE_DS_2_CHALLENGE"` — perform [challenge authentication](#step-2-challenge) again - If `redirect` is present — [redirect](#redirect-flow) the customer - If `form_submit` is present — [auto-submit the form](#form-submit-flow) - If `result` is present — the transaction is finalized (approved or declined) This additional authentication round can occur **at most once** per authentication attempt. If the issuer requests further authentication after the second round, the transaction will be automatically declined. However, if the transaction is declined and the gateway [auto-retries](#handling-repeated-authentication) with a different provider, a new 3DS flow may be initiated — see [Limits](#limits) for details. #### Transaction Status Values (trans_status) | Value | Meaning | |---|---| | `Y` | Authentication successful | | `N` | Not authenticated (denied) | | `U` | Authentication could not be performed | | `A` | Authentication attempted | | `C` | Challenge required (additional interaction needed) | | `R` | Authentication rejected | *** ### Redirect Flow When the response contains a `redirect` object instead of an action: ```json { "result": null, "action": null, "redirect": { "transaction_id": "vvIVFmPuwYosePLsoDsW", "session_id": "abc123-session-id", "url": "https://acs.issuer.com/challenge?id=..." }, "form_submit": null } ``` Redirect the customer's browser to the provided `url`. After authentication, the gateway processes the transaction and redirects the customer back to your `return_url`. See [After Authentication](#after-authentication) for details on verifying the transaction status. ### Form Submit Flow When the response contains a `form_submit` object: ```json { "result": null, "action": null, "redirect": null, "form_submit": { "transaction_id": "vvIVFmPuwYosePLsoDsW", "session_id": "abc123-session-id", "url": "https://acs.issuer.com/challenge", "data": { "PaReq": "eJxVUt1ugjAUvl...", "MD": "vvIVFmPuwYosePLsoDsW", "TermUrl": "https://gateway.example.com/callback" } } } ``` Create a form with the provided fields and auto-submit it: ```html
``` After authentication, the gateway processes the transaction and redirects the customer back to your `return_url`. See [After Authentication](#after-authentication) for details on verifying the transaction status. *** ## Handling Repeated Authentication In certain scenarios, the Payment Gateway may return an `action`, `redirect`, or `form_submit` response after you have already completed a 3DS authentication step. This means the issuer or the gateway requires additional authentication before the transaction can be finalized. ### When Does This Happen? **After completing a challenge (`/authentication-completed`):** The issuer may request an additional authentication round. For example, after submitting the challenge result with `trans_status: "C"`, the gateway may return a new `action` with `type: "THREE_DS_2_FINGERPRINT"` or `type: "THREE_DS_2_CHALLENGE"`. **After a transaction is declined and automatically retried:** When a transaction is declined, the Payment Gateway may automatically retry the transaction with a different payment provider. This retry goes through a full authorization flow, which includes 3DS authentication. As a result, you may receive a new `action`, `redirect`, or `form_submit` response that requires the customer to complete 3DS authentication again — this time with the new provider. ### How to Handle It Your integration should **always** check the response structure after every API call in the 3DS flow, not just after the initial `/api/transactions/authorize`: ``` 1. Check if `result` is present → Transaction is finalized (approved/declined) 2. Check if `action` is present → Handle fingerprint or challenge 3. Check if `redirect` is present → Redirect the customer 4. Check if `form_submit` is present → Auto-submit the form ``` !!! tip "Build a response handler, not a step counter" Rather than coding a linear sequence of steps, implement a single response handler that inspects each response and routes to the appropriate action. This naturally handles repeated authentication without special-case logic. ### Limits The gateway enforces limits at two levels: - **Per-authentication attempt:** A maximum of **one additional authentication round** is allowed. If the issuer requests further authentication after this, the attempt will be automatically declined. - **Fallback routing:** When a transaction is declined, the gateway may automatically retry with a different payment provider — up to **N times** (configurable, default is 5). Each retry goes through a full authorization and 3DS flow, so the customer may be asked to authenticate multiple times across retries. *** ## After Authentication Once the customer completes the 3DS challenge (via full-page redirect, redirect, or form submit flows), the gateway processes the authentication result and authorizes the transaction. The customer is then redirected back to your `return_url` regardless of the outcome (approved, declined, canceled, or error). !!! note "Auto-retries" If the gateway performed an [auto-retry](#handling-repeated-authentication) with a different provider, the customer may need to complete 3DS again before reaching a final status. ### Verifying Transaction Status The redirect to your `return_url` indicates that the transaction has been processed, but does not include the transaction result in the URL. To determine the outcome (approved or declined), verify the transaction status server-side: ```http GET /api/transactions/{transaction_id}/status Authorization: Bearer ``` The transaction will be in one of the following statuses: | Status | Description | |---|---| | `APPROVED` | Authentication successful, transaction authorized | | `DECLINED` | Authentication failed or transaction was declined by the issuer | | `CANCELED` | Customer canceled or abandoned the challenge | | `SESSION_EXPIRED` | Authentication session timed out before the customer completed the challenge | !!! tip "Use Webhooks" For the most reliable status updates, configure a [webhook](../webhooks/index.md) to receive real-time notifications when the transaction reaches a final status. This is especially important for handling edge cases where the customer closes the browser before being redirected back to your site. *** ## Browser Info The `browser_info` object is **required** for all card transactions and is used by the issuer to assess risk during 3DS authentication. You must collect this information from the customer's browser. ### Required Fields | Field | Type | Description | Example | |---|---|---|---| | `user_agent` | `string` | The browser's User-Agent header | `"Mozilla/5.0..."` | | `accept_header` | `string` | The browser's Accept header | `"text/html,application/xhtml+xml..."` | | `java_enabled` | `boolean` | Whether Java is enabled | `false` | | `color_depth` | `integer` | Screen color depth in bits | `24` | | `screen_height` | `integer` | Screen height in pixels | `1080` | | `screen_width` | `integer` | Screen width in pixels | `1920` | | `time_zone_offset` | `integer` | Timezone offset from UTC in minutes | `-120` | | `language` | `string` | Browser language | `"en-US"` | ### JavaScript Collection Example ```javascript const browserInfo = { user_agent: navigator.userAgent, accept_header: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", java_enabled: navigator.javaEnabled(), color_depth: screen.colorDepth, screen_height: screen.height, screen_width: screen.width, time_zone_offset: new Date().getTimezoneOffset(), language: navigator.language }; ``` !!! info "Accept Header" The `accept_header` value cannot be read directly from JavaScript. Use a standard value as shown above, or capture it server-side from the customer's HTTP request headers. *** ## Timeouts | Step | Timeout | Description | |---|---|---| | Device fingerprint | 5 seconds | If the ACS does not respond within this time, proceed to request authentication regardless | | Challenge | 10 minutes | Maximum time allowed for the customer to complete the challenge | *** ## Complete Integration Example Below is a simplified example of the full 3DS flow for a card payment: ```mermaid sequenceDiagram participant Browser as Customer Browser participant Server as Merchant Server participant Gateway as Payment Gateway participant ACS as Issuer ACS Browser ->> Server: Checkout (card details + browser info) Server ->> Gateway: POST /api/transactions/authorize Gateway -->> Server: action (type: THREE_DS_2_FINGERPRINT, token, payment_data) Server -->> Browser: Decoded token + payment_data Note over Browser: Step 1 — Device Fingerprint Browser ->> Browser: Build threeDSMethodData from token Browser ->> ACS: POST threeDSMethodData (hidden iframe to threeDsMethodUrl) Note over Browser: Wait up to 5 seconds Browser ->> Server: Fingerprint complete Server ->> Gateway: POST /api/transactions/three-ds/{id}/request-authentication Gateway -->> Server: Frictionless approval or challenge action alt Approved (Frictionless) Server -->> Browser: Payment successful else Challenge Required (action) Server -->> Browser: Challenge token + payment_data Note over Browser: Step 2 — Challenge Browser ->> Browser: Build CReq from token Browser ->> ACS: POST creq (full-page redirect to acsUrl) Browser ->> ACS: Customer completes authentication ACS -->> Gateway: Authentication result (CRes) Gateway ->> Gateway: Process authentication & authorize transaction Gateway -->> Browser: Redirect to merchant return_url Note over Browser: Customer returned — verify status via API or webhook else Redirect Required Server -->> Browser: Redirect URL Browser ->> ACS: Browser redirect ACS -->> Gateway: Authentication result Gateway -->> Browser: Redirect to merchant return_url Note over Browser: Customer returned — verify status via API or webhook end ``` *** ## Simulator The 3DS authentication simulator allows you to test different authentication flows without connecting to a real 3DS Directory Server or Access Control Server. When your merchant account is configured with the **authentication-simulator** service, you can control the 3DS flow by passing the `simulated_flow` field in the transaction `metadata`. ### Controlling the 3DS Flow Include the `simulated_flow` field in your authorization request metadata to select which authentication scenario to simulate: ```json { "terminal_id": "your-terminal-id", "payment_method": { "type": "card", "card": { "encrypted_data": "..." } }, "metadata": { "simulated_flow": "VERSIONING_FINGERPRINT_Y_CHALLENGE" } } ``` If `simulated_flow` is not provided, the simulator defaults to `VERSIONING_FINGERPRINT_Y_CHALLENGE`. ### Available Simulated Flows | Flow | Fingerprint | Authentication Result | Description | |---|---|---|---| | `VERSIONING_NOT_ENROLLED` | — | N/A | Card is not enrolled in 3DS. The gateway proceeds directly to authorization without authentication. | | `VERSIONING_FINGERPRINT_Y_CHALLENGE` | Yes (with method URL) | Challenge → `Y` | Full 3DS flow: fingerprint collection via method URL, then challenge authentication. The challenge completes with `trans_status: Y` (authenticated). **This is the default flow.** | | `VERSIONING_FINGERPRINT_TIMEOUT_CHALLENGE` | Yes (simulates timeout) | Challenge → `Y` | Same as above, but the fingerprint step simulates a timeout. The flow continues to challenge authentication after the timeout. | | `VERSIONING_FINGERPRINT_TIMEOUT_FRICTIONLESS` | Yes (simulates timeout) | Frictionless `A` | Fingerprint step simulates a timeout, but the issuer approves frictionlessly with `trans_status: A` (attempted). | | `VERSIONING_FINGERPRINT_N_CHALLENGE` | No (no method URL) | Challenge → `Y` | No fingerprint collection (no method URL returned). Proceeds directly to challenge authentication. | | `VERSIONING_FINGERPRINT_N_FRICTIONLESS` | No (no method URL) | Frictionless `N` | No fingerprint collection. The issuer returns `trans_status: N` (not authenticated) — the transaction will be declined. | | `VERSIONING_FINGERPRINT_A_FRICTIONLESS` | No (no method URL) | Frictionless `A` | No fingerprint collection. The issuer returns `trans_status: A` (attempted) — the transaction is approved frictionlessly. | ### Flow Details #### Flows with Fingerprint (Method URL) The flows `VERSIONING_FINGERPRINT_Y_CHALLENGE` and `VERSIONING_FINGERPRINT_TIMEOUT_CHALLENGE` return a `ThreeDsSupportedResponse` that includes a `threeDsMethodUrl`. Your integration should perform the [device fingerprinting step](#step-1-device-fingerprint) before requesting authentication. - **`VERSIONING_FINGERPRINT_Y_CHALLENGE`**: The method URL responds normally, allowing the fingerprint to complete within the timeout. - **`VERSIONING_FINGERPRINT_TIMEOUT_CHALLENGE`**: The method URL deliberately delays its response, simulating a timeout scenario. Your integration should proceed after the 5-second fingerprint timeout. Both flows result in a challenge being returned after the authentication request. #### Flows without Fingerprint (No Method URL) The flows `VERSIONING_FINGERPRINT_N_CHALLENGE`, `VERSIONING_FINGERPRINT_N_FRICTIONLESS`, and `VERSIONING_FINGERPRINT_A_FRICTIONLESS` return a versioning response without a `threeDsMethodUrl`. Your integration should skip the fingerprint step and proceed directly to requesting authentication. - **`VERSIONING_FINGERPRINT_N_CHALLENGE`**: Authentication request returns a challenge. - **`VERSIONING_FINGERPRINT_N_FRICTIONLESS`**: Authentication request returns a frictionless result with `trans_status: N` — the transaction is declined without customer interaction. - **`VERSIONING_FINGERPRINT_A_FRICTIONLESS`**: Authentication request returns a frictionless result with `trans_status: A` — the transaction is approved without customer interaction. #### Not Enrolled Flow `VERSIONING_NOT_ENROLLED` returns a `ThreeDsNotSupportedResponse`, indicating the card is not enrolled in 3DS. The gateway skips authentication entirely and proceeds to authorization. ### Customizing Challenge Responses You can override fields in the challenge response by including an `authentication_simulator` object in your transaction metadata. This allows you to test specific challenge outcomes: ```json { "metadata": { "simulated_flow": "VERSIONING_FINGERPRINT_Y_CHALLENGE", "authentication_simulator": { "transStatus": "N", "eci": "07", "authenticationValue": "custom-auth-value" } } } ``` Available override fields in `authentication_simulator`: | Field | Type | Default | Description | |---|---|---|---| | `transStatus` | `string` | `Y` | The challenge result status (`Y`, `N`, `U`, `A`, `C`, `R`) | | `eci` | `string` | `"05"` | Electronic Commerce Indicator | | `authenticationValue` | `string` | *(simulator default)* | The authentication value (CAVV) | | `messageVersion` | `string` | `"2.2.0"` | 3DS message version | | `dsTransID` | `string` | *(simulator default)* | Directory Server transaction ID | ### Testing Scenarios Here are common integration test scenarios and the metadata to use: #### Happy Path — Full 3DS with Challenge ```json { "metadata": { "simulated_flow": "VERSIONING_FINGERPRINT_Y_CHALLENGE" } } ``` Tests the complete flow: fingerprint → challenge → approved. #### Frictionless Approval ```json { "metadata": { "simulated_flow": "VERSIONING_FINGERPRINT_A_FRICTIONLESS" } } ``` Tests a frictionless approval without customer interaction. #### Frictionless Decline (Not Authenticated) ```json { "metadata": { "simulated_flow": "VERSIONING_FINGERPRINT_N_FRICTIONLESS" } } ``` Tests a frictionless decline where the issuer rejects the authentication. #### Card Not Enrolled in 3DS ```json { "metadata": { "simulated_flow": "VERSIONING_NOT_ENROLLED" } } ``` Tests behavior when the card does not support 3DS authentication. #### Fingerprint Timeout ```json { "metadata": { "simulated_flow": "VERSIONING_FINGERPRINT_TIMEOUT_FRICTIONLESS" } } ``` Tests that your integration handles a fingerprint timeout correctly and proceeds to authentication. *** ## Related Documentation - [Card Authorization](../cards/index.md) — Card payment overview - [API Integration](../../integration-types/api-integration.md) — Direct API integration guide - [Google Pay](../google-pay/index.md) — Google Pay integration with 3DS details - [Network Token](../network-token/index.md) — Network token payments (3DS pre-authenticated) - [Transaction Statuses](../../transaction-api/supported-transaction-statuses.md) — All transaction lifecycle states - [Webhooks](../webhooks/index.md) — Receive real-time payment notifications --- ## Source: Overview URL: https://docs.qorepayments.com/references/google-pay/ # Google Pay *** ## Introduction Google Pay is a digital wallet for online payments offered by Google. Customers can add their credit or debit cards to their Google Pay wallet and use them to pay at web shops or mobile applications through the Payment Gateway. **Benefits:** - Customers simply select a card from their Google Pay wallet — no need to manually enter card details - Secure encryption and decryption raises trust and reduces online fraud - Streamlined checkout experience increases conversion rates ### Tokenization Types Google Pay supports two types of tokenized cards: | Authentication Method | Description | 3D Secure Required | |-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| | **PAN_ONLY** | A standard credit/debit card stored in the Google account. The actual card PAN is tokenized. | Yes — 3DS verification is required for fraud protection | | **CRYPTOGRAM_3DS** | A virtual card with a device-specific account number (DPAN) used in place of the real card. A digital signature from the mobile device protects against fraud. | No — the cryptogram serves as authentication proof | The Payment Gateway supports both `CRYPTOGRAM_3DS` and `PAN_ONLY` authorization methods. **Supported card networks:** Visa, Mastercard *** ## Integration Methods There are two ways to accept Google Pay payments: | Method | Description | Additional Development | |---------------------------------------------|-------------------------------------------------------------------------------------------------|------------------------------------| | [Hosted Payment Page](#hosted-payment-page) | Google Pay is displayed as a payment option on the hosted payment form | None required | | [API Integration](#api-integration) | Merchant integrates directly with the Google Pay API and sends the payment token to the gateway | Yes — Google Pay API + gateway API | ### Activation Prerequisites Before accepting Google Pay payments, you must: 1. Submit a request to your account manager to enable Google Pay on your terminal 2. Follow the [Google Pay API Terms of Use](https://payments.developers.google.com/terms/aup) 3. Complete the Google Pay integration checklist and [publish your integration](https://developers.google.com/pay/api/web/guides/test-and-deploy/publish-your-integration) ( required for API integration only) *** ## Hosted Payment Page If you are already integrated with the [Hosted Payment Page](../../integration-types/hosted-payment-page.md), **no additional development is required**. Simply request activation of the Google Pay payment method from your account manager. Once enabled, a Google Pay option will appear on the hosted payment form alongside other payment methods. ```mermaid sequenceDiagram participant Customer participant Merchant participant Gateway as Payment Gateway participant Google as Google Pay Customer ->> Merchant: Initiates checkout Merchant ->> Gateway: Create HPP session Gateway -->> Merchant: Redirect URL Merchant ->> Customer: Redirect to HPP Customer ->> Gateway: Select Google Pay on HPP Gateway ->> Google: Initiate Google Pay Customer ->> Google: Authenticate & confirm payment Google -->> Gateway: Google Pay token Gateway ->> Gateway: Process payment Gateway -->> Merchant: Payment result (webhook/redirect) ``` *** ## API Integration For merchants who want full control over the checkout experience, you can integrate directly with the Google Pay API and submit the payment token to the gateway. ### Integration Steps 1. Integrate with the [Google Pay API for Web](https://developers.google.com/pay/api/web/overview) 2. Customer clicks the Google Pay button on your checkout page 3. Customer selects a card and confirms payment in the Google Pay dialog 4. Google Pay returns a payment token to your frontend 5. Send the token to your backend server 6. Submit the token to the Payment Gateway API ### Google Pay API Web Integration Before starting, review the following Google resources: - [Google Pay API documentation for web](https://developers.google.com/pay/api/web/overview) - [Google Pay web brand guidelines](https://developers.google.com/pay/api/web/guides/brand-guidelines) - [Google Pay web integration checklist](https://developers.google.com/pay/api/web/guides/test-and-deploy/integration-checklist) ### Google Pay API Configuration When calling the Google Pay API, configure the `PaymentMethod` object with the following parameters: ```json { "allowedPaymentMethods": [ { "type": "CARD", "parameters": { "allowedAuthMethods": [ "PAN_ONLY", "CRYPTOGRAM_3DS" ], "allowedCardNetworks": [ "VISA", "MASTERCARD" ] }, "tokenizationSpecification": { "type": "PAYMENT_GATEWAY", "parameters": { "gateway": "qorepayments", "gatewayMerchantId": "" } } } ], "transactionInfo": { "countryCode": "BA", "currencyCode": "EUR", "totalPriceStatus": "FINAL", "totalPrice": "10.00" }, "merchantInfo": { "merchantId": "", "merchantName": "" }, "callbackIntents": [ "PAYMENT_AUTHORIZATION" ] } ``` #### Configuration Parameters | Parameter | Value | Description | |----------------------------------|----------------------------------|-------------------------------------------| | `allowedAuthMethods` | `["PAN_ONLY", "CRYPTOGRAM_3DS"]` | Both authentication methods are supported | | `allowedCardNetworks` | `["VISA", "MASTERCARD"]` | Supported card networks | | `tokenizationSpecification.type` | `"PAYMENT_GATEWAY"` | Use gateway tokenization | | `tokenizationSpecification.parameters.gateway` | `"qorepayments"` | Gateway identifier for Qore Payments | | `tokenizationSpecification.parameters.gatewayMerchantId` | Your merchant ID | Provided by your account manager | !!! info "Merchant IDs" - `gatewayMerchantId`: Your merchant identifier in the Payment Gateway — provided by your account manager - `merchantId`: Your Google merchant ID — obtained from the [Google Pay Business Console](https://pay.google.com/business/console/) *** ### Payment Processing After the customer completes the Google Pay flow, the Google Pay API returns a `PaymentData` object. To process the payment: 1. Extract the token from `paymentData.paymentMethodData.tokenizationData.token` 2. **Base64-encode** the token string 3. Send it to the Payment Gateway API as the `payment_method.data.google_pay_token` field #### Flow Diagram ```mermaid sequenceDiagram participant Customer participant Frontend as Merchant Frontend participant Backend as Merchant Backend participant Gateway as Payment Gateway participant Acquirer Customer ->> Frontend: Click Google Pay button Frontend ->> Customer: Google Pay dialog Customer ->> Frontend: Confirm payment Frontend ->> Frontend: Receive PaymentData from Google Pay Frontend ->> Backend: Send Google Pay token Backend ->> Backend: Base64-encode token Backend ->> Gateway: POST /api/transactions/authorize
(payment_method.type = "google-pay") Gateway ->> Gateway: Decode & decrypt Google Pay token Gateway ->> Acquirer: Process transaction Acquirer -->> Gateway: Authorization result Gateway -->> Backend: Transaction response Backend -->> Frontend: Payment result Frontend -->> Customer: Payment confirmation ``` #### API Request Example ```http POST /api/transactions/authorize Authorization: Bearer Content-Type: application/json ``` ```json { "reference": "ORDER-GP-001", "terminal_id": "TERM001", "description": "Google Pay payment", "currency": "EUR", "amount": 10000, "transaction_type": "AUTHORIZE", "payment_method": { "type": "google-pay", "data": { "google_pay_token": "" } }, "customer": { "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "phone": "+1234567890", "address": "123 Example Street", "city": "Sampletown", "country": "BA", "postal_code": "12345" }, "browser_info": { "user_agent": "Mozilla/5.0...", "accept_header": "text/html,application/xhtml+xml...", "java_enabled": false, "color_depth": 24, "screen_height": 1080, "screen_width": 1920, "time_zone_offset": -120, "language": "en-US" }, "metadata": {}, "return_url": "https://merchant.example.com/return" } ``` #### Google Pay Payment Data Fields | Field | Type | Required | Description | |----------------------------------------|----------|----------|-----------------------------------------| | `payment_method.type` | `string` | Yes | Must be `"google-pay"` | | `payment_method.data.google_pay_token` | `string` | Yes | Base64-encoded Google Pay payment token | !!! note "Token Encoding" The Google Pay token received from the Google Pay API is a JSON string. You must **Base64-encode** the entire token string before sending it to the Payment Gateway. #### Google Pay Token Structure For reference, the Google Pay token (`paymentData.paymentMethodData.tokenizationData.token`) is a JSON string with the following structure: ```json { "signature": "MEUCIQDk...", "intermediateSigningKey": { "signedKey": "{\"keyValue\":\"MFkwEw...\",\"keyExpiration\":\"1234567890000\"}" }, "protocolVersion": "ECv2", "signedMessage": "{\"encryptedMessage\":\"abc123...\",\"ephemeralPublicKey\":\"BPni...\",\"tag\":\"xyz789...\"}" } ``` This entire JSON string must be **Base64-encoded** before being sent as `payment_method.data.google_pay_token`. *** ## Secure Customer Authentication (SCA/3DS) and PSD2 To comply with **SCA (Strong Customer Authentication)** and **PSD2** regulations, the Payment Gateway will process Google Pay transactions through the 3DS flow when necessary. After decrypting the Google Pay payment token, the payload contains one of two authentication methods: | Authentication Method | 3DS Behavior | |-----------------------|--------------------------------------------------------------------------------------------------------| | **PAN_ONLY** | 3D Secure 2.0 authentication **will be triggered** — the customer may need to complete a 3DS challenge | | **CRYPTOGRAM_3DS** | The payload is already authenticated — **no additional 3DS step** is required | !!! important "Merchant Responsibility" The decryption of the Google Pay token is handled entirely by the Payment Gateway. As a merchant, you do **not** have visibility into whether the underlying credential is PAN_ONLY or CRYPTOGRAM_3DS. Your integration must be prepared to handle the 3DS flow (redirects and callbacks) for any Google Pay transaction. *** ## Related Documentation - [Hosted Payment Page](../../integration-types/hosted-payment-page.md) — HPP integration guide - [API Integration](../../integration-types/api-integration.md) — Direct API integration guide - [Network Token](../network-token/index.md) — For merchants who decrypt Google Pay tokens themselves - [Webhooks](../webhooks/index.md) — Receive payment status notifications --- ## Source: Overview URL: https://docs.qorepayments.com/references/network-token/ # Network Token ## Introduction Network token authorization allows merchants to process payments using pre-decrypted network token data from digital wallets (Apple Pay, Google Pay) or direct Token Service Provider (TSP) integrations. This payment method type is designed for merchants who handle wallet token decryption on their own infrastructure and need to pass the resulting DPAN, cryptogram, and ECI data through the gateway. ## How It Works ```mermaid sequenceDiagram participant Customer participant Merchant participant Gateway as Payment Gateway participant Acquirer Customer->>Merchant: Initiates payment (Apple Pay / Google Pay) Merchant->>Merchant: Decrypts wallet token (DPAN, cryptogram, ECI) Merchant->>Merchant: Encrypts sensitive fields with merchant public key Merchant->>Gateway: POST /api/transactions/authorize
payment_method.type = "network-token" Gateway->>Gateway: Decrypts & validates DPAN (Luhn, brand) Gateway->>Gateway: Builds 3DS authorization from cryptogram + ECI Gateway->>Acquirer: Processes transaction Acquirer-->>Gateway: Authorization result Gateway-->>Merchant: Transaction response Merchant-->>Customer: Payment confirmation ``` ## Key Concepts ### Device Primary Account Number (DPAN) The DPAN is a tokenized card number issued by the card network (Visa, Mastercard, etc.) that substitutes the actual PAN. It is structurally similar to a card number (13–19 digits, passes Luhn validation) but is specific to the device and wallet. ### Cryptogram A payment cryptogram (also called TAVV — Token Authentication Verification Value) is a one-time-use value generated during the wallet authentication process. It proves that the transaction was authenticated by the cardholder's device. - **Required** for CRYPTOGRAM_3DS authentication (Apple Pay, Google Pay with full authentication) - **Not present** for PAN_ONLY tokens (Google Pay PAN_ONLY mode) ### ECI (Electronic Commerce Indicator) The ECI indicates the level of security used during authentication. It is required for CRYPTOGRAM_3DS flows and omitted for PAN_ONLY flows. | ECI Value | Card Network | Authentication Level | |-----------|-------------|---------------------| | `05` | Visa, Amex, Discover, JCB | Fully authenticated | | `02` | Mastercard, Maestro | Fully authenticated | | `06` | Visa, Amex, Discover, JCB | Attempted authentication | | `01` | Mastercard, Maestro | Attempted authentication | | `07` | Visa, Amex, Discover, JCB | No authentication | | `00` | Mastercard, Maestro | No authentication | ### Source The `source` field identifies the origin of the network token. If omitted, it defaults to `network-token`. | Source | Description | |--------|-------------| | `apple-pay` | Token from Apple Pay wallet | | `google-pay` | Token from Google Pay wallet | | `network-token` | Token from a direct TSP integration or other source (default) | ## Supported Card Brands Network tokens are validated against the same card brands supported by your terminal configuration. The gateway determines the brand from the DPAN's BIN (first 6 digits) and validates it against the terminal's accepted card brands. ## Data Fields | Field | Encrypted | Required | Description | |-------|-----------|----------|-------------| | `encrypted_token_number` | Yes | Yes | DPAN from the wallet/TSP | | `encrypted_expiration_month` | Yes | Yes | Token expiration month (1–12) | | `encrypted_expiration_year` | Yes | Yes | Token expiration year (4 digits, e.g. `2025`) | | `encrypted_cryptogram` | Yes | No | Payment cryptogram (absent for PAN_ONLY) | | `eci` | No | No | Electronic Commerce Indicator — required for CRYPTOGRAM_3DS, omitted for PAN_ONLY. Valid values: `00`, `01`, `02`, `05`, `06`, `07` | | `source` | No | No | Token origin (`apple-pay`, `google-pay`, `network-token`). Defaults to `network-token` | !!! info "Encryption" Encrypted fields must be encrypted using your merchant public key, the same key used for card payment encryption. See the [Encryption Example](../cards/encryption-example.md) for details. !!! warning "No CVV" Network token transactions do not require a CVV. The cryptogram serves as the authentication proof instead. ## Related Documentation - [Authorize API Reference](./authorize.md) — Full API specification - [API Integration Guide](../../integration-types/api-integration.md) — Integration walkthrough with request examples - [Encryption Example](../cards/encryption-example.md) — How to encrypt sensitive fields --- ## Source: Authorize URL: https://docs.qorepayments.com/references/network-token/authorize.html # Authorize [OAD(../../assets/openapi/openapi-transaction-authorize.yaml)] --- ## Source: Overview URL: https://docs.qorepayments.com/references/paysafe-redirect/ # Paysafe Redirect *** ## Paysafe Redirect Payment Methods Paysafe Redirect enables seamless access to a wide range of alternative payment methods through a single integration. It allows merchants to offer localized and preferred payment options tailored to specific markets, all while ensuring a secure and streamlined checkout experience. Customers are redirected to a secure environment to complete their payment using their selected method. There are two ways to initiate this redirect: *** ### 🧾 Hosted Payment Page (HPP) In this flow, the **Payments Platform** presents the available payment methods (e.g., ePay, PaySafeCard, Trustly, etc.) for the customer to choose from. Once a method is selected, the customer is **redirected to Paysafe** to complete the transaction securely. #### 🧬 Flow Diagram ```mermaid sequenceDiagram participant Customer participant Merchant Frontend participant Payments Platform participant Paysafe Customer->>Merchant Frontend: Start checkout Merchant Frontend->>Payments Platform: Create HPP session Payments Platform-->>Merchant Frontend: Session link Merchant Frontend->>Customer: Redirect to HPP Customer->>Payments Platform: View HPP and select payment method Payments Platform->>Paysafe: Initiate redirect for selected method Customer->>Paysafe: Complete payment Paysafe->>Payments Platform: Notify payment result Payments Platform->>Merchant Frontend: Notify result ``` *** ### 🔌 Direct API Integration In the direct API flow, the **payment method is already selected** on your platform. The customer is **immediately redirected to Paysafe** to complete the payment without an intermediate selection screen. This method provides more control over the user experience but requires you to manage available payment methods and their codes. #### 🧬 Flow Diagram ```mermaid sequenceDiagram participant Customer participant Merchant Frontend participant Payments Platform participant Paysafe Customer->>Merchant Frontend: Start checkout Merchant Frontend->>Payments Platform: Create redirect payment with selected method Payments Platform->>Paysafe: Initiate payment session Paysafe-->>Payments Platform: Redirect URL Payments Platform-->>Merchant Frontend: Redirect URL Merchant Frontend->>Customer: Redirect to Paysafe Customer->>Paysafe: Complete payment Paysafe->>Payments Platform: Notify payment result Payments Platform->>Merchant Frontend: Notify result ``` *** ### ✅ Supported Payment Methods Below is the list of supported payment methods available via Paysafe Redirect, along with their corresponding codes. These codes are **required when using Direct API Integration**. | Code | Payment Method | | ---- | ----------------------- | | ALI | AliPay | | BLT | Boleto | | CHP | SafetyPay Cash Payment | | GIR | Giropay | | GLU | Trustly | | GFC | Gift Cards | | OBT | Rapid Transfer | | NPY | EPS | | NTL | NETELLER | | ONB | SafetyPay Bank Transfer | | PGF | PagoEfectivo | | PSC | PaySafeCard | | PCH | Paysafecash | | PWY | Przelewy24 | | GCH | Gcash | | GRP | GrabPay | | CNS | Coins.ph | | DNA | Dana | | LNJ | LinkAja EWallet | | MAH | Mach | | SPX | Pix | | KHP | Khipu | | SPI | SPEI | | SFT | Sofort (Klarna) | | EPD | ePay | | WLT | PaySafe Wallet | > ℹ️ **Note:** For **Hosted Payment Page** integration, these codes are managed internally. For **Direct API Integration**, you must provide the correct code when initiating the payment request. *** ### 🔁 Flow Comparison | Feature | Hosted Payment Page | Direct API Integration | | ------------------------------ | ----------------------------------- | ---------------------------- | | Who shows payment methods? | Payments Platform | Merchant platform | | Payment method selection step? | Yes | No (selected beforehand) | | Redirect to Paysafe | After selection | Immediately after initiation | | Flexibility in UX | Moderate (managed selection screen) | High (fully controlled UX) | | Integration effort | Lower | Higher | --- ## Source: Direct API Integration URL: https://docs.qorepayments.com/references/paysafe-redirect/direct-api-integration.html # Direct API Integration ## Paysafe Redirect – API Integration This page provides guidance for integrating **Paysafe Redirect** using the **Direct API approach**. Unlike the Hosted Payment Page (HPP) flow where the customer selects their payment method during the checkout process, the API flow assumes that the payment method has already been selected on your platform. This allows for a more seamless and controlled user experience. With API integration, your platform is responsible for: * Presenting and managing available payment methods * Capturing the customer's selection * Initiating the redirect payment request with the correct **payment method type and data** * Redirecting the customer directly to Paysafe to complete the transaction * Handling asynchronous notifications and updating final payment status *** ### 🔧 Payment Method Type Format When initiating a payment request, the **payment method type** must follow this format: ``` paysafe-redirect-{code} ``` * All lowercase * Use a hyphen (`-`) #### Examples: * `paysafe-redirect-ali` → for **AliPay** * `paysafe-redirect-psc` → for **PaySafeCard** * `paysafe-redirect-obt` → for **Rapid Transfer** Refer to the [Supported Payment Methods](./index.md#supported-payment-methods) for the complete list of available codes. > ⚠️ **Note:** This prefix is mandatory. Incorrect or missing prefixes will result in request failures or improper routing. *** ### 🧾 Required Payment Method Data In addition to the payment method type, each Paysafe Redirect request **must** include the following customer details in the payment method data: * `email` * `first_name` * `last_name` These fields are required by Paysafe for regulatory and transactional reasons and must be collected **before** initiating the redirect. [OAD(docs/assets/openapi/paysafe-authorize-documentation.yaml)] OpenAPI: External YAML reference (to be migrated locally) --- ## Source: Customization Options URL: https://docs.qorepayments.com/references/paysafe-redirect/customization-options.html # Customization Options This section extends the [Direct API Integration](https://docs.finrelay.com/references/paysafe-redirect/direct-api-integration) documentation by describing how to send additional customization parameters to Paysafe through the API. ## Overview You can include optional Paysafe-specific parameters in the `options.paysafe_redirect.params` field of the authorization request. These parameters are forwarded as-is to Paysafe and can affect the redirect behavior or transaction processing. > ℹ️ For up-to-date details on the usage and meaning of individual parameters, refer to the official Paysafe integration documentation provided by your Paysafe account representative. ## Payload Structure The parameters are passed as an array of key-value objects under the `options.paysafe_redirect.params` field: ```json "options": { "paysafe_redirect": { "params": [ { "key": "recipient_description", "value": "Your Value" }, { "key": "psp_id", "value": "Your Value" }, { "key": "country", "value": "DEU" } ] } } ``` ## Supported Keys The following parameter keys are currently supported: * `recipient_description` * `psp_id` * `country` (ISO 3166-1 alpha-3 code, e.g., `DEU` for Germany) * `return_url_target` - Specifies the target window/frame for the return URL after successful payment. Accepts numeric values: * `1` = `_top` (default) - Loads in the full browser window * `2` = `_parent` - Loads in the parent frame * `3` = `_self` - Loads in the same frame * `4` = `_blank` - Opens in a new window/tab > ✅ These parameters are optional. If not provided, default settings from your Paysafe configuration will apply. ## Full Example ```json { "reference": "REF123456789", "terminal_id": "TEST_TERMINAL", "description": "TEST_TERMINAL", "currency": "EUR", "amount": 100, "transaction_type": "PURCHASE", "payment_method": { "type": "paysafe-redirect-ali", "data": { "email": "user@example.com", "first_name": "FirstName", "last_name": "LastName" } }, "metadata": { "order_id": "ORDER-XXXXXX" }, "return_url": "https://google.com", "options": { "paysafe_redirect": { "params": [ { "key": "recipient_description", "value": "Business Name" }, { "key": "psp_id", "value": "PSPID-XXXXXX" }, { "key": "country", "value": "DEU" }, { "key": "return_url_target", "value": "1" } ] } } } ``` > 📘 **Tip:** Ensure any values you include are validated and coordinated with Paysafe support to guarantee expected behavior. --- ## Source: Overview URL: https://docs.qorepayments.com/references/payout/ # Payout *** ## Payout Payment Method A **Payout** transaction allows merchants to transfer funds directly to a beneficiary's bank account or mobile wallet. Unlike standard purchase or authorization transactions that collect funds from a customer, a payout pushes funds to the recipient. Common use cases include disbursements, withdrawals, seller payments, and payroll. Payouts are initiated via the **Direct API** and are processed asynchronously — the transaction may initially return a `PENDING` status, with the final result delivered via [webhook](../webhooks/index.md). !!! warning "Integration Type Limitation" Payouts are only supported via the **Direct API** integration. **Hosted Payment Page (HPP)** and **Embedded Fields (EF)** integration types are **not supported** for payout transactions. *** ### Key Characteristics 1. **Push Payment** * A payout sends funds **to** a beneficiary, rather than collecting funds from a customer. * This is an initial transaction type — it does not require a parent transaction. 2. **Beneficiary-Based** * Instead of payment card details, the request requires a `beneficiary` object specifying the destination type (e.g., `bank_account`) and the relevant account details. * For **INR payouts**, the beneficiary must include an **IFSC code** (Indian Financial System Code) to identify the destination bank branch. 3. **No 3D Secure Authentication** * Payout transactions do not require 3D Secure authentication. * As a result, `browser_info` is **not required** in the request. 4. **Terminal ID Specification** * The `terminal_id` must be specified in the request. The terminal must be configured to support payout transactions (feature: `TRX_TYPE_PAYOUT`). 5. **Asynchronous Processing** * Payouts may be processed asynchronously. The initial API response may return a `PENDING` status. * The final transaction status is delivered via a [Transaction Processed Webhook](../webhooks/transaction-processed-webhook.md). * Always verify the final status using the [Transaction Status API](../../transaction-api/transaction-status-api.md). *** ### Request Requirements | Field | Required | Notes | |-------|----------|-------| | `terminal_id` | Yes | Must support payout transactions | | `reference` | Yes | Unique payout reference per terminal (max 40 chars). Idempotent — duplicate references with the same amount/currency will return the existing transaction. | | `description` | Yes | Human-readable payout description (max 100 chars) | | `currency` | Yes | ISO 4217 currency code (e.g., `INR`, `EUR`, `USD`) | | `amount` | Yes | Amount in minor units (e.g., `10000` = 100.00 INR) | | `beneficiary.type` | Yes | Beneficiary type: `bank_account` or `mobile_money` | | `beneficiary.data` | Yes | Beneficiary details (see below) | | `customer` | No | Customer details | | `metadata` | No | Arbitrary key-value metadata | | `customer_ip` | No | Customer IP address (defaults to request IP) | | `return_url` | No | URL to redirect after payout completes (approved, declined, canceled, or error) | | `options` | No | Provider-specific options | *** ### Beneficiary Data — Bank Account (INR) When processing INR payouts to Indian bank accounts, the `beneficiary` object must include the following fields: | Field | Required | Description | |-------|----------|-------------| | `account_number` | Yes | Beneficiary bank account number | | `account_name` | Yes | Name of the account holder | | `ifsc` | Yes | IFSC code (Indian Financial System Code) — identifies the bank branch | | `bank_code` | No | Bank code | | `bank_name` | No | Name of the bank | | `bank_branch` | No | Branch name | | `bank_address` | No | Bank address | | `bank_city` | No | Bank city | | `bank_state` | No | Bank state | | `mobile` | No | Beneficiary mobile number | *** ### Processing Flow ```mermaid sequenceDiagram participant Merchant participant Gateway participant PSP Merchant->>Gateway: POST /api/transactions/payout Gateway->>Gateway: Validate request & terminal Gateway->>Gateway: Resolve payment provider Gateway->>PSP: Submit payout PSP-->>Gateway: Processing result (PENDING/APPROVED/DECLINED) Gateway-->>Merchant: Transaction response Note over PSP,Gateway: Asynchronous settlement PSP->>Gateway: Callback with final status Gateway->>Merchant: Webhook notification (APPROVED/DECLINED) ``` *** ### Supported Transactions | Transaction Type | Supported | Description | | ---------------- | --------- | ----------- | | PAYOUT | Yes | Transfer funds to a beneficiary bank account or wallet | | REFUND | No | Not applicable for payouts | | CAPTURE | No | Not applicable (single-step flow) | | VOID | No | Not supported | *** ### Supported Currency | Currency | Code | Description | | ------------- | ---- | ----------- | | Indian Rupee | INR | Supported for bank account payouts with IFSC | > Additional currencies may be supported depending on the configured payment provider. Contact support for details. *** ### Transaction Statuses | Status | Description | Final State | | -------- | ----------- | ----------- | | PENDING | Payout submitted and awaiting provider confirmation | No | | APPROVED | Payout successfully processed and funds transferred | Yes | | DECLINED | Payout rejected by the provider or bank | Yes | *** ### Important Notes * The `reference` field is idempotent per terminal. Sending the same reference with the same amount and currency returns the existing transaction. Different amount/currency values will result in a validation error. * For **INR payouts**, the `ifsc` field is mandatory in the beneficiary data. Ensure the IFSC code is valid and corresponds to the correct bank branch. * Account numbers are masked in API responses (only the last 4 digits are visible) for security purposes. * Always subscribe to [webhooks](../webhooks/index.md) to receive the final payout status, as payouts are typically processed asynchronously. --- ## Source: Direct API Integration URL: https://docs.qorepayments.com/references/payout/direct-api-integration.html # Direct API Integration ## Payout – API Integration This page provides guidance for integrating **Payouts** using the **Direct API approach**. Payouts allow you to transfer funds directly to a beneficiary's bank account. Your platform is responsible for: * Collecting beneficiary bank account details (account number, account name, IFSC code for INR) * Initiating the payout request with the correct beneficiary type and data * Handling asynchronous webhook callbacks for final transaction status * Verifying the transaction status via the [Transaction Status API](../../transaction-api/transaction-status-api.md) *** ### Beneficiary Type When initiating a payout request, specify the beneficiary type in the `beneficiary.type` field: | Type | Description | |------|-------------| | `bank_account` | Bank account transfer (required for INR payouts) | *** ### Required Beneficiary Data (INR Bank Account) Each INR payout **must** include the following fields in `beneficiary.data`: | Field | Type | Required | Description | |-------|------|----------|-------------| | `account_number` | string | Yes | Beneficiary bank account number | | `account_name` | string | Yes | Full name of the account holder | | `ifsc` | string | Yes | IFSC code — Indian Financial System Code identifying the bank and branch (e.g., `SBIN0001234`) | | `bank_code` | string | No | Bank code | | `bank_name` | string | No | Name of the bank | | `bank_branch` | string | No | Branch name | | `bank_address` | string | No | Bank address | | `bank_city` | string | No | City of the bank branch | | `bank_state` | string | No | State of the bank branch | | `mobile` | string | No | Beneficiary mobile number | *** ### Example Request ```json { "terminal_id": "TERM001", "reference": "PAYOUT-INR-20260319-001", "description": "INR payout to beneficiary bank account", "currency": "INR", "amount": 10000, "beneficiary": { "type": "bank_account", "data": { "account_number": "000133524852", "account_name": "Test User", "ifsc": "SBIN0001234" } }, "customer": { "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com" }, "customer_ip": "192.168.1.1" } ``` > The `amount` is in **minor units**. For INR, `10000` represents **100.00 INR**. *** ### Example Response (Pending) ```json { "result": { "id": "PyTxAbCdEfGhIjKlMnOp", "merchant_id": "0000000000000fpg-dev", "order_id": "ORD_PAYOUT_20260319_001", "terminal_id": "TERM001", "reference": "PAYOUT-INR-20260319-001", "description": "INR payout to beneficiary bank account", "currency": "INR", "amount": 10000, "customer": { "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com" }, "processing_result": { "payment_provider_id": "PP_T365", "payment_provider_account_id": "PPACCT_INR001" }, "approved": false, "pending": true, "channel": "ecommerce", "transaction_type": "PAYOUT", "status": "PENDING", "payment_method": { "method": "****4852", "type": "payout_upi", "masked": "****4852" }, "normalized_amount": 10000, "errors": [] }, "action": null, "redirect": null, "form_submit": null } ``` *** ### Example Response (Approved) ```json { "result": { "id": "PyTxAbCdEfGhIjKlMnOp", "merchant_id": "0000000000000fpg-dev", "order_id": "ORD_PAYOUT_20260319_001", "terminal_id": "TERM001", "reference": "PAYOUT-INR-20260319-001", "description": "INR payout to beneficiary bank account", "currency": "INR", "amount": 10000, "customer": { "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com" }, "processing_result": { "payment_provider_id": "PP_T365", "payment_provider_account_id": "PPACCT_INR001", "approval_code": "AUTH789", "reference_number": "REF_PAYOUT_001" }, "approved": true, "pending": false, "channel": "ecommerce", "transaction_type": "PAYOUT", "status": "APPROVED", "payment_method": { "method": "****4852", "type": "payout_upi", "masked": "****4852" }, "normalized_amount": 10000, "errors": [] }, "action": null, "redirect": null, "form_submit": null } ``` *** [OAD(docs/assets/openapi/openapi-payout.yaml)] --- ## Source: Overview URL: https://docs.qorepayments.com/references/neogated/ # Neogated (BLIK) *** ## BLIK Payment Method BLIK is a Polish instant payment system that enables customers to make payments using their mobile banking app. Through Neogated integration, merchants can offer BLIK as a payment option to Polish customers. Customers are redirected to complete their payment via their bank's mobile app. There are multiple ways to initiate this redirect: *** ### 🧾 Hosted Payment Page (HPP) In this flow, the **Payments Platform** presents BLIK as an available payment method. Once selected, the customer is **redirected to Neogated** to complete the transaction by approving it in their mobile banking app. #### 🧬 Flow Diagram ```mermaid sequenceDiagram participant Customer participant Merchant Frontend participant Payments Platform participant Neogated participant Bank App Customer->>Merchant Frontend: Start checkout Merchant Frontend->>Payments Platform: Create HPP session Payments Platform-->>Merchant Frontend: Session link Merchant Frontend->>Customer: Redirect to HPP Customer->>Payments Platform: View HPP and select BLIK Payments Platform->>Neogated: Initiate BLIK payment Neogated-->>Payments Platform: Redirect URL Customer->>Neogated: Access checkout page Neogated->>Bank App: Push notification Customer->>Bank App: Approve payment Bank App->>Neogated: Confirmation Neogated->>Payments Platform: Callback (COMPLETED/FAILED) Payments Platform->>Merchant Frontend: Notify result ``` *** ### 🖼️ Embedded Fields (EF) In this flow, the merchant embeds a BLIK payment button directly into their checkout page using a JavaScript integration. The Payments Platform loads the Neogated script which renders the BLIK payment option. Once clicked, the customer is **redirected to Neogated** to complete the payment. #### 🧬 Flow Diagram ```mermaid sequenceDiagram participant Customer participant Merchant Frontend participant Payments Platform participant Neogated participant Bank App Customer->>Merchant Frontend: Start checkout Merchant Frontend->>Payments Platform: Load Embedded Fields session Payments Platform-->>Merchant Frontend: Session with neogated.js Merchant Frontend->>Customer: Show BLIK payment button Customer->>Merchant Frontend: Click BLIK Merchant Frontend->>Payments Platform: Submit payment (neogated-blik) Payments Platform->>Neogated: POST /api/v1/deposit Neogated-->>Payments Platform: Redirect URL Payments Platform-->>Merchant Frontend: Redirect URL Merchant Frontend->>Customer: Redirect to Neogated Customer->>Neogated: Access BLIK checkout Neogated->>Bank App: Push notification Customer->>Bank App: Approve in mobile app Bank App->>Neogated: Confirmation Neogated->>Payments Platform: Callback (status) Payments Platform->>Merchant Frontend: Notify result ``` *** ### 🔌 Direct API Integration In the direct API flow, **BLIK is already selected** on your platform. The customer is **immediately redirected to Neogated** to complete the payment without an intermediate selection screen. This method provides more control over the user experience. #### 🧬 Flow Diagram ```mermaid sequenceDiagram participant Customer participant Merchant Frontend participant Payments Platform participant Neogated participant Bank App Customer->>Merchant Frontend: Start checkout (BLIK selected) Merchant Frontend->>Payments Platform: Create payment with neogated-blik Payments Platform->>Neogated: POST /api/v1/deposit Neogated-->>Payments Platform: Redirect URL + Transaction ID Payments Platform-->>Merchant Frontend: Redirect URL Merchant Frontend->>Customer: Redirect to Neogated Customer->>Neogated: Access BLIK checkout Neogated->>Bank App: Push notification Customer->>Bank App: Approve in mobile app Bank App->>Neogated: Confirmation Neogated->>Payments Platform: Callback (status) Payments Platform->>Merchant Frontend: Notify result ``` *** ### ✅ Supported Transactions | Transaction Type | Supported | Description | | ---------------- | --------- | ----------- | | PURCHASE | Yes | One-time purchase with redirect to BLIK authentication | | REFUND | No | Not supported | | CAPTURE | No | Not applicable (purchase-only flow) | | VOID | No | Not supported | *** ### 💱 Supported Currency | Currency | Code | Description | | -------- | ---- | ----------- | | Polish Zloty | PLN | Only PLN is supported for BLIK payments | > ⚠️ **Note:** BLIK only supports PLN currency. Requests with other currencies will be rejected. *** ### 📊 Transaction Statuses Neogated sends callback notifications with the following statuses: | Callback Status | Internal Status | Description | Final State | | --------------- | --------------- | ----------- | ----------- | | COMPLETED | APPROVED | Payment successfully completed | Yes | | FAILED | DECLINED | Payment failed | Yes | | REJECTED | DECLINED | Payment rejected by bank or customer | Yes | | CANCELED | DECLINED | Payment canceled by customer | Yes | | PROCESSING | PENDING | Payment is being processed | No | *** ### 🔄 Redirect Handling After the customer completes (or abandons) the BLIK payment on Neogated's checkout page, they are redirected back to the Payments Platform. The platform then checks the transaction status and redirects the customer to the appropriate merchant URL: | Transaction Status | Redirect Destination | | ------------------ | -------------------- | | APPROVED | Merchant's `return_url` | | DECLINED / CANCELED | Merchant's `return_url` | | PENDING | Waits for callback resolution, then redirects to `return_url` | > ℹ️ **Note:** If the transaction is still `PENDING` when the customer is redirected back, the platform will wait briefly for the callback to arrive before redirecting the customer to the `return_url`. The merchant should verify the final transaction status via the [Transaction Status API](../../transaction-api/index.md) regardless of the redirect outcome. *** ### 🔁 Flow Comparison | Feature | Hosted Payment Page | Embedded Fields | Direct API Integration | | ------------------------------ | ----------------------------------- | ------------------------------------ | ---------------------------- | | Who shows payment methods? | Payments Platform | Payments Platform (via JS) | Merchant platform | | Payment method selection step? | Yes | Yes (embedded in merchant page) | No (selected beforehand) | | Redirect to Neogated | After selection | After selection | Immediately after initiation | | Flexibility in UX | Moderate (managed selection screen) | High (embedded in merchant checkout) | High (fully controlled UX) | | Integration effort | Lower | Moderate | Higher | --- ## Source: Direct API Integration URL: https://docs.qorepayments.com/references/neogated/direct-api-integration.html # Direct API Integration ## Neogated BLIK – API Integration This page provides guidance for integrating **Neogated BLIK** using the **Direct API approach**. Unlike the Hosted Payment Page (HPP) flow where the customer selects their payment method during the checkout process, the API flow assumes that BLIK has already been selected on your platform. This allows for a more seamless and controlled user experience. With API integration, your platform is responsible for: * Presenting BLIK as a payment option * Collecting required customer data (email, first name, last name) * Initiating the payment request with the correct **payment method type and data** * Redirecting the customer to Neogated to complete BLIK authentication * Handling asynchronous callbacks and updating final payment status *** ### 🔧 Payment Method Type Format When initiating a payment request, use the following **payment method type**: ``` neogated-blik ``` > ⚠️ **Note:** This exact type is required. Incorrect payment method types will result in request failures. *** ### 🧾 Required Payment Method Data Each Neogated BLIK request **must** include the following customer details in the payment method data: * `email` – Customer email address * `first_name` – Customer first name * `last_name` – Customer last name These fields are required by Neogated for regulatory and transactional reasons and must be collected **before** initiating the redirect. *** ### 📝 Optional Payment Method Data The following fields are optional but recommended for better transaction processing: | Field | Type | Default | Description | | -------------- | ------ | ------- | ----------- | | `phone` | string | – | Customer phone number with country code | | `country` | string | PL | ISO 3166-1 alpha-2 country code | | `address` | string | – | Customer street address | | `city` | string | – | Customer city | | `postal_code` | string | – | Customer postal code | | `state` | string | – | Customer state/region | | `date_of_birth`| string | – | Customer date of birth (YYYY-MM-DD) | | `language` | string | – | Preferred language code (e.g., pl, en) | | `blik_code` | string | – | 6-digit BLIK code if pre-collected | > ℹ️ **Note:** If `blik_code` is provided, the customer may skip entering it on the Neogated checkout page. [OAD(docs/assets/openapi/neogated-blik-authorize.yaml)] --- ## Source: Overview URL: https://docs.qorepayments.com/references/transaction-management/ # Transaction Manage API Documentation Welcome to the **Transaction Manage API**, part of the Finrelay Payments Platform. This API enables merchants and integrators to **manage existing transactions** by initiating follow-up actions such as **captures**, **refunds**, and **voids** based on original transaction references. This is a **general transaction management API** and may support **additional management operations** in the future, allowing for expanded flexibility as integration needs evolve. #### ✅ Use Cases The API supports a variety of **transaction management operations**, including: * **Partial or full capture** of previously authorized transactions * **Partial or full refunds** of completed purchases or captured transactions * **Void operations** on authorized or pending transactions > ⚠️ **Note**: Availability of specific transaction types (e.g., capture, void, refund) depends on: > > * The underlying integration with the payment service provider (PSP) > * The type of credentials issued by the third-party PSP to your merchant account #### 🔐 Authentication All requests require a valid **JWT Bearer Token**. The token must be included in the `Authorization` header of the request: ``` Authorization: Bearer ``` #### 🌐 Base URLs | Environment | URL | | ----------- | ------------------------------------- | | Dev | `https://dev-api.qorepayments.com` | | Production | `https://api.qorepayments.com` | #### 🧾 Endpoint Overview **POST** `/api/transactions/manage`\ Initiates a transaction management operation using a reference to a previous transaction. * **Request Body**: `ManageTransactionRequestDto` * **Response**: `ExtendedTransactionDto` (includes updated transaction details) * **Security**: Requires Bearer JWT --- ## Source: Manage Transaction API URL: https://docs.qorepayments.com/references/transaction-management/manage-transaction-api.html # Manage transaction [OAD(../../assets/openapi/openapi-manage-transaction.yaml)] --- ## Source: Overview URL: https://docs.qorepayments.com/references/webhooks/ # Webhooks ### Introduction Webhooks allow your application to receive real-time notifications from Finrelay whenever specific events occur. Instead of polling APIs, you can set up a webhook endpoint to automatically receive updates when important activities happen. Webhooks enable event-driven workflows, such as transaction status updates or system notifications, without the need for manual polling. ### How Webhooks Work When an event occurs (for example, a transaction is processed), Finrelay sends an HTTP `POST` request to your registered webhook URL. The request contains a JSON payload describing the event. Each webhook request is **signed using JWT (JSON Web Token)** for security and integrity verification. You must set up an endpoint on your server to receive, validate, and process these webhook notifications. ### Configuring Webhooks Currently, webhook configuration is not self-service.\ To configure webhook delivery for your account: 1. Contact the Finrelay Support Team. 2. Provide your webhook endpoint URL and any environment-specific details. 3. Our team will configure your webhook subscriptions on your behalf. Support contact information is available through your client portal or support@finrelay.com. ### Security and Signature Verification Every webhook request from Finrelay includes a **JWT token** in the `Authorization` header.\ The JWT is **signed** and must be verified to ensure the authenticity of the webhook. Signature details: * The JWT payload contains a `data` claim: ```json { "SHA512": "" } ``` * The `` is a SHA-512 hash digest of the webhook request body. * The webhook request body must be hashed using SHA-512, and the resulting digest must match the one in the JWT payload. * The JWT must also be cryptographically verified using the correct public key. **Important:** * Depending on the event type, different private keys are used for signing. * For **transaction-related events**, the merchant's private key is used. * Always refer to the event-specific documentation for details about which key applies. ### Delivery and Retry Policy * If your webhook endpoint responds with a `2xx` HTTP status code, the delivery is considered successful. * If your endpoint returns a `4xx`/`5xx` error or times out, Finrelay retries the webhook with exponential backoff. * Retry attempts continue for up to **24 hours**. * After the retry window expires without success, the event is marked as undeliverable. Ensure that your webhook endpoint: * Is highly available. * Quickly responds to POST requests (recommended within 2 seconds). ### Best Practices * Always verify the JWT signature and the SHA-512 digest before processing the event. * Log webhook payloads and headers for audit and troubleshooting purposes. * Implement idempotency to safely handle duplicate webhook deliveries. * Respond with an HTTP 2xx status as quickly as possible. * Secure your webhook endpoints with HTTPS. ### JWT Signature Verification – Flow Description When your server receives a webhook request from Finrelay, you must: 1. **Extract** the `Authorization` header (which contains the JWT). 2. Split authorization header on two parts (Bearer and token) 3. **Decode** the JWT header and payload (without verifying yet). 4. **Retrieve the "data" claim** from the JWT payload, which contains the SHA-512 digest of the request body. 5. **Hash** the received request body yourself using SHA-512. 6. **Compare** your calculated hash to the `SHA512` value inside the `data` claim. 7. **Verify** the JWT's signature using the correct public key. * For transaction events: use the **merchant’s public key**. * (Other events may use different keys; see event-specific documentation.) 8. If the digest matches and the signature is valid → **accept** the webhook. If any of these steps fail → **reject** the webhook (return `401 Unauthorized`). ```mermaid flowchart TD A[Receive Webhook Request] --> B[Extract Authorization Header JWT] B --> C[Decode JWT Header and Payload] C --> D[Extract 'data' Claim SHA512 Digest] C --> E[Extract Public Key based on Event Type] A --> F[Hash the Request Body using SHA-512] D --> G{Compare Digest Values} F --> G G -- Match --> H{Verify JWT Signature} G -- Mismatch --> X[Reject Webhook 401 Unauthorized] H -- Valid --> Y[Accept Webhook and Process Event] H -- Invalid --> X ``` ## Summary * The digest comparison ensures **payload integrity** (body wasn't tampered). * The JWT signature ensures **origin authenticity** (Finrelay sent it, not someone else). * Both must pass. --- ## Source: Transaction Processed Webhook URL: https://docs.qorepayments.com/references/webhooks/transaction-processed-webhook.html # Transaction Processed Webhook ### Overview The **Transaction Processed Webhook** is triggered when a transaction reaches a terminal state, such as **approved**, **declined**, **canceled**, or **expired**.\ This webhook enables you to synchronize your system with the transaction outcome without polling. ### When It Is Triggered Finrelay will send the Transaction Processed Webhook when: * A transaction is **approved** or **declined** by the PSP. * A transaction is **voided** or **expired**. * A transaction **fails** due to an error. Only terminal transaction states trigger this webhook. ### Supported Events The **Transaction Processed Webhook** is triggered for the following events: | Event Value | Description | | --------------------------------------------------------- | ------------------------------------- | | `transaction-svc:transaction:authorized` | Transaction authorized (initial auth) | | `transaction-svc:transaction:authorize:authorized` | Authorization transaction successful | | `transaction-svc:transaction:purchase:approved` | Purchase transaction approved | | `transaction-svc:transaction:authorize:declined` | Authorization declined | | `transaction-svc:transaction:purchase:declined` | Purchase declined | | `transaction-svc:transaction:authorize:pending` | Authorization pending | | `transaction-svc:transaction:purchase:pending` | Purchase pending | | `transaction-svc:transaction:pending` | General transaction pending | | `transaction-svc:transaction:declined` | General transaction declined | | `transaction-svc:transaction:management:declined` | Management operation declined | | `transaction-svc:transaction:management:approved` | Management operation approved | | `transaction-svc:transaction:management:refund:declined` | Refund operation declined | | `transaction-svc:transaction:management:refund:approved` | Refund operation approved | | `transaction-svc:transaction:management:void:declined` | Void operation declined | | `transaction-svc:transaction:management:void:approved` | Void operation approved | | `transaction-svc:transaction:management:capture:declined` | Capture operation declined | | `transaction-svc:transaction:management:capture:approved` | Capture operation approved | ### Request Details | Detail | Value | | -------------------- | ------------------------------------- | | HTTP Method | `POST` | | Content-Type | `application/json` | | Authorization Header | `Bearer ` | | Retries | Yes, with exponential backoff | | Retry Window | 24 hours | | Signature Validation | JWT signed using Merchant Private Key | ### Headers | Header | Description | | --------------- | ------------------------------------------------------------------- | | `Authorization` | Bearer token containing a signed JWT with SHA-512 digest validation | | `Content-Type` | `application/json` | Example: ```http POST /your/webhook/endpoint HTTP/1.1 Host: your-server.com Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9... Content-Type: application/json ``` ### Payload Schema Here’s the JSON structure you can expect: ```json { "event": "transaction-svc:transaction:authorized", "payload": { "id": "KlLQpgjqCmsmogHfNjuS", "merchant_id": "eyCnTugDzIMADoQLTUJF", "terminal_id": "eyCnTugDzIMADoQLTUJF", "order_id": "auDCeVomqaFvBLxStpuO", "payment_link_id": null, "reference": "oONQfOdgIF", "description": "kzCPRVsEbY", "currency": "EUR", "amount": 100, "transaction_type": "AUTHORIZE", "processing_code": "0000", "response_message": "approved or completed successfully", "status": "APPROVED", "metadata": { "response_code": "TRX_STATUS_APPROVED", "simulated_flow": "VERSIONING_FINGERPRINT_N_FRICTIONLESS", "additionalProp1": "string", "additionalProp2": "string" }, "created_at": "2025-04-28T12:42:26.976811Z", "updated_at": "2025-04-28T12:42:27.144339Z", "approval_code": "bfzLph", "reference_number": "", "parent_id": null, "systan": null, "integration_type": "UNRECOGNIZED", "transaction_initiated_by": "CUSTOMER", "authentication_level": null, "payment_provider_response_code": "approved", "payment_provider_response_message": "approved", "normalized_amount": 100, "customer_id": null, "outgoing_amount": null, "outgoing_currency": null, "payment_method_method": "411111-******-1111", "payment_method_hash": null, "payment_method_type": "card", "payment_method_brand": "VISA", "payment_method_masked": "411111-******-1111", "payment_method_token": null, "customer_first_name": "Carey", "customer_last_name": "Kuhn", "customer_address": "877 Don Centers", "customer_city": "Port Dalton", "customer_country": "Singapore", "customer_postal_code": "43000", "customer_email": "barrett.rolfson@hotmail.com", "customer_phone_number": "+1 985-502-6937 x8449", "authentication_status": "AUTHENTICATED", "payment_method_expiry": "2027-12-31" } } ``` #### Field Descriptions | Field | Type | Description | | -------------------------- | ---------------------------- | ------------------------------------------------------- | | `event` | `string` | One of transaction processed events | | `payload.id` | `string` | Unique transaction identifier | | `payload.merchant_id` | `string` | Merchant identifier | | `payload.terminal_id` | `string` | Terminal identifier | | `payload.order_id` | `string` | Order ID associated with transaction | | `payload.payment_link_id` | `string` | Payment Link ID (nullable) | | `payload.reference` | `string` | Merchant-provided reference | | `payload.description` | `string` | Transaction description | | `payload.currency` | `string` | Currency code (ISO 4217) | | `payload.amount` | `number` (integer) | Amount in minor units (e.g., cents) | | `payload.transaction_type` | `string` | Transaction type (e.g., "purchase", "refund") | | `payload.processing_code` | `string` | Processor-specific code | | `payload.response_message` | `string` | Human-readable response | | `payload.status` | `string` | Final transaction status (`approved`, `declined`, etc.) | | `payload.metadata` | `object` | Custom merchant metadata (optional) | | `payload.created_at` | `string` (ISO-8601 datetime) | When the transaction was created | | `payload.updated_at` | `string` (ISO-8601 datetime) | When the transaction was last updated | | `payload.payment_method_expiry` | `string` (ISO-8601 date) or `null` | Payment method expiry date (last day of expiry month, e.g., `2027-12-31`) | ### JSON Schema Here is the JSON Schema for validating the Transaction Processed Webhook payload (download and view): [transaction-processed-payload.json](../../assets/openapi/transaction-processed-payload.json) ### Important Notes * Always verify the JWT signature using the appropriate merchant public key. * Always validate that the SHA-512 digest of the request body matches the value inside the JWT. * Finrelay retries failed webhook deliveries with exponential backoff for up to 24 hours. * Duplicate webhook deliveries are possible: ensure your webhook handlers are idempotent. --- ## Source: Overview URL: https://docs.qorepayments.com/transaction-api/ # Transaction API --- ## Source: Transaction Status API URL: https://docs.qorepayments.com/transaction-api/transaction-status-api.html # Transaction Status API [OAD(../assets/openapi/transaction-status-api.yaml)] --- ## Source: Supported Transaction Statuses URL: https://docs.qorepayments.com/transaction-api/supported-transaction-statuses.html # TransactionStatus The `TransactionStatus` enum represents the lifecycle states of a transaction within the system. These statuses reflect different phases and outcomes, including both transitional and terminal states. ## Enum Values | Status | Description | Terminal/Final Status for transaction | | ----------------- | --------------------------------------------------------------------------- | ------------------------------------- | | `CREATED` | The transaction has been created but not yet processed. | No | | `PENDING` | The transaction is awaiting further action (e.g., authorization, 3DS, etc). | No | | `APPROVED` | The transaction has been successfully authorized or completed. | ✅ Yes | | `DECLINED` | The transaction was rejected by the payment provider or bank. | ✅ Yes | | `CANCELED` | The transaction was canceled either by the user or system. | ✅ Yes | | `INVALID` | The transaction is marked as invalid due to inconsistencies or errors. | ✅ Yes | | `SESSION_EXPIRED` | The transaction session expired before it could complete. | ✅ Yes | *** ## Transaction Lifecycle Diagram (High-Level) *** ```mermaid stateDiagram-v2 [*] --> CREATED CREATED --> PENDING : user initiated / submitted PENDING --> APPROVED : success PENDING --> DECLINED : failure PENDING --> CANCELED : user/system cancel PENDING --> SESSION_EXPIRED : timeout CREATED --> CANCELED : user cancel CREATED --> INVALID : validation failure APPROVED --> [*] DECLINED --> [*] CANCELED --> [*] INVALID --> [*] SESSION_EXPIRED --> [*] ``` ## Transaction Transition Rules (Detailed) ```mermaid graph TD CREATED["CREATED"] PENDING["PENDING"] APPROVED["APPROVED"] DECLINED["DECLINED"] CANCELED["CANCELED"] INVALID["INVALID"] SESSION_EXPIRED["SESSION_EXPIRED"] CREATED -->|submit| PENDING CREATED -->|cancel| CANCELED CREATED -->|validation error| INVALID PENDING -->|authorized| APPROVED PENDING -->|rejected| DECLINED PENDING -->|cancel| CANCELED PENDING -->|timeout| SESSION_EXPIRED ``` *** ## Notes * **Terminal Statuses**: Once a transaction reaches a terminal status, it is considered finalized and immutable. * **Non-Terminal Statuses**: Allow additional actions or progression through the transaction lifecycle. --- ## Source: API Integration URL: https://docs.qorepayments.com/integration-types/api-integration.html # API Integration Direct API integration allows you to process payments programmatically using RESTful endpoints. This approach gives you full control over the payment flow and UI/UX. *** ## 🔐 Authentication All API requests require a valid **OAuth 2.0 access token**: ### HTTP Headers ```http Authorization: Bearer Content-Type: application/json ``` ### Obtaining an Access Token Use the Client Credentials flow to obtain an access token. See [Obtain Access Token](../getting-started/authentication/obtain-access-token.md) for detailed instructions. Your `client_id` and `client_secret` are available in your merchant dashboard. *** ## 💳 Processing Card Payments ### Endpoint ```http POST /api/transactions/authorize ``` ### Data Encryption **All sensitive card data must be encrypted** using your merchant public key before sending to the API. Fields requiring encryption: - Card number - CVV - Expiration month - Expiration year See the [Encryption Example](../references/cards/encryption-example.md) for detailed encryption instructions. ### Basic Request Example ```json { "reference": "ORDER-123456", "terminal_id": "TERM001", "description": "Order payment", "currency": "EUR", "amount": 10000, "transaction_type": "AUTHORIZE", "payment_method": { "type": "card", "data": { "encrypted_card_number": "base64_encrypted_value", "encrypted_cvv": "base64_encrypted_value", "encrypted_expiration_month": "base64_encrypted_value", "encrypted_expiration_year": "base64_encrypted_value" } }, "customer": { "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "phone": "+1234567890", "address": "123 Example Street", "city": "Sampletown", "country": "BA", "postal_code": "12345" }, "browser_info": { "user_agent": "Mozilla/5.0...", "accept_header": "text/html,application/xhtml+xml...", "java_enabled": false, "color_depth": 24, "screen_height": 1080, "screen_width": 1920, "time_zone_offset": -120, "language": "en-US" }, "metadata": {}, "return_url": "https://merchant.example.com/return" } ``` *** ## Network Token Payments (Apple Pay / Google Pay) For merchants who handle wallet token decryption on their own infrastructure, the `network-token` payment method type allows you to send pre-decrypted network token data (DPAN, cryptogram, ECI) through the gateway. This is useful for: - **Apple Pay** integrations where you decrypt the PKPaymentToken server-side - **Google Pay** integrations with CRYPTOGRAM_3DS or PAN_ONLY authentication - **Direct TSP** (Token Service Provider) integrations ### Data Encryption The following fields must be encrypted using your merchant public key (same key used for card encryption): - Token number (DPAN) - Expiration month - Expiration year - Cryptogram (when present) The `eci` and `source` fields are sent as **plain text**. See the [Encryption Example](../references/cards/encryption-example.md) for encryption instructions. ### Request Example (Apple Pay) ```json { "reference": "ORDER-NT-001", "terminal_id": "TERM001", "description": "Apple Pay payment", "currency": "EUR", "amount": 10000, "transaction_type": "AUTHORIZE", "payment_method": { "type": "network-token", "data": { "encrypted_token_number": "base64_encrypted_dpan", "encrypted_expiration_month": "base64_encrypted_value", "encrypted_expiration_year": "base64_encrypted_value", "encrypted_cryptogram": "base64_encrypted_cryptogram", "eci": "05", "source": "apple-pay" } }, "customer": { "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "phone": "+1234567890", "address": "123 Example Street", "city": "Sampletown", "country": "BA", "postal_code": "12345" }, "browser_info": { "user_agent": "Mozilla/5.0...", "accept_header": "text/html,application/xhtml+xml...", "java_enabled": false, "color_depth": 24, "screen_height": 1080, "screen_width": 1920, "time_zone_offset": -120, "language": "en-US" }, "metadata": {}, "return_url": "https://merchant.example.com/return" } ``` ### Request Example (Google Pay — PAN_ONLY) When using Google Pay with PAN_ONLY authentication, the cryptogram and ECI are not present: ```json { "reference": "ORDER-NT-002", "terminal_id": "TERM001", "description": "Google Pay payment", "currency": "EUR", "amount": 5000, "transaction_type": "AUTHORIZE", "payment_method": { "type": "network-token", "data": { "encrypted_token_number": "base64_encrypted_dpan", "encrypted_expiration_month": "base64_encrypted_value", "encrypted_expiration_year": "base64_encrypted_value", "source": "google-pay" } }, "customer": { ... }, "browser_info": { ... }, "metadata": {}, "return_url": "https://merchant.example.com/return" } ``` ### Network Token Payment Data Fields | Field | Type | Required | Encrypted | Description | |-------|------|----------|-----------|-------------| | `encrypted_token_number` | `string` | Yes | Yes | DPAN from the wallet or TSP | | `encrypted_expiration_month` | `string` | Yes | Yes | Token expiration month (1–12) | | `encrypted_expiration_year` | `string` | Yes | Yes | Token expiration year (4 digits, e.g. `2025`) | | `encrypted_cryptogram` | `string` | No | Yes | Payment cryptogram (absent for PAN_ONLY) | | `eci` | `string` | No | No | Electronic Commerce Indicator — required for CRYPTOGRAM_3DS, omitted for PAN_ONLY. Valid values: `00`, `01`, `02`, `05`, `06`, `07` | | `source` | `string` | No | No | Token origin: `apple-pay`, `google-pay`, or `network-token`. Defaults to `network-token` if omitted | For detailed information about network tokens, see the [Network Token Reference](../references/network-token/index.md). *** ## 💾 Saving Cards for Future Payments The API supports tokenization, allowing you to save payment cards for future transactions. This is useful for: - Subscription or recurring payments - One-click checkout experiences - Repeat customer transactions ### Requirements for Card Tokenization Before using tokenization, ensure: 1. **Tokenization is enabled** at the merchant level (contact your payment platform administrator) 2. **Customer exists** (if using `customer.id`): The customer must already exist in the Payment Platform 3. **PCI compliance**: Ensure your integration complies with PCI-DSS requirements ### Saving a Card During Payment To save a card for future use, include the `tokenization` object in your transaction request: **Request Example:** ```json { "reference": "ORDER-123456", "terminal_id": "TERM001", "description": "Order payment", "currency": "EUR", "amount": 10000, "transaction_type": "AUTHORIZE", "payment_method": { "type": "card", "data": { "encrypted_card_number": "base64_encrypted_value", "encrypted_cvv": "base64_encrypted_value", "encrypted_expiration_month": "base64_encrypted_value", "encrypted_expiration_year": "base64_encrypted_value" } }, "customer": { "id": "CUST123456", "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "phone": "+1234567890", "address": "123 Example Street", "city": "Sampletown", "country": "BA", "postal_code": "12345" }, "tokenization": { "save_card_for_future_payments": true }, "browser_info": { "user_agent": "Mozilla/5.0...", "accept_header": "text/html,application/xhtml+xml...", "java_enabled": false, "color_depth": 24, "screen_height": 1080, "screen_width": 1920, "time_zone_offset": -120, "language": "en-US" }, "metadata": {}, "return_url": "https://merchant.example.com/return" } ``` ### Tokenization Parameters | Field | Type | Description | |-------|------|-------------| | `tokenization.save_card_for_future_payments` | `boolean` | If `true`, automatically saves the card for future use | **⚠️ Compliance Note**: When saving cards without explicit customer consent, ensure your terms of service and privacy policy inform customers that payment information will be stored for future use. Depending on your jurisdiction, this may have regulatory implications (GDPR, PCI-DSS, etc.). ### Response with Saved Payment Method When a card is successfully saved, the response includes a `token` in the `payment_method` object: ```json { "result": { "id": "TXN123456", "status": "APPROVED", "payment_method": { "method": "411111-******-1111", "type": "card", "brand": "VISA", "masked": "411111-******-1111", "token": "abc123def456ghi789" }, ... } } ``` The `token` value (e.g., `abc123def456ghi789`) is the **payment method ID** that can be used for future transactions. *** ## 🔑 Assigning Saved Payment Methods to a Customer When saving a card, the `customer.id` field determines how the payment method is associated: ### With `customer.id` (Recommended) When you include `customer.id` in the request: - The saved payment method is **automatically assigned** to the specified customer - The customer must **already exist** in the Payment Platform - The payment method can be retrieved and used in future transactions for this customer - You can list all saved payment methods for a customer **Example:** ```json { "reference": "ORDER-123456", "customer": { "id": "CUST123456", "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com" }, "tokenization": { "save_card_for_future_payments": true }, ... } ``` ### Without `customer.id` When you **do not** include `customer.id` in the request: - The payment method is still saved and tokenized - The payment method remains **unassociated** with any customer - You can still use the payment method token for future transactions - The payment method **cannot be associated with a customer later** - You cannot retrieve this payment method through customer-based queries **Example:** ```json { "reference": "ORDER-123456", "customer": { "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com" }, "tokenization": { "save_card_for_future_payments": true }, ... } ``` **⚠️ Important Notes:** - Payment methods saved with a `customer.id` are linked to that customer profile and can be managed through customer-based operations - Payment methods saved without a `customer.id` remain permanently unassociated and can only be referenced by their token - **Best practice**: Always include `customer.id` when saving cards to enable better customer management and payment method organization *** ## 💳 Paying with Saved Cards Once a card has been saved, you can use it for future transactions without requiring the customer to re-enter their card details. ### Requirements To pay with a saved card, you need: 1. The **payment method ID** (token) from the original transaction response 2. The **CVV** (encrypted) - may be required depending on Payment Provider support ### Request Example ```json { "reference": "ORDER-789012", "terminal_id": "TERM001", "description": "Subscription renewal", "currency": "EUR", "amount": 5000, "transaction_type": "PURCHASE", "payment_method": { "type": "saved_card", "data": { "payment_method_id": "abc123def456ghi789", "encrypted_cvv": "base64_encrypted_value" } }, "customer": { "id": "CUST123456", "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com" }, "browser_info": { "user_agent": "Mozilla/5.0...", "accept_header": "text/html,application/xhtml+xml...", "java_enabled": false, "color_depth": 24, "screen_height": 1080, "screen_width": 1920, "time_zone_offset": -120, "language": "en-US" }, "metadata": {}, "return_url": "https://merchant.example.com/return" } ``` ### Key Differences for Saved Card Payments | Field | Value | Description | |-------|-------|-------------| | `payment_method.type` | `"saved_card"` | Indicates using a saved payment method | | `payment_method.data.payment_method_id` | `string` | The token from the original save operation | | `payment_method.data.encrypted_cvv` | `string` | Base64-encoded encrypted CVV | **⚠️ Security Note:** - CVV may be **required** for saved card transactions depending on Payment Provider support - When required, CVV must be **encrypted** using your merchant public key - The full card number is **never** required for saved card payments - Only the payment method ID (and CVV if required) are needed ### Response Example ```json { "result": { "id": "TXN789012", "status": "APPROVED", "payment_method": { "method": "411111-******-1111", "type": "saved_card", "brand": "VISA", "masked": "411111-******-1111", "token": "abc123def456ghi789" }, "approved": true, ... } } ``` *** ## ⚠️ Error Handling ### Tokenization Not Enabled If tokenization is not enabled at the merchant level: ```json { "status": "INVALID", "errors": [ { "property": "tokenization", "message": "one of requested features not supported=[TOKENIZATION]" } ] } ``` **Solution**: Contact your payment platform administrator to enable tokenization. ### Customer Not Found If you provide a `customer.id` that doesn't exist: ```json { "status": "INVALID", "errors": [ { "property": "customer.id", "message": "Customer with id 'CUST123456' not found" } ] } ``` **Solution**: Create the customer first, or omit `customer.id` to save the payment method without customer association. ### Invalid Payment Method ID If the payment method ID is invalid or doesn't exist: ```json { "status": "INVALID", "errors": [ { "property": "payment_method.data.payment_method_id", "message": "Payment method not found" } ] } ``` **Solution**: Verify the payment method ID is correct and that the payment method hasn't been deleted. *** ## 📋 Complete Workflow Examples ### Workflow 1: First-Time Payment with Card Saving 1. **Encrypt card data** using merchant public key 2. **Submit transaction** with `tokenization.save_card_for_future_payments: true` 3. **Store payment method ID** from response for future use 4. **Use saved card** for subsequent payments ### Workflow 2: Subscription Payment with Saved Card 1. **Retrieve payment method ID** from your database (saved from first transaction) 2. **Encrypt CVV** (collected from customer) 3. **Submit transaction** with `payment_method.type: "saved_card"` 4. **Process response** as normal transaction *** ## 🔗 Related Documentation - [Card Authorization API Reference](../references/cards/authorize.md) - Detailed API specification - [Network Token Reference](../references/network-token/index.md) - Network token payment method details - [Encryption Example](../references/cards/encryption-example.md) - How to encrypt card data *** ## ✅ Best Practices 1. **Always encrypt sensitive data**: Card numbers, CVV, and expiration dates must be encrypted 2. **Use customer IDs**: Always associate saved cards with a customer ID for better organization 3. **Handle tokens securely**: Store payment method IDs securely in your database 4. **Implement webhooks**: Use webhooks for reliable payment status updates 5. **Validate responses**: Always check the `status` and `approved` fields in responses 6. **Error handling**: Implement comprehensive error handling for all API calls 7. **Compliance**: Ensure your integration meets PCI-DSS and local regulatory requirements --- ## Source: Embedded Fields URL: https://docs.qorepayments.com/integration-types/embedded-fields.html # Embedded Fields (EF) ## Introduction This guide covers how to integrate **Embedded Fields (EF)** into your merchant checkout page. Embedded Fields allow you to embed secure payment form fields directly into your website while maintaining PCI compliance. This guide includes: - Backend and Frontend Integration - Customization Options - Handling Callbacks - Field Configuration - Ensuring PCI Compliance ## What are Embedded Fields? Embedded Fields are a method of integrating payment forms directly into a merchant's website or application, rather than redirecting users to an external payment page. This provides a seamless checkout experience while keeping sensitive card data secure. *** ## Pre-Integration Requirements Before integrating Embedded Fields, ensure you have the following: | Requirement | Description | Where to Obtain | |:------------|:------------|:----------------| | **Client ID & Secret** | OIDC credentials for authentication | Merchant Portal (Merchant/API details) | | **x-client-key** | Public key identifying the merchant (used in frontend) | Merchant Portal | For environment-specific URLs and configuration values, see [Environments](../getting-started/environments.md). *** ## Backend Integration The backend integration involves two steps: obtaining an authentication token and creating a payment session. ### Step 1: Obtain an Access Token Authenticate using the OIDC Client Credentials flow to obtain an access token. **UAT Endpoint:** ``` POST https://uat-auth.qorepayments.com/realms/uat/protocol/openid-connect/token ``` **Production Endpoint:** ``` POST https://auth.qorepayments.com/realms/production/protocol/openid-connect/token ``` **Request Headers:** ```http Content-Type: application/x-www-form-urlencoded ``` **Request Body:** ``` grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET ``` **Response:** ```json { "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 300, "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "scope": "email profile" } ``` For more details on authentication, see [Authentication](../getting-started/authentication/index.md). ### Step 2: Create a Payment Session Once authenticated, create a payment session by calling the sessions endpoint. **UAT Endpoint:** ``` POST https://uat-api.qorepayments.com/api/sessions ``` **Production Endpoint:** ``` POST https://api.qorepayments.com/api/sessions ``` **Request Headers:** ```http Content-Type: application/json Accept: application/json Authorization: Bearer YOUR_ACCESS_TOKEN ``` **Request Body:** ```json { "terminal_id": "TERM001", "reference": "ORDER-123456", "description": "Order payment", "currency": "EUR", "amount": 25600, "transaction_type": "AUTHORIZE", "return_url": "https://merchant.example.com/return", "customer": { "first_name": "John", "last_name": "Doe", "address": "123 Main Street", "city": "Sampletown", "country": "BA", "postal_code": "10000", "email": "john.doe@example.com", "phone": "+1234567890" }, "country": "BA" } ``` **Response:** ```json { "id": "tcNyatfpvOjxxIDEdcYH", "terminal_id": "TERM001", "reference": "ORDER-123456", "description": "Order payment", "currency": "EUR", "amount": 25600, "return_url": "https://merchant.example.com/return", "customer": { "first_name": "John", "last_name": "Doe", "address": "123 Main Street", "city": "Sampletown", "country": "BA", "postal_code": "10000", "email": "john.doe@example.com", "phone": "+1234567890" }, "country": "BA", "status": "CREATED", "transaction_type": "AUTHORIZE" } ``` The `id` field in the response is your **session_id** — use this to initialize the Embedded Fields on the frontend. ### Session Request Parameters | Field | Type | Required | Description | |:------|:-----|:--------:|:------------| | `terminal_id` | string | Yes | Terminal identifier | | `reference` | string(40) | Yes | Unique order reference for the terminal | | `description` | string | Yes | Order description | | `currency` | string(3) | Yes | ISO 4217 currency code (e.g., "EUR") | | `amount` | integer | Yes | Amount in minor units (e.g., 25600 = €256.00) | | `transaction_type` | string | Yes | `"AUTHORIZE"` or `"PURCHASE"` | | `return_url` | string | Yes | Redirect URL after payment completes (approved, declined, canceled, or error) | | `customer` | object | No | Customer details (pre-populated if provided) | | `country` | string(2) | No | ISO 3166-1 alpha-2 country code | *** ## Frontend Integration To integrate Embedded Fields into your frontend, you need the **session_id** from the backend and your **client_key** from the merchant portal. ### Step 1: Load the Embedded Fields Script **UAT:** ```html ``` **Production:** ```html ``` ### Step 2: Initialize the Payment Component ```javascript const initCheckout = async (sessionId, clientKey) => { // Initialize the payment component // Note: The function name may vary by brand (e.g., Finrelay(), Paydor(), Kynta()) const paymentComponent = await window.PaymentComponent({ locale: 'en-US', environment: 'test', // Use 'production' for live transactions clientKey: clientKey, sessionId: sessionId }); // Create the card payment component const cardPaymentComponent = paymentComponent.create('card'); // Mount it to a DOM element cardPaymentComponent.mount('#card-wrapper'); return cardPaymentComponent; }; ``` !!! note "Brand-Specific Initialization" The global function name may vary depending on the payment gateway brand. Common variations include `PaymentComponent()`, `Finrelay()`, `Paydor()`, or `Kynta()`. Check your integration documentation or the loaded script for the correct function name. ### Step 3: Submit the Payment ```javascript function submitPayment(cardPaymentComponent) { const isValid = cardPaymentComponent.validate(); if (isValid) { // Get any customer data entered in the embedded fields const customer = cardPaymentComponent.getCustomer(); cardPaymentComponent.submitPayment({ customer: { // Spread existing customer data from the fields ...customer, // Override or add additional customer information firstName: 'John', lastName: 'Doe', address: '123 Main Street', city: 'Sampletown', postalCode: '10000', country: 'BA', phone: '+1234567890', email: 'john.doe@example.com' }, callback: (response, error) => { if (response?.approved) { // Payment successful console.log('Payment approved:', response); } else { // Payment failed console.log('Payment failed:', response); } if (error) { console.error('Payment error:', error); } } }); } } ``` !!! tip "Using getCustomer()" The `getCustomer()` method retrieves any customer data entered in the embedded fields (such as cardholder name and email when `hasHolderName` or `hasHolderEmail` are enabled). Spread this data with your own customer information to ensure field values are preserved. ### Component Types The Embedded Fields library supports multiple component types for different payment scenarios: | Component Type | Description | |:---------------|:------------| | `'card'` | Standard card payment form with card number, expiry, and CVV fields | | `'saved-card'` | Pre-populated saved card component for returning customers | | `'alternative-payment-method'` | Alternative payment methods (availability depends on configuration) | #### Saved Card Component For returning customers with saved payment methods, use the `'saved-card'` component type: ```javascript const savedCardConfig = { componentConfig: { data: { paymentMethodId: 'pm_abc123xyz', maskedPan: '424242******4242', expirationDate: '1227', cardBrand: 'visa' } } }; const savedCardComponent = paymentComponent.create('saved-card', savedCardConfig); savedCardComponent.mount('#saved-card-wrapper'); ``` | Config Property | Type | Description | |:----------------|:-----|:------------| | `paymentMethodId` | string | The stored payment method identifier | | `maskedPan` | string | Masked card number for display | | `expirationDate` | string | Card expiration in MMYY format | | `cardBrand` | string | Card brand (e.g., `'visa'`, `'mastercard'`, `'amex'`) | *** ## Lifecycle Management The Embedded Fields components provide methods for managing their lifecycle: ### Component Methods | Method | Description | |:-------|:------------| | `mount(selector)` | Mount the component to a DOM element | | `unmount()` | Remove the component from the DOM (can be remounted) | | `validate()` | Validate all fields and return boolean | | `getCustomer()` | Get customer data entered in the fields | | `changeLocale(locale)` | Change the UI language dynamically | | `submitPayment(options)` | Submit the payment | ### Core Instance Methods | Method | Description | |:-------|:------------| | `paymentComponent.create(type, config)` | Create a new component | | `paymentComponent.destroy()` | Clean up the core instance and all components | ### Lifecycle Example ```javascript // Initialize const paymentComponent = await window.PaymentComponent({ ... }); const cardComponent = paymentComponent.create('card'); cardComponent.mount('#card-wrapper'); // Change language dynamically cardComponent.changeLocale('bs-BA'); // Unmount temporarily (e.g., when switching payment methods) cardComponent.unmount(); // Remount when needed cardComponent.mount('#card-wrapper'); // Clean up when leaving the page paymentComponent.destroy(); ``` ### Supported Locales | Locale | Language | |:-------|:---------| | `'en-US'` | English (US) | | `'bs-BA'` | Bosnian | !!! note Additional locales may be available depending on your configuration. Contact your account manager for details. *** ### Complete Example ```html Checkout
``` *** ## Field Configuration The Embedded Fields component provides granular control over which fields are displayed and their validation requirements. ### Card Payment Fields These fields are **always displayed** and cannot be hidden: | Field | Description | Configurable | Default State | |:------|:------------|:------------:|:--------------| | Card Number | The 16-digit card number | No | Always visible, required | | Expiry Date | Card expiration date (MM/YY) | No | Always visible, required | | CVV/CVC | 3 or 4 digit security code | No | Always visible, required | ### Customer Information Fields These fields can be shown, hidden, or made required through the `customerConfig` object: | Field | Show/Hide Property | Required Property | Default Visibility | Default Required | |:------|:-------------------|:------------------|:------------------:|:----------------:| | Cardholder Name | `hasHolderName` | `holderNameRequired` | Hidden | No | | Cardholder Email | `hasHolderEmail` | `holderEmailRequired` | Hidden | No | ### Configuration Reference ```javascript const componentConfig = { componentConfig: { customerConfig: { // Cardholder Name Configuration hasHolderName: true, // Show the cardholder name field holderNameRequired: true, // Make the name field required // Cardholder Email Configuration hasHolderEmail: true, // Show the cardholder email field holderEmailRequired: false // Email is optional } }, componentListeners: { onChange: (e) => { console.log('Field changed:', e); }, onError: (e) => { console.error('Error:', e); } } }; const cardPaymentComponent = paymentComponent.create('card', componentConfig); ``` ### Configuration Options #### `hasHolderName` (boolean) Controls whether the cardholder name input field is displayed. - **`true`**: The name field is displayed - **`false`** (default): The name field is hidden #### `holderNameRequired` (boolean) When `hasHolderName` is `true`, controls whether the field is required. - **`true`**: The name field must be filled before submitting - **`false`** (default): The name field is optional !!! note Setting `holderNameRequired: true` without `hasHolderName: true` has no effect since the field is not visible. #### `hasHolderEmail` (boolean) Controls whether the cardholder email input field is displayed. - **`true`**: The email field is displayed - **`false`** (default): The email field is hidden #### `holderEmailRequired` (boolean) When `hasHolderEmail` is `true`, controls whether the field is required. - **`true`**: The email field must be filled with a valid email - **`false`** (default): The email field is optional !!! note Setting `holderEmailRequired: true` without `hasHolderEmail: true` has no effect since the field is not visible. ### Common Configuration Patterns === "Card Fields Only" ```javascript const componentConfig = { componentConfig: { customerConfig: { // All customer fields hidden by default } } }; ``` === "Required Cardholder Name" ```javascript const componentConfig = { componentConfig: { customerConfig: { hasHolderName: true, holderNameRequired: true } } }; ``` === "Optional Email for Receipts" ```javascript const componentConfig = { componentConfig: { customerConfig: { hasHolderEmail: true, holderEmailRequired: false } } }; ``` === "Full Customer Information" ```javascript const componentConfig = { componentConfig: { customerConfig: { hasHolderName: true, holderNameRequired: true, hasHolderEmail: true, holderEmailRequired: true } } }; ``` ### Providing Customer Data at Submission You can provide or override customer information when submitting the payment: ```javascript cardPaymentComponent.submitPayment({ customer: { firstName: 'John', lastName: 'Doe', address: '123 Main Street', city: 'Sampletown', postalCode: '10000', country: 'US', phone: '+1-555-123-4567', email: 'john.doe@example.com' }, callback: (response, error) => { // Handle response } }); ``` | Customer Field | Description | |:---------------|:------------| | `firstName` | Customer's first name | | `lastName` | Customer's last name | | `address` | Street address | | `city` | City name | | `postalCode` | Postal/ZIP code | | `country` | ISO 3166-1 alpha-2 country code (e.g., 'US', 'GB', 'DE') | | `phone` | Phone number | | `email` | Email address | !!! tip Use `cardPaymentComponent.getCustomer()` to retrieve any customer data entered in the embedded fields, then spread it with your own data: `{ ...customer, firstName: 'Override' }`. *** ## Event Handling ### Available Callbacks | Callback | Description | |:---------|:------------| | `onChange` | Triggered when any field value changes | | `onError` | Triggered when a validation or processing error occurs | ### Example with Event Listeners ```javascript const componentConfig = { componentConfig: { customerConfig: { hasHolderEmail: true, holderEmailRequired: false } }, componentListeners: { onChange: (event) => { console.log('Field changed:', event); // Use this to track form completion progress }, onError: (error) => { console.error('Error occurred:', error); // Display error to user or log for debugging } } }; const cardPaymentComponent = paymentComponent.create('card', componentConfig); ``` ### Validation Use the `validate()` method to check if all required fields are filled correctly: ```javascript const isValid = cardPaymentComponent.validate(); // For detailed validation information: const validationObject = cardPaymentComponent.getDataWithValidation(); // Returns which fields are invalid and their error messages ``` *** ## Customization Embedded Fields can be customized to match your website's branding by overriding CSS variables. ### CSS Variables ```css embedded-fields-card, #card-wrapper { --lib-font-family: var(--app-font-family); --lib-input-font-size: 1.7rem; --lib-accent-color: #556a61; --lib-accent-background-color: #DFE8E1; --lib-label-font-size: 1rem; --lib-warning-color: #ff8c70; --lib-warning-background-color: #fff6ee; --lib-input-color: #556a61; --lib-input-focus-border-color: #86a599; --lib-input-border-color: #d4d7d6; --lib-input-focus-color: #179261; --lib-place-holder-color: #e1e1e1; --lib-input-border-radius: 0.5rem; --lib-button-border-radius: 0.7rem; --lib-button-font-size: 1.1rem; } ``` ### CSS Selectors For more granular control, use CSS selectors to target specific elements: ```css embedded-fields-card div.input-container.lib-input-root div.lib-input-wrapper input { font-family: var(--app-font-family); } lib-input .lib-input-wrapper, lib-iframe-input div.iframe-input-wrapper { border-style: initial !important; border-bottom: 1px solid var(--lib-input-border-color) !important; border-radius: initial !important; } ``` *** ## PCI Compliance Payment Card Industry Data Security Standard (PCI DSS) compliance is essential when handling card data. ### Why Embedded Fields Help with PCI Compliance Embedded Fields are designed to minimize your PCI scope: - **Sensitive card data never touches your servers** — card numbers, CVV, and expiration dates are captured directly by the secure iframe - **Tokenization** — actual card data is replaced with secure tokens - **Encryption** — all data transmission is encrypted ### Merchant Levels | Level | Transaction Volume | Validation Method | |:------|:-------------------|:------------------| | Level 1 | Over 6 million annually | External audit by QSA | | Level 2 | 1-6 million annually | SAQ + external scan | | Level 3 | 20,000-1 million online | SAQ + external scan | | Level 4 | Under 20,000 online | SAQ | ### Consequences of Non-Compliance - Fines from payment networks - Loss of payment processing privileges - Legal action by regulatory bodies - Reputation damage - Increased risk of data breaches *** ## Troubleshooting ### Common Issues #### Unable to Load Embedded Fields 1. Verify the script URL is correct for your environment 2. Check that your `client_key` is valid 3. Ensure the `session_id` has not expired #### Unable to Create a Session 1. Verify the `Authorization` header is correct 2. Check that your access token has not expired 3. Review the request payload for missing or invalid values #### Payment Submission Fails 1. Use `cardPaymentComponent.validate()` to check for validation errors 2. Check `cardPaymentComponent.getDataWithValidation()` for detailed error information 3. Review the `onError` callback for error details ### Debugging Tips Use the callback functions to monitor the payment flow: ```javascript const componentConfig = { componentListeners: { onChange: (e) => console.log('Change:', e), onError: (e) => console.error('Error:', e) } }; ``` *** ## Best Practices 1. **Obtain Required Parameters First** — Retrieve `sessionId` from your backend and `clientKey` from the merchant portal before initializing 2. **Load Script Asynchronously** — Load the embedded-fields.js script asynchronously to avoid blocking page rendering 3. **Handle All Callbacks** — Implement both `onChange` and `onError` callbacks for better debugging and user experience 4. **Validate Before Submission** — Always call `validate()` before `submitPayment()` to catch errors early 5. **Provide Clear Error Messages** — Use validation results to show users exactly what needs to be corrected 6. **Test in UAT First** — Always test your integration in the UAT environment before going to production *** ## FAQ ### What is the difference between Embedded Fields and Hosted Payment Page? **Embedded Fields** integrate payment forms directly into your website, giving you full control over the UI/UX while maintaining PCI compliance. **Hosted Payment Page (HPP)** redirects customers to a hosted checkout page. See the [Hosted Payment Page](./hosted-payment-page.md) documentation for details. ### How do I obtain the necessary API keys? All credentials (Client ID, Client Secret, x-client-key) can be obtained from the Merchant Portal under Merchant/API details. ### Can I customize the appearance of Embedded Fields? Yes, you can customize the appearance using CSS variables and selectors. See the [Customization](#customization) section. ### What currencies are supported? The supported currencies depend on your merchant configuration. Contact your account manager for details. ### How do I test my integration? Use the UAT environment for testing. Set `environment: 'test'` when initializing the PaymentComponent. --- ## Source: Hosted Payment Page URL: https://docs.qorepayments.com/integration-types/hosted-payment-page.html # Hosted Payment Page API The **Hosted Payment Page (HPP)** allows you to create secure, preconfigured payment sessions that redirect customers to a checkout interface for completing a transaction. *** ## 🔐 Authentication All requests require a valid **OIDC access token**: ### HTTP Header ```http Authorization: Bearer Content-Type: application/json ``` Obtain the token using OIDC flows (typically **Client Credentials** or **Authorization Code** flow). *** ## 🛠️ Backend Setup 1) Obtain an authentication token. Use the OIDC Client Credentials flow. See [Obtain Access Token](../getting-started/authentication/obtain-access-token.md) for endpoint details. ```javascript function createToken() { return loadJsonFile('[path]/secret.json') .then((secretData) => { const params = new URLSearchParams(); params.append('grant_type', 'client_credentials'); params.append('client_id', secretData.clientId); params.append('client_secret', secretData.clientSecret); return fetch( secretData.tokenEndpoint, // e.g. https://uat-auth.finrelay.io/realms/uat/protocol/openid-connect/token { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: params.toString() } ).then(response => { if (!response.ok) { throw new Error(`Network response was not ok: ${response.status}`); } return response.json(); }) }) } ``` 2) Create a session using the `access_token`. ```javascript function createSession(data) { // data -> result from token endpoint const randomString = `date-${new Date().getTime()}`; return loadJsonFile('[path]/secret.json') .then((secretData) => { return fetch( '/api/sessions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': `Bearer ${data?.access_token}` }, body: JSON.stringify({ "terminal_id": "DEV02", "reference": randomString, "description": "f2aed380-b8bb-481f-92be-3060cc1ed4a2", "currency": "EUR", "amount": 25600, "transaction_type": "AUTHORIZE", "return_url": "https://google.com/return-url", "customer": { "first_name": "John", "last_name": "Doe", "address": "Address", "city": "City", "country": "BA", "postal_code": "10000", "email": "email@email.com", "phone": "00000" }, "country": "BA", "metadata": { "simulated_flow": "VERSIONING_FINGERPRINT_Y_CHALLENGE", "response_code": "APPROVED", "payment_provider_response_message": "approved", "payment_provider_response_code": "approved-code", "approval_code": "N/A" } }) } ).then(response => { if (!response.ok) { throw new Error(`Network response was not ok: ${response.status}`); } return response.json(); // session id }) }) } ``` *** ## 📤 Endpoint ```http POST /api/hosted-payment-page ``` Creates a hosted payment session and returns a URL for redirection. *** ## 📦 Request Body ```json { "reference": "ORDER-123456", "terminal_id": "TERM001", "currency": "EUR", "amount": 100, "transaction_type": "PURCHASE", "description": "Order payment", "return_url": "https://merchant.example.com/return", "customer_id": "CUST123456", "customer_first_name": "John", "customer_last_name": "Doe", "customer_email": "john.doe@example.com", "customer_phone_number": "+1234567890", "customer_address": "123 Example Street", "customer_city": "Sampletown", "customer_country": "BA", "customer_postal_code": "12345", "tokenization": { "save_card_for_future_payments": true } } ``` ### ✅ Required Fields | Field | Type | Description | | ------------------ | --------- | -------------------------------------------- | | `reference` | `string` | Unique reference for the order | | `terminal_id` | `string` | ID of the terminal used for payment | | `currency` | `string` | Currency in ISO 4217 format (e.g., "EUR") | | `amount` | `integer` | Amount in minor units (e.g., 100 = €1.00) | | `transaction_type` | `string` | Must be `"PURCHASE"` or `"AUTHORIZE"` | | `return_url` | `string` | Redirect URL after payment completes (approved, declined, canceled, or error) | ### 🧾 Optional Fields | Field | Type | Description | | ------------------------------------------------- | --------- | -------------------------------------------------------------------------------------- | | `description` | `string` | Payment description shown on HPP | | `customer_id` | `string` | Payment Platform customer ID for saved cards (required to use saved payment methods) | | `customer_first_name` | `string` | Customer's first name | | `customer_last_name` | `string` | Customer's last name | | `customer_email` | `string` | Email address | | `customer_phone_number` | `string` | Phone number (E.164 format recommended) | | `customer_address` | `string` | Street address | | `customer_city` | `string` | City name | | `customer_country` | `string` | ISO 3166-1 alpha-2 code (e.g., "BA") | | `customer_postal_code` | `string` | ZIP/postal code | | `tokenization.save_card_for_future_payments` | `boolean` | If `true`, automatically saves the card for future use | | `tokenization.show_save_card_for_future_payments` | `boolean` | If `true`, displays a checkbox allowing customers to opt-in to save card | *** ## 🧾 Response ```json { "redirect_url": "https://api.example.com/hpp/payment/TO6FjoePBl0GoiAZI6IJeAYIuUanvAmaW1YsBpbj/GfXYVKxPXGksQhBJuyAZ", "session_id": "GfXYVKxPXGksQhBJuyAZ" } ``` | Field | Type | Description | | -------------- | -------- | -------------------------------------------------------------------------- | | `redirect_url` | `string` | Fully qualified URL to which the customer should be redirected for payment | | `session_id` | `string` | Unique identifier for the hosted payment session | ### URL Structure The `redirect_url` follows this pattern: ``` https:///hpp/payment// ``` The HPP application automatically extracts the `client-key` and `session-id` from the URL path. Simply redirect your customer to the `redirect_url` returned in the response. *** ## 💳 Tokenization Notes If `tokenization.save_card_for_future_payments = true` is used: * Tokenization must be **enabled at the merchant level** (contact your payment platform administrator to enable this feature). * If unsupported, the response will include: ```json { "status": "INVALID", "errors": [ { "property": "tokenization", "message": "one of requested features not supported=[TOKENIZATION]" } ] } ``` For detailed information on the two methods of saving cards, see the [Saving Cards for Future Payments](#saving-cards-for-future-payments) section below. *** ## 💾 Saving Cards for Future Payments The HPP supports two methods for saving payment cards for future use: | Feature | `show_save_card_for_future_payments` | `save_card_for_future_payments` | |---------|--------------------------------------|--------------------------------| | Displays checkbox | ✅ Yes | ❌ No | | Customer consent | Explicit (opt-in) | Implicit | | Use case | Customer choice | Subscription/recurring payments | ### Method 1: `show_save_card_for_future_payments` This parameter displays a **checkbox** on the HPP allowing customers to opt-in to saving their card. **Request Body Example:** ```json { "reference": "ORDER-123456", "terminal_id": "TERM001", "currency": "EUR", "amount": 100, "transaction_type": "PURCHASE", "return_url": "https://merchant.example.com/return", "customer_id": "CUST123456", "tokenization": { "show_save_card_for_future_payments": true } } ``` When `show_save_card_for_future_payments` is set to `true`, a checkbox appears on the payment page allowing the customer to choose whether to save their card for future payments. ### Method 2: `save_card_for_future_payments` This parameter **implicitly saves** the card without displaying a checkbox to the customer. **Request Body Example:** ```json { "reference": "ORDER-123456", "terminal_id": "TERM001", "currency": "EUR", "amount": 100, "transaction_type": "PURCHASE", "return_url": "https://merchant.example.com/return", "customer_id": "CUST123456", "tokenization": { "save_card_for_future_payments": true } } ``` When `save_card_for_future_payments` is set to `true`, the card is automatically saved without requiring customer interaction. **⚠️ Compliance Note**: When using `save_card_for_future_payments` (implicit saving), ensure your terms of service and privacy policy inform customers that payment information will be stored for future use. Depending on your jurisdiction, this may have regulatory implications (GDPR, PCI-DSS, etc.). **Note**: If both `show_save_card_for_future_payments` and `save_card_for_future_payments` are set to `true`, the `save_card_for_future_payments` behavior takes precedence and the card is saved without displaying a checkbox. ### 🔑 Requirements for Card Tokenization For both methods to work, the following requirements must be met: 1. **Tokenization must be enabled** at the merchant level 2. **Customer must exist**: A customer with the specified `customer_id` must already exist in the Payment Platform before creating the HPP session (if `customer_id` is provided). If the customer doesn't exist, the API will return a validation error. ### 📌 Assigning Saved Payment Methods to a Customer When a card is successfully saved (via either method): - **With `customer_id`**: The saved payment method is automatically assigned to the specified `customer_id`. The customer record must already exist in the Payment Platform before creating the HPP session. - **Without `customer_id`**: The payment method is still saved but remains unassociated with any customer. These payment methods can be retrieved via API but cannot be associated with a customer later. **Important Notes:** - Payment methods saved with a `customer_id` are linked to that customer profile - Payment methods saved without a `customer_id` remain permanently unassociated - To save a payment method for a specific customer, include the `customer_id` in the initial HPP request *** ## 💳 Paying with Saved Cards Customers who have saved payment methods can use them for future payments by providing their `customer_id`. ### Using Saved Cards via HPP When a `customer_id` is included in the HPP request, and that customer has saved payment methods, the HPP will display a **"Saved Cards"** payment option allowing the customer to select from their previously saved cards. **Request Body Example:** ```json { "reference": "ORDER-789012", "terminal_id": "TERM001", "currency": "EUR", "amount": 5000, "transaction_type": "PURCHASE", "return_url": "https://merchant.example.com/return", "customer_id": "CUST123456" } ``` ### Using Saved Cards via API Saved payment methods can also be used directly through the API by referencing the payment method token. See the [API Integration](./api-integration.md) documentation for details on using saved payment methods with direct API calls. ### ⚠️ Important: Customer ID Required **It is not possible to pay with a saved card if no `customer_id` is provided in the request.** - Without a `customer_id`, the system cannot retrieve saved payment methods - Always include the `customer_id` field when you want to enable saved card payment options *** ## ⚠️ Misconfiguration Handling If the link is invalid (e.g., no supported payment methods), the system returns a user-friendly message: > **"This payment is currently unavailable. Please contact the merchant."** *** ## 🔁 Redirects and Callbacks The HPP handles redirects automatically based on the URLs configured in your session request: The customer is always redirected to the `return_url` after the payment completes, regardless of the outcome (approved, declined, canceled, or error). This URL is configured when creating the HPP session via the `return_url` field in the request body. *** ## ✅ Best Practices - **Use the `redirect_url` from the response**: After creating an HPP session, redirect the customer to the `redirect_url` returned in the response. The HPP extracts all required parameters from the URL automatically. - **Authenticate properly**: First obtain an auth token, then create the session with the `Authorization` header. - **Configure the redirect URL**: Always provide `return_url` to handle the post-payment redirect. *** ## 🛠️ Troubleshooting - Any issues during HPP setup are shown during the flow with descriptive errors and guidance. *** ## 🎨 Customization The Hosted Payment Page can be customized through the Merchant Portal dashboard. Contact your account manager for details on available customization options. ## 🧪 Sample cURL Request ```bash curl -X POST https://api.example.com/api/hosted-payment-page \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "reference": "ORDER-123456", "terminal_id": "TERM001", "currency": "EUR", "amount": 100, "transaction_type": "PURCHASE", "return_url": "https://merchant.example.com/return", "customer_id": "CUST123456", "tokenization": { "save_card_for_future_payments": true } }' ``` --- ## Source: 3DS Card Payment URL: https://docs.qorepayments.com/recipes/3ds-card-payment.html # Recipe: 3DS Card Payment Integration This recipe walks you through the complete 3D Secure card payment flow — from authentication and card encryption through to device fingerprinting, challenge handling, and status verification. All examples use plain HTTP requests (with `curl`) for server-side steps and browser JavaScript for the client-side steps that 3DS requires. *** ## Prerequisites You will need the following from your merchant dashboard: - **API Base URL** and **OIDC Base URL** — see [Environments](../getting-started/environments.md) - **Client ID** and **Client Secret** — OIDC credentials (dashboard > Merchant / API details) - **Terminal ID** — configured with a card provider or the card-simulator - **RSA Public Key** — PEM public key for card data encryption (dashboard > Merchant details) - A **return URL** — publicly accessible URL where the customer is redirected after 3DS authentication (use [ngrok](https://ngrok.com/) or similar for local development) *** ## Step 1 — Obtain an Access Token All API calls require a Bearer token. Obtain one using the OAuth 2.0 Client Credentials flow: ```http POST {OIDC_BASE_URL}/realms/{realm}/protocol/openid-connect/token Content-Type: application/x-www-form-urlencoded ``` Use `uat` as the realm for UAT, `production` for production. Cache the token and refresh it before `expires_in` elapses. === "curl" ```bash curl -X POST "{OIDC_BASE_URL}/realms/{realm}/protocol/openid-connect/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id={CLIENT_ID}" \ -d "client_secret={CLIENT_SECRET}" ``` === "Node.js" Save as `get-token.mjs` and run with `node get-token.mjs`: ```javascript // get-token.mjs const TOKEN_URL = '{OIDC_BASE_URL}/realms/{realm}/protocol/openid-connect/token'; const CLIENT_ID = '{CLIENT_ID}'; const CLIENT_SECRET = '{CLIENT_SECRET}'; async function getAccessToken() { const res = await fetch(TOKEN_URL, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'client_credentials', client_id: CLIENT_ID, client_secret: CLIENT_SECRET, }).toString(), }); if (!res.ok) { throw new Error(`Token request failed: ${res.status} ${await res.text()}`); } return await res.json(); } const data = await getAccessToken(); console.log('Access Token:', data.access_token); console.log('Expires In:', data.expires_in, 'seconds'); ``` === "Java" Save as `GetToken.java` and run with `java GetToken.java` (Java 11+): ```java // GetToken.java import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; public class GetToken { private static final String TOKEN_URL = "{OIDC_BASE_URL}/realms/{realm}/protocol/openid-connect/token"; private static final String CLIENT_ID = "{CLIENT_ID}"; private static final String CLIENT_SECRET = "{CLIENT_SECRET}"; public static void main(String[] args) throws Exception { String body = "grant_type=client_credentials" + "&client_id=" + CLIENT_ID + "&client_secret=" + CLIENT_SECRET; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(TOKEN_URL)) .header("Content-Type", "application/x-www-form-urlencoded") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); HttpResponse response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { System.err.println("Token request failed: " + response.statusCode()); System.err.println(response.body()); System.exit(1); } System.out.println(response.body()); } } ``` **Response:** ```json { "access_token": "eyJhbGciOiJSUzI1NiIs...", "expires_in": 300, "token_type": "Bearer" } ``` See [Obtain Access Token](../getting-started/authentication/obtain-access-token.md) for full details. *** ## Step 2 — Encrypt Card Data All sensitive card fields must be encrypted with your merchant RSA public key using **RSA-OAEP with SHA-256** before sending to the API. Encrypt each field individually and Base64-encode the result. Fields requiring encryption: **card number**, **CVV**, **expiration month**, **expiration year**. ```bash encrypt_field() { echo -n "$1" | openssl pkeyutl -encrypt \ -pubin -inkey <(echo "$PUBLIC_KEY") \ -pkeyopt rsa_padding_mode:oaep \ -pkeyopt rsa_oaep_md:sha256 \ -pkeyopt rsa_mgf1_md:sha256 | base64 } ENCRYPTED_CARD_NUMBER=$(encrypt_field "4111111111111111") ENCRYPTED_CVV=$(encrypt_field "123") ENCRYPTED_EXP_MONTH=$(encrypt_field "12") ENCRYPTED_EXP_YEAR=$(encrypt_field "2027") ``` !!! warning "Server-side only" Card encryption must happen on your server. Never expose the RSA public key or raw card data in the browser. See [Encryption Example](../references/cards/encryption-example.md) for more languages and details. *** ## Step 3 — Collect Browser Info The `browser_info` object is **required** for all card transactions. It is used by the issuer for 3DS risk assessment. Collect it from the customer's browser using JavaScript: ```javascript const browserInfo = { user_agent: navigator.userAgent, accept_header: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", java_enabled: navigator.javaEnabled?.() ?? false, color_depth: screen.colorDepth, screen_height: screen.height, screen_width: screen.width, time_zone_offset: new Date().getTimezoneOffset(), language: navigator.language, }; ``` Send this object to your server along with the card details so it can be included in the authorize request. *** ## Step 4 — Authorize the Payment Submit the encrypted card data, browser info, and payment details to the gateway: ```http POST {API_BASE_URL}/api/transactions/authorize Authorization: Bearer {access_token} Content-Type: application/json ``` ```json { "terminal_id": "{TERMINAL_ID}", "reference": "ORDER-123456", "description": "Card payment", "currency": "EUR", "amount": 10000, "transaction_type": "PURCHASE", "return_url": "https://your-domain.com/payment/return", "error_url": "https://your-domain.com/payment/return", "cancel_url": "https://your-domain.com/payment/return", "payment_method": { "type": "card", "data": { "encrypted_card_number": "{ENCRYPTED_CARD_NUMBER}", "encrypted_cvv": "{ENCRYPTED_CVV}", "encrypted_expiration_month": "{ENCRYPTED_EXP_MONTH}", "encrypted_expiration_year": "{ENCRYPTED_EXP_YEAR}" } }, "customer": { "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com" }, "browser_info": { "user_agent": "Mozilla/5.0...", "accept_header": "text/html,application/xhtml+xml...", "java_enabled": false, "color_depth": 24, "screen_height": 1080, "screen_width": 1920, "time_zone_offset": -120, "language": "en-US" } } ``` *** ## Step 5 — Handle the Response The authorize response contains four top-level fields. **Which field is populated determines your next step.** This same logic applies after every API call in the 3DS flow — not just the initial authorize. | Response Field | Meaning | Next Step | |---|---|---| | `result` | Transaction finalized (approved or declined) | Done | | `action` with type `THREE_DS_2_FINGERPRINT` | Device fingerprint required | Go to [Step 6](#step-6-device-fingerprint) | | `action` with type `THREE_DS_2_CHALLENGE` | Challenge required | Go to [Step 8](#step-8-challenge) | | `redirect` | Redirect the customer | Redirect browser to `redirect.url` | | `form_submit` | Auto-submit a form | Post a hidden form to `form_submit.url` with the fields in `form_submit.data` | **Example response (fingerprint required):** ```json { "result": null, "action": { "transaction_id": "vvIVFmPuwYosePLsoDsW", "session_id": "abc123-session-id", "type": "THREE_DS_2_FINGERPRINT", "token": "eyJhbGciOiJIUzI1NiIs...", "payment_data": "encrypted_payment_data_string" }, "redirect": null, "form_submit": null } ``` !!! tip "Build a response handler, not a step counter" Implement a single function that inspects each response and routes to the appropriate action. The gateway may return a new fingerprint or challenge at any point (e.g., after auto-retry with a different provider). See [Handling Repeated Authentication](../references/3ds/index.md#handling-repeated-authentication) for details. **Example response router (browser JavaScript):** ```javascript async function processPayment(data) { if (data.result) { // Transaction finalized — show result to customer displayResult(data.result); return; } if (data.action) { if (data.action.type === 'THREE_DS_2_FINGERPRINT') { const result = await handleFingerprint(data.action); return processPayment(result); // recurse — next response may need more steps } if (data.action.type === 'THREE_DS_2_CHALLENGE') { handleChallenge(data.action); return; // page navigates away } } if (data.redirect) { window.location.href = data.redirect.url; return; } if (data.form_submit) { submitHiddenForm(data.form_submit.url, '_self', data.form_submit.data); return; } } ``` !!! info "`form_submit.data` shape" `form_submit.data` is a flat key–value object, for example: ```json { "PaReq": "eJxVUdtugkAQ/RXC...", "MD": "vvIVFmPuwYosePLsoDsW", "TermUrl": "https://gateway.example.com/return" } ``` The `submitHiddenForm` helper iterates these entries with `Object.entries()` and creates a hidden `` for each one. *** ## Step 6 — Device Fingerprint When the response contains `action.type: "THREE_DS_2_FINGERPRINT"`, you must collect the customer's device fingerprint before authentication can proceed. ### 6a. Decode the token The `action.token` is a Base64-encoded JSON object: ```javascript const decodedToken = JSON.parse(atob(action.token)); ``` Decoded structure: ```json { "three_ds_server_trans_id": "abc123-server-trans-id", "three_ds_method_url": "https://acs.issuer.com/fingerprint", "three_ds_method_notification_url": "https://gateway.example.com/notification", "acs_url": "https://acs.issuer.com/challenge", "acs_trans_id": "xyz789-acs-trans-id", "message_version": "2.2.0", "challenge_window_size": "05" } ``` ### 6b. Submit the fingerprint form If `three_ds_method_url` is present, submit a hidden form to it inside a hidden iframe: ```html ``` ```javascript const methodData = btoa(JSON.stringify({ threeDSServerTransID: decodedToken.three_ds_server_trans_id, threeDSMethodNotificationURL: decodedToken.three_ds_method_notification_url, })); // Submit to ACS in hidden iframe submitHiddenForm(decodedToken.three_ds_method_url, 'fingerprint-iframe', { threeDSMethodData: methodData, }); // The ACS signals completion to the server, not to this iframe. // Wait a fixed 5 seconds (per EMVCo 3DS spec) before proceeding. await new Promise(resolve => setTimeout(resolve, 5000)); ``` If `three_ds_method_url` is **not** present, skip the iframe submission and proceed directly to Step 7. ### Helper: submitHiddenForm ```javascript function submitHiddenForm(url, target, fields) { const form = document.createElement('form'); form.method = 'POST'; form.action = url; form.target = target; form.style.display = 'none'; for (const [name, value] of Object.entries(fields)) { const input = document.createElement('input'); input.type = 'hidden'; input.name = name; input.value = value; form.appendChild(input); } document.body.appendChild(form); form.submit(); form.remove(); } ``` *** ## Step 7 — Request Authentication After the fingerprint step (or immediately if no `three_ds_method_url` was provided), call the request-authentication endpoint from your server: ```http POST {API_BASE_URL}/api/transactions/three-ds/{three_ds_server_trans_id}/request-authentication Authorization: Bearer {access_token} Content-Type: application/json ``` ```json { "fingerprint_result": "{base64-encoded 'Y' or 'N'}", "payment_data": "{payment_data from the action response}" } ``` - `fingerprint_result`: Base64-encode `"Y"` if the fingerprint form was submitted, `"N"` if `three_ds_method_url` was absent - `payment_data`: pass through the `action.payment_data` value from the authorize response **The response uses the same structure as Step 5.** Check `result`, `action`, `redirect`, and `form_submit` to determine the next step: - `result` present — transaction finalized (frictionless approval or decline). **Done.** - `action` with `type: "THREE_DS_2_CHALLENGE"` — proceed to [Step 8](#step-8-challenge) - `redirect` or `form_submit` — redirect or auto-submit as described in Step 5 *** ## Step 8 — Challenge If the issuer requires additional verification, the response will contain `action.type: "THREE_DS_2_CHALLENGE"`. The customer must interact with the issuer's authentication UI (e.g., OTP, biometric, banking app confirmation). ### 8a. Build the Challenge Request (CReq) Decode the token (same structure as Step 6a) and construct the CReq: ```javascript const decodedToken = JSON.parse(atob(action.token)); const creq = btoa(JSON.stringify({ threeDSServerTransID: decodedToken.three_ds_server_trans_id, acsTransID: decodedToken.acs_trans_id, messageVersion: decodedToken.message_version, messageType: "CReq", challengeWindowSize: decodedToken.challenge_window_size, })); ``` ### 8b. Submit to the ACS (full-page redirect) Submit the CReq as a hidden form targeting the current window. The customer's browser navigates to the issuer's authentication page: ```javascript submitHiddenForm(decodedToken.acs_url, '_self', { creq }); ``` After the customer completes the challenge, the gateway processes the authentication result, authorizes the transaction, and redirects the customer back to your `return_url`. !!! info "No server-side callback needed" With the full-page redirect approach, the gateway handles the `authentication-completed` step internally. You do **not** need to call this endpoint yourself. *** ## Step 9 — Verify Transaction Status Once the customer is redirected back to your `return_url`, verify the transaction outcome from your server: ```http GET {API_BASE_URL}/api/transactions/{transaction_id}/status Authorization: Bearer {access_token} ``` **Response:** ```json { "id": "vvIVFmPuwYosePLsoDsW", "status": "APPROVED", "amount": 10000, "currency": "EUR" } ``` | Status | Description | |---|---| | `APPROVED` | Authentication successful, transaction authorized | | `DECLINED` | Authentication failed or transaction declined by issuer | | `CANCELED` | Customer canceled or abandoned the challenge | | `SESSION_EXPIRED` | Authentication session timed out | !!! tip "Use Webhooks" For the most reliable status updates, configure a [webhook](../references/webhooks/index.md) to receive real-time notifications. This handles edge cases where the customer closes the browser before being redirected back. *** ## Complete Flow Summary ```mermaid sequenceDiagram participant Browser participant Server as Your Server participant Gateway as Payment Gateway participant ACS as Issuer ACS Server ->> Gateway: Step 1: Obtain access token (OIDC) Server ->> Server: Step 2: Encrypt card data (RSA-OAEP) Browser ->> Server: Card details + browser info Server ->> Gateway: Step 4: POST /api/transactions/authorize Gateway -->> Server: Step 5: action (THREE_DS_2_FINGERPRINT) Server -->> Browser: Forward action to browser Note over Browser: Step 6 — Fingerprint Browser ->> ACS: POST threeDSMethodData (hidden iframe) Note over Browser: Wait 5 seconds (fixed, per EMVCo spec) Browser ->> Server: Step 7: Fingerprint result + payment_data Server ->> Gateway: POST /three-ds/{id}/request-authentication Gateway -->> Server: action (THREE_DS_2_CHALLENGE) or result Server -->> Browser: Forward response alt Frictionless — result returned Note over Browser: Transaction approved, no customer interaction needed else Challenge Required Note over Browser: Step 8 — Challenge Browser ->> ACS: POST creq (full-page redirect) ACS ->> Browser: Authentication UI (OTP, biometric, etc.) ACS -->> Gateway: Authentication result Gateway -->> Browser: Redirect to return_url Note over Browser: Step 9 — Verify status Server ->> Gateway: GET /api/transactions/{id}/status end ``` *** ## Testing with the 3DS Simulator In UAT, you can control the 3DS flow by passing `simulated_flow` in the transaction `metadata`: | Flow | Description | |---|---| | `VERSIONING_FINGERPRINT_Y_CHALLENGE` | Full flow: fingerprint + challenge **(default)** | | `VERSIONING_FINGERPRINT_A_FRICTIONLESS` | Frictionless approval — no customer interaction | | `VERSIONING_FINGERPRINT_N_FRICTIONLESS` | Frictionless decline | | `VERSIONING_NOT_ENROLLED` | Card not enrolled in 3DS — skips authentication | | `VERSIONING_FINGERPRINT_TIMEOUT_CHALLENGE` | Fingerprint times out, then challenge | | `VERSIONING_FINGERPRINT_TIMEOUT_FRICTIONLESS` | Fingerprint times out, then frictionless | Control the final transaction result with `response_code`: | Code | Result | |---|---| | `TRX_STATUS_APPROVED` | Approved | | `TRX_STATUS_DECLINED` | Declined | | `TRX_STATUS_FAILED` | Failed | **Example:** ```json { "metadata": { "simulated_flow": "VERSIONING_FINGERPRINT_Y_CHALLENGE", "response_code": "TRX_STATUS_APPROVED" } } ``` See [3DS Simulator](../references/3ds/index.md#simulator) for full details and additional override options. *** ## Related Documentation - [3D Secure Reference](../references/3ds/index.md) — Full protocol details, token structures, and edge cases - [API Integration](../integration-types/api-integration.md) — Direct API integration guide - [Card Encryption](../references/cards/encryption-example.md) — RSA-OAEP encryption examples - [Authentication](../getting-started/authentication/obtain-access-token.md) — OIDC token retrieval - [Webhooks](../references/webhooks/index.md) — Real-time payment status notifications - [Transaction Statuses](../transaction-api/supported-transaction-statuses.md) — All transaction lifecycle states --- ## Source: Changelog URL: https://docs.qorepayments.com/changelog.html # Changelog All notable changes to the Payment Gateway documentation will be documented on this page. --- ## 2026-04-16 ### Added - Added `payment_method_expiry` field to the Transaction Processed Webhook — an ISO-8601 date representing the last day of the payment method's expiry month (e.g., `2027-12-31`), nullable - Added **AI-Assisted Integration** page under Getting Started with brand-specific `llms.txt` and `llms-full.txt` URLs and usage examples for Claude, Cursor, GitHub Copilot, and other AI tools - Added `` meta tags to every page's `` for automatic discovery of `llms.txt` and `llms-full.txt` by AI agents and crawlers - Added tip callout on the homepage pointing to the AI-Assisted Integration page --- ## 2026-04-15 ### Added - New **Recipes** section with step-by-step 3DS card payment integration guide - Encryption example now includes Node.js and Java examples (runnable single-file snippets) - Access token example in recipe includes curl, Node.js, and Java tabs - Introduced documentation changelog ### Fixed - Fixed redirect URLs for 3DS authentication flows ## 2026-04-13 ### Added - Simulator documentation for card and 3DS flows ## 2026-04-10 ### Changed - Removed `x-api-key` from hosted payment page docs ## 2026-04-08 ### Fixed - Corrected maximum retry limits for 3DS authentication ### Added - Documentation for 3DS fallback behavior and repeated authentication ## 2026-04-01 ### Fixed - Use exact 3DS action type values (`THREE_DS_2_FINGERPRINT`, `THREE_DS_2_CHALLENGE`) ## 2026-03-31 ### Changed - Replaced `/api/oauth2/login/token` with OIDC client credentials in HPP example ### Added - Payment (deposit) documentation - 3D Secure integration documentation ## 2026-03-19 ### Added - Payout documentation with INR bank transfer support - Google Pay integration docs and PDF generation ### Changed - Added integration type limitation warning to OCT docs - Added note that HPP and Embedded Fields are not supported for OCT transactions - Added note that HPP and Embedded Fields are not supported for payouts - Removed `/api/token` endpoints from docs ### Fixed - Removed `{.md-button}` from Google Pay PDF download link - Added `pymdownx.emoji` extension for Material icon rendering ## 2026-03-16 ### Added - Neogated (BLIK) payment method documentation - Google Pay integration documentation ## 2026-03-13 ### Added - OCT (Original Credit Transaction) documentation ## 2026-03-08 ### Fixed - Updated `submitPayment` customer postal code field from `zip` to `postalCode` ## 2026-03-04 ### Added - Network Token payment method documentation ### Changed - Updated Network Token docs for ECI and source field changes ### Fixed - Removed erroneous redirect for API integration page ## 2026-01-29 ### Changed - Updated Embedded Fields documentation with brand-specific initialization notes, component types, lifecycle management, and supported locales ## 2026-01-13 ### Changed - Updated Embedded Fields documentation with current integration details ## 2026-01-12 ### Added - Field Configuration documentation for Embedded Fields ## 2025-11-10 ### Added - Support for `return_url_target` parameter in Paysafe redirect ## 2025-10-21 ### Added - API integration documentation for card tokenization and saved cards ## 2025-10-20 ### Added - Documentation for saving and using saved cards in HPP ### Changed - Updated tokenization requirements and `customer_id` behavior ## 2025-10-15 — Initial version ### Added - Initial documentation import