# Altitude Pay Payment Gateway Documentation > API documentation for Altitude Pay Payment Gateway — card payments, 3D Secure, Google Pay, payouts, webhooks, and more. This file contains the complete documentation for AI consumption. --- ## Source: Get started › Overview URL: https://docs.altitudepay.solutions/index.html # Make your first request to the Altitude Pay API Exchange your client credentials for an access token, build and test against UAT, then switch to production when you go live. - UAT: OIDC Token URL https://uat-auth.altitudepay.solutions/realms/qore-uat/protocol/openid-connect/token, API URL https://uat-api.altitudepay.solutions - Production: OIDC Token URL https://auth.altitudepay.solutions/realms/qore-prod/protocol/openid-connect/token, API URL https://api.altitudepay.solutions ## Get an access token 1. **Find your credentials** Your client ID and client secret are under Merchant/API details. 2. **Request a token** POST to the OIDC Token URL with `grant_type=client_credentials`, your `client_id` and `client_secret`. 3. **Call the API** Send the token in the `Authorization` header as `Bearer ` to the API URL for your environment. 4. **Refresh the token** Tokens last 300 seconds on UAT and 60 seconds in production. Read `expires_in` from each response and request a new token shortly before it runs out. === "cURL" ```bash title="POST /token" curl -X POST \ https://uat-auth.altitudepay.solutions/realms/qore-uat/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 ``` ```json title="Response · 200 OK" { "access_token": "eyJz93a...k4laUWw", "token_type": "Bearer", "refresh_token": "string", "expires_in": 300 } ``` === "Node.js" ```javascript title="POST /token" const res = await fetch('https://uat-auth.altitudepay.solutions/realms/qore-uat/protocol/openid-connect/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'client_credentials', client_id: process.env.CLIENT_ID, client_secret: process.env.CLIENT_SECRET, }), }); const { access_token, expires_in } = await res.json(); ``` ```json title="Response · 200 OK" { "access_token": "eyJz93a...k4laUWw", "token_type": "Bearer", "refresh_token": "string", "expires_in": 300 } ``` ## What you need 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. To start integrating with Payments Platform you will need: | Property | Description | | --------------- | ------------------------------------------------------------------------ | | `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 | !!! 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. ## Choose how to integrate All three integrations use the same access token. Open one to see its guide. **Hosted Payment Page** Redirect customers to a checkout hosted by Altitude Pay. PCI DSS scope**Lowest** [Open guide](integration-types/hosted-payment-page.md) **Embedded Fields** Secure card fields inside your own checkout page. PCI DSS scope**Reduced** [Open guide](integration-types/embedded-fields.md) **API Integration** Build the checkout yourself and call the API directly. PCI DSS scope**Full** [Open guide](integration-types/api-integration.md) ## Go further **API Integration Guidelines** Conventions to follow in every request. [Read guidelines](getting-started/api-integration-guidelines.md) **AI-Assisted Integration** Build your integration with an AI coding assistant. [Read guide](getting-started/ai-assisted-integration.md) **Changelog** Latest changes to the API and platform. [View changelog](changelog.md) --- ## Source: Get started › Environments URL: https://docs.altitudepay.solutions/getting-started/environments.html # Find the OIDC token and API URLs for UAT and production Each environment has its own OIDC Token URL and API URL. These values are referenced throughout the documentation. - UAT: OIDC Token URL https://uat-auth.altitudepay.solutions/realms/qore-uat/protocol/openid-connect/token, API URL https://uat-api.altitudepay.solutions - Production: OIDC Token URL https://auth.altitudepay.solutions/realms/qore-prod/protocol/openid-connect/token, API URL https://api.altitudepay.solutions The table below outlines commonly used configuration values for integrating API. These values will be referenced throughout the document. #### UAT | Value Type | Value | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | OIDC Token URL | [https://uat-auth.altitudepay.solutions/realms/qore-uat/protocol/openid-connect/token](https://uat-auth.altitudepay.solutions/realms/qore-uat/protocol/openid-connect/token) | | API URL | [https://uat-api.altitudepay.solutions](https://uat-api.altitudepay.solutions) | #### Production | Value Type | Value | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | OIDC Token URL | [https://auth.altitudepay.solutions/realms/qore-prod/protocol/openid-connect/token](https://auth.altitudepay.solutions/realms/qore-prod/protocol/openid-connect/token) | | API URL | [https://api.altitudepay.solutions](https://api.altitudepay.solutions) | --- ## Source: Get started › API Integration Guidelines URL: https://docs.altitudepay.solutions/getting-started/api-integration-guidelines.html # Keep your API requests from being blocked at the edge Send a `User-Agent` header, use HTTPS only and send only the documented fields. A 403 with an empty body means the edge firewall blocked the request before it reached the API. How it works: 1. **Your client** — Sends User-Agent · HTTPS · documented fields 2. **Edge firewall** — Blocks with 403 and an empty body 3. **Altitude Pay API** — Auth failures return a JSON error body Follow these guidelines when integrating with the Payment Gateway API. They reflect common HTTP and security best practices and help your requests pass through our edge protections without being rejected. ## Required and recommended request hygiene - **Always send a `User-Agent` header** identifying your client (for example, `MyShop/1.4 (+https://myshop.example)`). Requests without a `User-Agent`, or with an empty one, may be rejected at the edge before reaching the API. Most HTTP clients send a default `User-Agent` automatically — make sure it is not stripped or overridden with an empty value. - **Use HTTPS only.** Plain HTTP requests are not accepted. - **Send a correct `Content-Type`.** For JSON payloads use `application/json; charset=utf-8`. - **Send a sensible `Accept` header**, typically `application/json`. - **Use a standard, up-to-date HTTP client.** Avoid hand-crafted raw sockets and keep TLS settings current. Do not disable default request headers your client sets for you. - **Keep request bodies and query strings clean.** Send only the fields documented for each endpoint. Avoid embedding HTML, scripts, SQL fragments, or path traversal sequences (`../`) inside field values — even when they appear in free-text fields such as descriptions or names. URL-encode values placed in the query string. - **Do not send unusually large payloads.** Stick to the documented request schema; do not attach unrelated binary data. - **Authenticate every request.** See [Authentication](authentication/index.md) for how to obtain and use access tokens. ## Troubleshooting blocked requests If a request is rejected with **HTTP 403 and an empty response body**, it was blocked by our edge firewall before reaching the API (a normal API authorization failure returns a JSON error body instead). The most common cause is a missing `User-Agent` header. Review the guidelines above first. If your request already follows all of the recommendations and you still receive a 403 with an empty body, contact support with: - The HTTP method and full request path - The approximate timestamp (with timezone) of the request - The `User-Agent` your client sent - The source IP address you were calling from This information lets us correlate the request on our side and help unblock you quickly. --- ## Source: Get started › AI-Assisted Integration URL: https://docs.altitudepay.solutions/getting-started/ai-assisted-integration.html # Give your AI coding assistant the complete Altitude Pay docs Paste the URL of `llms.txt`, a structured index of every page, or `llms-full.txt`, the complete docs in one plain-text file, into your AI coding tool. 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.altitudepay.solutions/llms.txt](https://docs.altitudepay.solutions/llms.txt) | Structured index of all documentation pages — ideal for giving your AI assistant a quick overview | | `llms-full.txt` | [https://docs.altitudepay.solutions/llms-full.txt](https://docs.altitudepay.solutions/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: https://docs.altitudepay.solutions/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: https://docs.altitudepay.solutions/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: Get started › Authentication › Overview URL: https://docs.altitudepay.solutions/getting-started/authentication/ # Authenticate every request with a Bearer token Get a token through the OIDC client credentials flow and send it in the `Authorization` header. Your `client_id` and `client_secret` are in the dashboard under Merchant details. 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: Get started › Authentication › Obtain/Refresh Access Token URL: https://docs.altitudepay.solutions/getting-started/authentication/obtain-access-token.html # Exchange your client credentials for an access token Post your `client_id` and `client_secret` with `grant_type=client_credentials` to the OpenID Connect `/token` endpoint. The response returns a Bearer `access_token` and its `expires_in`. [OAD(../../assets/openapi/keycloak.yaml)] --- ## Source: Integrate › Hosted Payment Page URL: https://docs.altitudepay.solutions/integration-types/hosted-payment-page.html # Accept card payments without touching card data Create a session on your server, redirect the customer to a checkout hosted by Altitude Pay, and bring them back to your success, cancel or error URL. How it works: 1. **Your checkout** — Built by you · creates the session 2. **Hosted Payment Page** — Hosted by Altitude Pay · collects card details (PCI DSS) 3. **Your redirect URLs** — Built by you · success, cancel or error ## What your customer sees The page keeps one layout for every merchant. The numbered parts change per merchant — try the controls on the right. Preview (illustrative, test data): Preview of the Hosted Payment Page: the merchant's logo, the order amount, wallet buttons, the card option, and a language menu. 1. **Your logo** — Upload it in the Merchant Portal. It replaces the Altitude Pay logo at the top of the page. 2. **Brand colors** — Set the colour scheme and font per merchant in the Merchant Portal. 3. **Wallets** — Apple Pay and Google Pay appear only when they are enabled on the merchant terminal. 4. **Payment methods** — Methods depend on the terminal configuration. Saved Cards appear when you pass a `customer_id` that has saved cards. 5. **Language** — English, Bosanski, Română or Svenska. Set a default per merchant, override it per session with `language`, and customers can switch on the page. ## Compare integrations How the three integrations differ. | | Hosted Payment Page | Embedded Fields | API Integration | | ---------------- | ----------------------------------- | --------------- | --------------- | | Checkout runs on | Altitude Pay | Your page | Your page | | PCI DSS scope | Lowest | Reduced | Full | | Customization | Logo, colours, language | CSS variables | Full UI control | | Effort | Lowest | Medium | Highest | ## Integrate in four steps 1. **Get an access token** Use the OIDC Client Credentials flow with your client ID and secret. 2. **Create a payment session** Send the order reference, amount in minor units, currency and your `success_url`, `cancel_url` and `error_url`. 3. **Redirect the customer** Send them to the `redirect_url` from the response. 4. **Confirm the result** The customer returns to one of your redirect URLs. Verify the final status before fulfilling the order. === "cURL" ```bash title="POST /api/hosted-payment-page" curl -X POST https://api.altitudepay.solutions/api/hosted-payment-page \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "reference": "ORDER-123456", "terminal_id": "TERM001", "description": "Order payment", "amount": 4990, "currency": "EUR", "transaction_type": "PURCHASE", "success_url": "https://shop.example/success", "cancel_url": "https://shop.example/cancel", "error_url": "https://shop.example/error" }' ``` === "Node.js" ```javascript title="POST /api/hosted-payment-page" const res = await fetch('https://api.altitudepay.solutions/api/hosted-payment-page', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ reference: 'ORDER-123456', terminal_id: 'TERM001', description: 'Order payment', amount: 4990, // minor units currency: 'EUR', transaction_type: 'PURCHASE', success_url: 'https://shop.example/success', cancel_url: 'https://shop.example/cancel', error_url: 'https://shop.example/error', }), }); const { redirect_url } = await res.json(); ``` ## 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, // the OIDC Token URL for your environment — see Getting Started > Environments { 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`. !!! note The example below posts to `/api/sessions`, which is the session-creation endpoint used by [Embedded Fields](./embedded-fields.md) and accepts a `return_url` plus a `customer` object. It is a different endpoint from `/api/hosted-payment-page`, documented under [Endpoint](#endpoint) below, which the rest of this page describes. Use `/api/hosted-payment-page` for Hosted Payment Page integrations. ```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": "PURCHASE", "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", "success_url": "https://merchant.example.com/success", "cancel_url": "https://merchant.example.com/cancel", "error_url": "https://merchant.example.com/error", "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", "language": "en", "originator_url": "https://shop.merchant.com/checkout", "options": { "number_of_instalments": 1, "skip_three_ds": false, "cvv_less": false }, "tokenization": { "save_card_for_future_payments": true } } ``` ### Required Fields | Field | Type | Description | | ------------------ | --------- | -------------------------------------------- | | `reference` | `string` | Unique reference for the order. Maximum 40 characters. | | `terminal_id` | `string` | ID of the terminal used for payment | | `description` | `string` | Payment description shown on HPP. 1–100 characters. | | `currency` | `string` | Currency in ISO 4217 format (e.g., "EUR") | | `amount` | `integer` | Amount in minor units (e.g., 100 = €1.00). Must be an integer — decimal or fractional values (e.g., `1.00` or `"1.00"`) are rejected at request validation. | | `transaction_type` | `string` | Must be `"PURCHASE"` or `"AUTHORIZE"` | | `success_url` | `string` | Redirect URL after a successful payment | | `cancel_url` | `string` | Redirect URL after the customer cancels the payment | | `error_url` | `string` | Redirect URL after a failed or errored payment | ### Optional Fields | Field | Type | Description | | --------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------- | | `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 in E.164 format (e.g., `+12125551234`). Validated using Google libphonenumber `isPossibleNumber` — the number must be a plausible length for its country code. | | `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 | | `language` | `string` | ISO code for the HPP's initial locale. See [Supported Languages](#supported-languages). | | `originator_url` | `string` | URL of the website or page on which the customer started the purchase. `http`/`https` only, a `#` fragment is allowed. If omitted or empty, the terminal website is used. Kept when the transaction is retried or routed to another provider, and forwarded to providers that require it. | | `options.number_of_instalments` | `integer` | Number of instalments offered for the payment, where supported | | `options.skip_three_ds` | `boolean` | If `true`, skips 3-D Secure authentication for this payment | | `options.cvv_less` | `boolean` | If `true`, allows payment without a CVV | | `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", "description": "Order payment", "currency": "EUR", "amount": 100, "transaction_type": "PURCHASE", "success_url": "https://merchant.example.com/success", "cancel_url": "https://merchant.example.com/cancel", "error_url": "https://merchant.example.com/error", "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", "description": "Order payment", "currency": "EUR", "amount": 100, "transaction_type": "PURCHASE", "success_url": "https://merchant.example.com/success", "cancel_url": "https://merchant.example.com/cancel", "error_url": "https://merchant.example.com/error", "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", "description": "Order payment", "currency": "EUR", "amount": 5000, "transaction_type": "PURCHASE", "success_url": "https://merchant.example.com/success", "cancel_url": "https://merchant.example.com/cancel", "error_url": "https://merchant.example.com/error", "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: - On a successful payment, the customer is redirected to `success_url`. - If the customer cancels the payment, they are redirected to `cancel_url`. - If the payment fails or errors out, the customer is redirected to `error_url`. All three URLs are required when creating the HPP session. *** ## 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 URLs**: Always provide `success_url`, `cancel_url`, and `error_url` to handle the post-payment redirects. *** ## 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. Available customizations include your logo, font, colour scheme and the display language (see [Supported Languages](#supported-languages)). Contact your account manager for details on available customization options. *** ## Supported Languages The Hosted Payment Page is available in the following languages: | Code | Language | Displayed as | | ---- | ------------ | ------------ | | `en` | English | English | | `bs` | Bosnian | Bosanski | | `ro` | Romanian | Română | | `sv` | Swedish | Svenska | ### Setting the Default Language The default language is configured **per merchant** in the Merchant Portal dashboard. Once set, every Hosted Payment Page created for that merchant opens in that language unless overridden per session. To set it, go to **Accounts → Merchants** in the Merchant Portal, open the merchant, and select the **Language** in the **Hosted Payment Page customizations** section. It sits alongside the other Hosted Payment Page customizations for that merchant — logo, font family, font URL and colours. If no language is configured, the page opens in English (`en`). ### Setting the Language per Session The optional `language` field in the session request body sets the HPP's initial locale for that session, overriding the merchant-level default. It accepts one of the language codes listed above. ### Customer Language Selection Customers can change the language themselves using the language selector on the payment page. All supported languages are offered, and each one is listed in its own language (for example **Svenska** for Swedish), so customers can find their language regardless of which one the page is currently displaying. If a language that is not supported is configured, the page falls back to English rather than failing to load. !!! note If a language you need is not listed in the **Language** dropdown, contact your account manager to have it enabled for your account. *** ## 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", "description": "Order payment", "currency": "EUR", "amount": 100, "transaction_type": "PURCHASE", "success_url": "https://merchant.example.com/success", "cancel_url": "https://merchant.example.com/cancel", "error_url": "https://merchant.example.com/error", "customer_id": "CUST123456", "tokenization": { "save_card_for_future_payments": true } }' ``` ## Go further **Save cards for future payments** Let customers opt in, or always save the card. [Read guide](#saving-cards-for-future-payments) **3DS card payment** A complete recipe with challenge flow. [Open recipe](../recipes/3ds-card-payment.md) **Webhooks** Get notified when a payment changes status. [View reference](../references/webhooks/index.md) --- ## Source: Integrate › Embedded Fields URL: https://docs.altitudepay.solutions/integration-types/embedded-fields.html # Keep customers on your checkout page while Altitude Pay captures card data Embed secure payment fields directly into your website. Card number, expiry and CVV are captured in a secure iframe, so card data never touches your servers. How it works: 1. **Your backend** — Built by you · creates the session - Your checkout page (Built by you): 2. **Embedded Fields** — Secure iframe by Altitude Pay · card number, expiry, CVV (PCI DSS) 3. **Your Pay button** — Submits the payment · result in a callback ## What your customer sees Embedded Fields render inside your checkout page. Card number, expiry and CVV are always shown; cardholder name and email are optional. Preview (illustrative, test data): Preview of a merchant checkout page with Embedded Fields: secure card fields from the gateway inside the merchant's own page, an optional billing form and the merchant's Pay button. 1. **Card fields** — Card number, expiry date and CVV/CVC are always visible and required. They are captured in a secure iframe, so card data never touches your servers. 2. **Cardholder name and email** — Hidden by default. Show them with `hasHolderName` and `hasHolderEmail`, and make them required if you need to. 3. **Billing details** — Collect the address on your own form, then pass it in the session `customer` object or in `submitPayment()`. 4. **Styling** — Match your brand by overriding CSS variables such as `--lib-accent-color` and `--lib-input-border-radius`. 5. **Your Pay button** — Your button calls `validate()` and then `submitPayment()`. The callback tells you whether the payment was approved. ## Compare integrations How the three integrations differ. | | Hosted Payment Page | Embedded Fields | API Integration | | ---------------- | ----------------------------------- | --------------- | --------------- | | Checkout runs on | Altitude Pay | Your page | Your page | | PCI DSS scope | Lowest | Reduced | Full | | Customization | Logo, colours, language | CSS variables | Full UI control | | Effort | Lowest | Medium | Highest | ## Integrate in four steps 1. **Get an access token** Use the OIDC Client Credentials flow with your client ID and secret. 2. **Create a session** Call `POST /api/sessions` from your backend. The `id` in the response is your session ID. 3. **Mount the fields** Load `embedded-fields.js`, initialize it with your client key and session ID, and mount the card component. 4. **Submit the payment** Call `validate()`, then `submitPayment()`. The callback returns the result. === "JavaScript" ```javascript title="checkout.js · frontend" const payment = await window.PaymentComponent({ environment: 'test', clientKey: CLIENT_KEY, sessionId: sessionId, }); const card = payment.create('card', { componentConfig: { customerConfig: { hasHolderName: true }, }, }); card.mount('#card-wrapper'); payButton.onclick = () => { if (!card.validate()) return; card.submitPayment({ callback: (response) => { if (response?.approved) location.href = '/success'; }, }); }; ``` ## 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.altitudepay.solutions/realms/qore-uat/protocol/openid-connect/token ``` **Production Endpoint:** ``` POST https://auth.altitudepay.solutions/realms/qore-prod/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.altitudepay.solutions/api/sessions ``` **Production Endpoint:** ``` POST https://api.altitudepay.solutions/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": "PURCHASE", "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": "PURCHASE" } ``` 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). Must be an integer — decimal or fractional values (e.g., `256.00` or `"256.00"`) are rejected at request validation. | | `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 | | `originator_url` | string | No | URL of the website or page on which the customer started the purchase. `http`/`https` only, a `#` fragment is allowed. If omitted or empty, the terminal website is used. Kept when the transaction is retried or routed to another provider, and forwarded to providers that require it. | *** ## 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 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; }; ``` #### Initialization Options | Option | Type | Required | Description | |:-------|:-----|:--------:|:------------| | `clientKey` | string | Yes | Public client key linked to your account | | `sessionId` | string | Yes | Session identifier returned by the session request | | `environment` | string | Yes | `'test'` or `'production'` | | `locale` | string | No | Language the UI is rendered in. See [Supported Locales](#supported-locales). If omitted, the customer's browser language is used when it is one of the supported locales; otherwise English is used. | ### 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 | | `'ro-RO'` | Romanian | | `'sv-SE'` | Swedish | Set the initial locale with the `locale` option when initializing the payment component, and call `changeLocale(locale)` to switch language after the component has been mounted. !!! 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 in E.164 format (e.g., `+12125551234`). Validated using Google libphonenumber `isPossibleNumber` — the number must be a plausible length for its country code. | | `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. ## Go further **Save cards for future payments** Show a returning customer's saved card with the saved-card component. [Read guide](#saved-card-component) **3DS card payment** A complete recipe with challenge flow. [Open recipe](../recipes/3ds-card-payment.md) **Webhooks** Get notified when a payment changes status. [View reference](../references/webhooks/index.md) --- ## Source: Integrate › API Integration URL: https://docs.altitudepay.solutions/integration-types/api-integration.html # Build the entire checkout and call the API directly Process payments with RESTful endpoints and keep full control over the payment flow and UI. Card data is encrypted with your merchant public key before it is sent. How it works: 1. **Your checkout page** — Built by you · collects card details 2. **Your backend** — Built by you · encrypts the card data (in your PCI scope) 3. **Altitude Pay API** — `POST /api/transactions/authorize` (PCI DSS) ## What your customer sees Everything the customer sees is yours. Altitude Pay renders no UI; your backend encrypts the card details and sends them to the API. Preview (illustrative, test data): Preview of a checkout page the merchant builds entirely: the card form and the Pay button are the merchant's own. ```json title="Your server → POST /api/transactions/authorize" { "amount": 4990, "currency": "EUR", "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" } }, "return_url": "https://shop.example/return" } ``` 1. **Your card form** — You build and host every field, including card number and CVV. 2. **Encrypted request** — Your backend encrypts the card number, CVV and expiry with your merchant public key before sending them. Your integration must meet PCI DSS requirements. 3. **3-D Secure and result** — Send `browser_info` and a `return_url` for 3-D Secure, then check `status` and `approved` in the response. ## Compare integrations How the three integrations differ. | | Hosted Payment Page | Embedded Fields | API Integration | | ---------------- | ----------------------------------- | --------------- | --------------- | | Checkout runs on | Altitude Pay | Your page | Your page | | PCI DSS scope | Lowest | Reduced | Full | | Customization | Logo, colours, language | CSS variables | Full UI control | | Effort | Lowest | Medium | Highest | ## Integrate in four steps 1. **Get an access token** Use the OIDC Client Credentials flow with your client ID and secret. 2. **Encrypt the card data** Encrypt card number, CVV, expiration month and year with your merchant public key. 3. **Authorize the payment** Send the encrypted card, `browser_info` and a `return_url` to `/api/transactions/authorize`. 4. **Handle the result** Check `status` and `approved`, complete 3-D Secure when required, and use webhooks for status updates. === "cURL" ```bash title="POST /api/transactions/authorize" curl -X POST https://api.altitudepay.solutions/api/transactions/authorize \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "reference": "ORDER-123456", "terminal_id": "TERM001", "description": "Order payment", "amount": 4990, "currency": "EUR", "transaction_type": "PURCHASE", "payment_method": { "type": "card", "data": { "encrypted_card_number": "..." } }, "browser_info": { "...": "..." }, "return_url": "https://shop.example/return" }' ``` === "Node.js" ```javascript title="POST /api/transactions/authorize" const res = await fetch('https://api.altitudepay.solutions/api/transactions/authorize', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ reference: 'ORDER-123456', terminal_id: 'TERM001', description: 'Order payment', amount: 4990, // minor units currency: 'EUR', transaction_type: 'PURCHASE', payment_method: { type: 'card', data: encryptedCard }, browser_info: browserInfo, return_url: 'https://shop.example/return', }), }); const result = await res.json(); ``` ## 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. ### Card Payment Data Fields | Field | Type | Required | Encrypted | Description | |-------|------|----------|-----------|-------------| | `encrypted_card_number` | `string` | Yes | Yes | Card PAN (digits only, no spaces) | | `encrypted_cvv` | `string` | Yes | Yes | Card security code (digits only) | | `encrypted_expiration_month` | `string` | Yes | Yes | Expiration month (1–12, no leading zero) | | `encrypted_expiration_year` | `string` | Yes | Yes | Expiration year — 2-digit (`26`) or 4-digit (`2026`); must be current or future year | ### Basic Request Example ```json { "reference": "ORDER-123456", "terminal_id": "TERM001", "description": "Order payment", "currency": "EUR", "amount": 10000, "transaction_type": "PURCHASE", "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": "PURCHASE", "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": "PURCHASE", "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, no leading zero) | | `encrypted_expiration_year` | `string` | Yes | Yes | Token expiration year — 2-digit (`26`) or 4-digit (`2026`); must be current or future year | | `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). *** ## Creating a Customer Card tokenization associates a saved card with a customer, so the customer must exist before you save a card against it (see [Saving Cards for Future Payments](#saving-cards-for-future-payments)). Create one with the Customers API and reference the returned `id` as `customer.id` in your transactions. ### Create a Customer `POST /api/customers` **Request Example:** ```json { "email": "john.doe@example.com", "first_name": "John", "last_name": "Doe", "phone": "+38970123456", "description": "Repeat customer", "address": { "country": "SI", "address": "123 Main St", "city": "Ljubljana", "postal_code": "1000", "region": "Osrednjeslovenska" } } ``` **Request Fields:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `email` | string | **Yes** | Valid email address (max 512). The only required field. | | `first_name` | string | No | Max 255. | | `last_name` | string | No | Max 255. | | `phone` | string | No | Contact phone number. | | `description` | string | No | Free-text label (max 255). | | `type` | string | No | Customer type. | | `address` | object | No | Nested address object (fields below). | | `address.country` | string | No | ISO 3166-1 alpha-2 country code (2 letters). | | `address.address` | string | No | Street address (max 255). | | `address.city` | string | No | Max 255. | | `address.postal_code` | string | No | Max 255. | | `address.region` | string | No | State/region (max 255). | **Response Example:** ```json { "id": "CUST123456", "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "phone": "+38970123456", "address": "123 Main St", "city": "Ljubljana", "country": "SI", "postal_code": "1000" } ``` The returned **`id`** is the customer identifier — pass it as `customer.id` in subsequent authorization/purchase requests to link saved cards to this customer. The response flattens the address into top-level `address`, `city`, `country`, and `postal_code`. *** ## 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 — see [Creating a Customer](#creating-a-customer) 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": "PURCHASE", "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 | | `tokenization.tokenize_for` | `string` | Optional. Intended use of the saved payment method: `CUSTOMER_INITIATED_TRANSACTION` (default when omitted) or `MERCHANT_INITIATED_TRANSACTION` | **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. ### Retrieving the Token via Webhook The synchronous response above returns the token in `payment_method.token`. If you rely on **webhooks** (for example, hosted-page or redirect flows), the token is delivered on a **dedicated event** — not on the transaction webhook: **Event:** `tokenization-webhook:payment-method:tokenized` ```json { "event": "tokenization-webhook:payment-method:tokenized", "data": { "payment_method_id": "abc123def456ghi789", "payment_method_type": "CARD", "card_brand": "VISA", "card_bin": "411111", "card_last_4": "1111", "card_masked": "411111******1111", "card_exp_month": 12, "card_exp_year": 2028, "customer_id": "CUST123456", "transaction_id": "TXN123456" } } ``` **Important:** `payment_method_token` is `null` on the `transaction-svc:transaction:purchase:approved` webhook — this is **expected, not an error**. The token is delivered only on the `tokenization-webhook:payment-method:tokenized` event. To receive it, your webhook subscription must include that event (webhook subscriptions are configured by Altitude Pay — contact support to enable it). *** ## 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, ... } } ``` *** ## Deleting a Saved Card A stored card can be removed at any time — when the customer asks for it, when they remove it themselves from a "saved cards" screen in your application, or when you retire it on your own. ### Endpoint ```http DELETE /api/payment-methods/{payment_method_id} ``` The `{payment_method_id}` is the same token returned when the card was saved (the `payment_method.token` field of the transaction response, or `payment_method_id` on the `tokenization-webhook:payment-method:tokenized` event). The request takes the same OAuth access token as the rest of the API, no request body, and — unlike saving a card — **no `Idempotency-Key` header**, because the call is already idempotent. Your API client needs the **`payment_method:detach`** permission. If the call returns `403`, contact support to have it enabled for your client. ### Request Example ```bash curl -X DELETE "{API_BASE_URL}/api/payment-methods/abc123def456ghi789" \ -H "Authorization: Bearer " ``` See [Environments](../getting-started/environments.md) for the API URL of your environment. ### Response Example `200 OK`, returning the payment method in its deleted state: ```json { "id": "abc123def456ghi789", "type": "CARD", "status": "DELETED", "customer_id": "", "email": "john.doe@example.com", "metadata": {}, "card": { "card_brand": "VISA", "exp_month": 12, "exp_year": 2030, "last_4": "1111", "bin": "411111" }, "created_at": "2026-01-15T10:30:00Z", "updated_at": "2026-02-01T09:12:44Z" } ``` ### What Deletion Does - **The card can no longer be charged.** Any transaction sent with `payment_method.type: "saved_card"` referencing a deleted payment method is rejected — see [Charging a Deleted Card](#charging-a-deleted-card). - **It is removed from the customer.** The customer association is cleared, so the card no longer appears among the saved payment methods offered for that customer. - **The record is kept.** `GET /api/payment-methods/{payment_method_id}` keeps returning the payment method with `status: "DELETED"`, so a card referenced by an older transaction can still be identified for reconciliation, disputes and audit. - **Deletion is final.** A deleted payment method cannot be reactivated or assigned to a customer again. To pay with the same card later, the customer has to enter it again and it is saved as a new payment method with a new id. The stored card data itself remains in the vault, encrypted, for as long as dispute handling and regulatory obligations require — it is simply no longer reachable for payments. If a customer requires the card data to be erased permanently (for example a GDPR erasure request), contact support: that is handled as a separate operation. ### Repeated Deletion Deleting is idempotent. Repeating the call for a payment method that is already deleted returns the same `200 OK` response and leaves the original deletion untouched, so a delete that timed out can safely be retried. ### Unknown or Foreign Payment Method A payment method id that does not exist — or that belongs to a different merchant — is reported as not found rather than as forbidden, so the response never reveals whether the id exists: ```json { "status": "INVALID", "errors": [ { "message": "Not found" } ] } ``` ### Response Codes | Status | Meaning | |--------|---------| | `200` | The payment method is deleted (or was already deleted) | | `403` | The API client does not hold the `payment_method:detach` permission | | `422` | Not found — unknown id, or a payment method belonging to another merchant | ### Implementing "Remove Card" in Your Application The call uses your merchant API credentials, so it must be made **from your backend**. A "remove card" action in a mobile or web application should call your own backend, which then calls the Payment Gateway: 1. The customer taps **Remove card** in your application. 2. Your application calls your backend with the payment method id it is displaying. 3. Your backend verifies that the payment method belongs to the signed-in customer, then calls `DELETE /api/payment-methods/{payment_method_id}`. 4. On `200`, your backend removes the card from the list it shows the customer. Never perform this call directly from a client application: it would require your merchant API credentials to be present in the application. The publishable client key used by Embedded Fields and the Hosted Payment Page cannot delete payment methods. *** ## 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. ### Charging a Deleted Card If a transaction references a payment method that has been deleted (or otherwise deactivated), the transaction is rejected before it reaches the payment provider: ```json { "status": "INVALID", "errors": [ { "property": "payment_method.data.payment_method_id", "message": "INVALID_REQUEST_PAYMENT_METHOD_INACTIVE" } ] } ``` **Solution**: The card is no longer available for payments and cannot be restored. Ask the customer to enter the card again, which saves it as a new payment method. You can confirm the state of a payment method with `GET /api/payment-methods/{payment_method_id}` — a deleted one reports `status: "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 ### Workflow 3: Customer Removes a Saved Card 1. **Customer requests removal** of a card in your application 2. **Verify ownership** in your backend — the payment method must belong to the signed-in customer 3. **Call** `DELETE /api/payment-methods/{payment_method_id}` with your merchant API credentials 4. **Remove the card** from the list shown to the customer on `200`; the payment method stays retrievable with `status: "DELETED"` for your own reconciliation *** ## 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 - [Apple Pay](../references/apple-pay/index.md) - Apple Pay overview and the available integration methods - [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 ## Go further **Save cards for future payments** Let customers opt in, or always save the card. [Read guide](#saving-cards-for-future-payments) **3DS card payment** A complete recipe with challenge flow. [Open recipe](../recipes/3ds-card-payment.md) **Webhooks** Get notified when a payment changes status. [View reference](../references/webhooks/index.md) --- ## Source: Integrate › Recipes › 3DS Card Payment URL: https://docs.altitudepay.solutions/recipes/3ds-card-payment.html # Take a card payment through the full 3D Secure flow Encrypt the card on your server, call `/api/transactions/authorize`, and route each response through device fingerprint and challenge until a result comes back. > **Server-side only.** Card encryption must happen on your server. Never expose the RSA public key or raw card data in the browser. How it works: 1. **Your server** — Gets a token · encrypts the card data 2. **Altitude Pay** — `POST /api/transactions/authorize` 3. **Issuer ACS** — Device fingerprint in the browser 4. **Altitude Pay** — `POST /api/transactions/three-ds/{id}/request-authentication` 5. **Issuer ACS** — Challenge in the browser, if required 6. **Your server** — `GET /api/transactions/{transaction_id}/status` 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. !!! tip "Advanced: terminate the ACS callback on your own backend" If you need to run server-side code the moment the challenge finishes (e.g. custom logging, order updates), you can override the ACS notification URL by passing `options.three_ds.notification_url` in the authorize request. When you do, **you** become responsible for invoking `POST /api/transactions/three-ds/authentication-completed` so the gateway can finalize the authorization. See [Challenge Notification URL](../references/3ds/index.md#challenge-notification-url) for the full contract, validation rules, and required follow-up. *** ## 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: Step 3: 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 /api/transactions/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 The **default and recommended** way to test 3DS in UAT is the [Simplified Authentication Simulator](../references/3ds/simplified-simulator.md): send one of its [test card numbers](../references/3ds/simplified-simulator.md#test-cards) and the gateway resolves a deterministic outcome (frictionless, redirect, form-submit, or decline) with **no metadata required**. For example, encrypt `4000000000000002` for a frictionless approval or `4000000000000010` to be redirected to an interactive approve/decline page. ### Advanced: metadata-driven flows If you need to exercise the full EMVCo fingerprint + challenge sequence, you can instead control the 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: Payment methods › Alternative › Overview URL: https://docs.altitudepay.solutions/references/payments/ # Collect funds with Open Banking, UPI, wallets, bank transfer and QR codes Send one request to `/api/transactions/payment` with the method as a string. No card data, encryption or `browser_info` is required. How it works: 1. **Your checkout** — Customer selects a payment method 2. **Altitude Pay** — `POST /api/transactions/payment` 3. **Provider page** — Bank login, wallet app or QR scan 4. **Your webhook endpoint** — Receives the final status ## Payment methods Pick a category, then jump to a method to see how it works, its request fields and an example. ### Open Banking Bank-initiated payment via Open Banking APIs. Send one request to `/api/transactions/payment` with `method` set to `OPEN_BANKING`. Most methods redirect the customer to the provider, and the final status arrives by webhook. - method: `OPEN_BANKING` - Endpoint: `POST /api/transactions/payment` - Optional fields: `description`, `customer`, `country`, `customer_ip`, `metadata`, `return_url`, `options`, `originator_url` #### Required fields | Field | Description | | ----- | ----------- | | `terminal_id` | Must support payment transactions (`TRX_TYPE_PAYMENT`). | | `reference` | Unique per terminal, max 80 characters. Idempotent: the same reference with the same amount and currency returns the existing transaction. | | `method` | One of `OPEN_BANKING`, `UPI`, `WALLETS`, `BANK_TRANSFER` or `QR_CODE`. | | `currency` | ISO 4217 currency code. | | `amount` | Integer in minor units, e.g. `10000` = 100.00. Decimal values are rejected. | [Payments reference](#payment-method) [Direct API Integration](direct-api-integration.md) === "cURL" ```bash title="POST /api/transactions/payment" curl -X POST https://api.altitudepay.solutions/api/transactions/payment \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "terminal_id": "TERM001", "reference": "ORDER-123456", "method": "OPEN_BANKING", "amount": 10000, "currency": "EUR", "return_url": "https://shop.example/return" }' ``` === "Node.js" ```javascript title="POST /api/transactions/payment" const res = await fetch('https://api.altitudepay.solutions/api/transactions/payment', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ terminal_id: 'TERM001', reference: 'ORDER-123456', method: 'OPEN_BANKING', amount: 10000, // minor units currency: 'EUR', return_url: 'https://shop.example/return', }), }); const { status, redirect } = await res.json(); // PENDING: send the customer to the provider via redirect ``` ### UPI Unified Payments Interface (India). Send one request to `/api/transactions/payment` with `method` set to `UPI`. Most methods redirect the customer to the provider, and the final status arrives by webhook. - method: `UPI` - Endpoint: `POST /api/transactions/payment` - Optional fields: `description`, `customer`, `country`, `customer_ip`, `metadata`, `return_url`, `options`, `originator_url` #### Required fields | Field | Description | | ----- | ----------- | | `terminal_id` | Must support payment transactions (`TRX_TYPE_PAYMENT`). | | `reference` | Unique per terminal, max 80 characters. Idempotent: the same reference with the same amount and currency returns the existing transaction. | | `method` | One of `OPEN_BANKING`, `UPI`, `WALLETS`, `BANK_TRANSFER` or `QR_CODE`. | | `currency` | ISO 4217 currency code. | | `amount` | Integer in minor units, e.g. `10000` = 100.00. Decimal values are rejected. | [Payments reference](#payment-method) [Direct API Integration](direct-api-integration.md) === "cURL" ```bash title="POST /api/transactions/payment" curl -X POST https://api.altitudepay.solutions/api/transactions/payment \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "terminal_id": "TERM001", "reference": "ORDER-123456", "method": "UPI", "amount": 10000, "currency": "INR", "return_url": "https://shop.example/return" }' ``` === "Node.js" ```javascript title="POST /api/transactions/payment" const res = await fetch('https://api.altitudepay.solutions/api/transactions/payment', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ terminal_id: 'TERM001', reference: 'ORDER-123456', method: 'UPI', amount: 10000, // minor units currency: 'INR', return_url: 'https://shop.example/return', }), }); const { status, redirect } = await res.json(); // PENDING: send the customer to the provider via redirect ``` ### E-wallets Digital wallet payments. Send one request to `/api/transactions/payment` with `method` set to `WALLETS`. Most methods redirect the customer to the provider, and the final status arrives by webhook. - method: `WALLETS` - Endpoint: `POST /api/transactions/payment` - Optional fields: `description`, `customer`, `country`, `customer_ip`, `metadata`, `return_url`, `options`, `originator_url` #### Required fields | Field | Description | | ----- | ----------- | | `terminal_id` | Must support payment transactions (`TRX_TYPE_PAYMENT`). | | `reference` | Unique per terminal, max 80 characters. Idempotent: the same reference with the same amount and currency returns the existing transaction. | | `method` | One of `OPEN_BANKING`, `UPI`, `WALLETS`, `BANK_TRANSFER` or `QR_CODE`. | | `currency` | ISO 4217 currency code. | | `amount` | Integer in minor units, e.g. `10000` = 100.00. Decimal values are rejected. | [Payments reference](#payment-method) [Direct API Integration](direct-api-integration.md) === "cURL" ```bash title="POST /api/transactions/payment" curl -X POST https://api.altitudepay.solutions/api/transactions/payment \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "terminal_id": "TERM001", "reference": "ORDER-123456", "method": "WALLETS", "amount": 10000, "currency": "EUR", "return_url": "https://shop.example/return" }' ``` === "Node.js" ```javascript title="POST /api/transactions/payment" const res = await fetch('https://api.altitudepay.solutions/api/transactions/payment', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ terminal_id: 'TERM001', reference: 'ORDER-123456', method: 'WALLETS', amount: 10000, // minor units currency: 'EUR', return_url: 'https://shop.example/return', }), }); const { status, redirect } = await res.json(); // PENDING: send the customer to the provider via redirect ``` ### Bank Transfer Direct bank transfer. Send one request to `/api/transactions/payment` with `method` set to `BANK_TRANSFER`. Most methods redirect the customer to the provider, and the final status arrives by webhook. - method: `BANK_TRANSFER` - Endpoint: `POST /api/transactions/payment` - Optional fields: `description`, `customer`, `country`, `customer_ip`, `metadata`, `return_url`, `options`, `originator_url` #### Required fields | Field | Description | | ----- | ----------- | | `terminal_id` | Must support payment transactions (`TRX_TYPE_PAYMENT`). | | `reference` | Unique per terminal, max 80 characters. Idempotent: the same reference with the same amount and currency returns the existing transaction. | | `method` | One of `OPEN_BANKING`, `UPI`, `WALLETS`, `BANK_TRANSFER` or `QR_CODE`. | | `currency` | ISO 4217 currency code. | | `amount` | Integer in minor units, e.g. `10000` = 100.00. Decimal values are rejected. | [Payments reference](#payment-method) [Direct API Integration](direct-api-integration.md) === "cURL" ```bash title="POST /api/transactions/payment" curl -X POST https://api.altitudepay.solutions/api/transactions/payment \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "terminal_id": "TERM001", "reference": "ORDER-123456", "method": "BANK_TRANSFER", "amount": 10000, "currency": "EUR", "return_url": "https://shop.example/return" }' ``` === "Node.js" ```javascript title="POST /api/transactions/payment" const res = await fetch('https://api.altitudepay.solutions/api/transactions/payment', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ terminal_id: 'TERM001', reference: 'ORDER-123456', method: 'BANK_TRANSFER', amount: 10000, // minor units currency: 'EUR', return_url: 'https://shop.example/return', }), }); const { status, redirect } = await res.json(); // PENDING: send the customer to the provider via redirect ``` ### QR Code QR code-based payment. Send one request to `/api/transactions/payment` with `method` set to `QR_CODE`. Most methods redirect the customer to the provider, and the final status arrives by webhook. - method: `QR_CODE` - Endpoint: `POST /api/transactions/payment` - Optional fields: `description`, `customer`, `country`, `customer_ip`, `metadata`, `return_url`, `options`, `originator_url` #### Required fields | Field | Description | | ----- | ----------- | | `terminal_id` | Must support payment transactions (`TRX_TYPE_PAYMENT`). | | `reference` | Unique per terminal, max 80 characters. Idempotent: the same reference with the same amount and currency returns the existing transaction. | | `method` | One of `OPEN_BANKING`, `UPI`, `WALLETS`, `BANK_TRANSFER` or `QR_CODE`. | | `currency` | ISO 4217 currency code. | | `amount` | Integer in minor units, e.g. `10000` = 100.00. Decimal values are rejected. | [Payments reference](#payment-method) [Direct API Integration](direct-api-integration.md) === "cURL" ```bash title="POST /api/transactions/payment" curl -X POST https://api.altitudepay.solutions/api/transactions/payment \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "terminal_id": "TERM001", "reference": "ORDER-123456", "method": "QR_CODE", "amount": 10000, "currency": "THB", "return_url": "https://shop.example/return" }' ``` === "Node.js" ```javascript title="POST /api/transactions/payment" const res = await fetch('https://api.altitudepay.solutions/api/transactions/payment', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ terminal_id: 'TERM001', reference: 'ORDER-123456', method: 'QR_CODE', amount: 10000, // minor units currency: 'THB', return_url: 'https://shop.example/return', }), }); const { status, redirect } = await res.json(); // PENDING: send the customer to the provider via redirect ``` #### Payments (deposits) Alternative payment methods go through `/api/transactions/payment`. Most methods redirect the customer to the provider, and the final status arrives by webhook. [Payments reference](#processing-flow) ### Cards Process card payments through `/api/transactions/authorize`. All sensitive card data must be encrypted with your merchant public key before it is sent to the API. - payment_method.type: `card` - Encrypted fields: Card number, CVV, expiration month, expiration year - 3D Secure: Always initiated for `card` and `saved_card`. The authorize response returns an `action` (`THREE_DS_2_FINGERPRINT`, `THREE_DS_2_CHALLENGE`), a `redirect` or a `form_submit`. - Saved cards: Save with `tokenization.save_card_for_future_payments`, then pay with type `saved_card`. #### Card payment data fields | Field | Description | | ----- | ----------- | | `encrypted_card_number` | Card PAN (digits only, no spaces) | | `encrypted_cvv` | Card security code (digits only) | | `encrypted_expiration_month` | Expiration month (1–12, no leading zero) | | `encrypted_expiration_year` | 2-digit (`26`) or 4-digit (`2026`); must be current or future year | [Overview](../cards/index.md) [Authorize](../cards/authorize.md) [Encryption example](../cards/encryption-example.md) [3D Secure](../3ds/index.md) [3DS Authentication Simulator](../3ds/simplified-simulator.md) [3DS card payment recipe](../../recipes/3ds-card-payment.md) === "Basic request" ```json title="POST /api/transactions/authorize" { "reference": "ORDER-123456", "terminal_id": "TERM001", "description": "Order payment", "currency": "EUR", "amount": 10000, "transaction_type": "PURCHASE", "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": { ... }, "browser_info": { ... }, "metadata": {}, "return_url": "https://merchant.example.com/return" } ``` === "Saved card" ```json title="POST /api/transactions/authorize" { "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", ... }, "browser_info": { ... }, "metadata": {}, "return_url": "https://merchant.example.com/return" } ``` 3DS is always initiated for card payments. The `/api/transactions/authorize` response then guides you through up to two steps: device fingerprinting and challenge authentication. #### How it works 1. `THREE_DS_2_FINGERPRINT`: Decode `action.token`. If `three_ds_method_url` is present, submit `threeDSMethodData` to it in a hidden iframe and wait a fixed 5 seconds. 2. `POST /api/transactions/three-ds/{id}/request-authentication`: Send `fingerprint_result` (Base64 of `Y` if the fingerprint form was submitted, `N` if `three_ds_method_url` was absent) and pass through `action.payment_data`. 3. `THREE_DS_2_CHALLENGE`: Build a CReq from the decoded token and post it to `acs_url`. After the challenge, the gateway authorizes the transaction and redirects the customer back to your `return_url`. #### Handling the response Which field is populated determines your next step. The same logic applies after every call in the 3DS flow. | Response field | Meaning | Next step | | -------------- | ------- | --------- | | `result` | Transaction finalized (approved or declined) | Done | | `action` with type `THREE_DS_2_FINGERPRINT` | Device fingerprint required | Step 1 above | | `action` with type `THREE_DS_2_CHALLENGE` | Challenge required | Step 3 above | | `redirect` | Redirect the customer | Redirect the 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` | [3DS card payment recipe](../../recipes/3ds-card-payment.md) #### 3D Secure flow overview The `/api/transactions/authorize` response includes fields that guide you through up to two steps: device fingerprinting and challenge authentication. [3D Secure reference](../3ds/index.md#3ds-flow-overview) ```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 ``` #### Complete 3DS integration example A simplified example of the full 3DS flow for a card payment, with the frictionless, challenge and redirect outcomes. [3D Secure reference](../3ds/index.md#complete-integration-example) ```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 ``` UAT The default and recommended way to test 3DS in UAT: send a test card number and the gateway resolves a deterministic outcome, with no metadata required. #### Test cards | Scenario | Visa | Mastercard | | -------- | ---- | ---------- | | Frictionless, approved | `4000000000000002` | `5200000000000007` | | Redirect, interactive approve or decline | `4000000000000010` | `5200000000000015` | | Form submit, authenticated | `4000000000000028` | `5200000000000023` | | Form submit, authentication declined | `4000000000000036` | `5200000000000031` | | Frictionless authenticated, then processing decline | `4000000000000044` | `5200000000000049` | #### Metadata-driven flows To exercise the full EMVCo fingerprint and challenge sequence, set `metadata.simulated_flow` on the authorize request. `metadata.response_code` controls the final result: `TRX_STATUS_APPROVED`, `TRX_STATUS_DECLINED` or `TRX_STATUS_FAILED`. - Full flow: fingerprint + challenge (default): `VERSIONING_FINGERPRINT_Y_CHALLENGE` - Frictionless approval, no customer interaction: `VERSIONING_FINGERPRINT_A_FRICTIONLESS` - Frictionless decline: `VERSIONING_FINGERPRINT_N_FRICTIONLESS` - Card not enrolled in 3DS, skips authentication: `VERSIONING_NOT_ENROLLED` - Fingerprint times out, then challenge: `VERSIONING_FINGERPRINT_TIMEOUT_CHALLENGE` - Fingerprint times out, then frictionless: `VERSIONING_FINGERPRINT_TIMEOUT_FRICTIONLESS` ```json title="POST /api/transactions/authorize" { "metadata": { "simulated_flow": "VERSIONING_FINGERPRINT_Y_CHALLENGE", "response_code": "TRX_STATUS_APPROVED" } } ``` ```json title="POST /api/transactions/authorize" { "metadata": { "simulated_flow": "VERSIONING_FINGERPRINT_A_FRICTIONLESS", "response_code": "TRX_STATUS_APPROVED" } } ``` ```json title="POST /api/transactions/authorize" { "metadata": { "simulated_flow": "VERSIONING_FINGERPRINT_N_FRICTIONLESS", "response_code": "TRX_STATUS_APPROVED" } } ``` ```json title="POST /api/transactions/authorize" { "metadata": { "simulated_flow": "VERSIONING_NOT_ENROLLED", "response_code": "TRX_STATUS_APPROVED" } } ``` ```json title="POST /api/transactions/authorize" { "metadata": { "simulated_flow": "VERSIONING_FINGERPRINT_TIMEOUT_CHALLENGE", "response_code": "TRX_STATUS_APPROVED" } } ``` ```json title="POST /api/transactions/authorize" { "metadata": { "simulated_flow": "VERSIONING_FINGERPRINT_TIMEOUT_FRICTIONLESS", "response_code": "TRX_STATUS_APPROVED" } } ``` #### 3DS Authentication Simulator The test card number decides the outcome. It arrives in the same `result`, `action`, `redirect` and `form_submit` fields as a real 3DS transaction, so only the card number changes between the simulator and production. [Simulator reference](../3ds/simplified-simulator.md#how-it-works) ```mermaid sequenceDiagram participant Browser participant Server as Your Server participant Gateway as Payment Gateway Server ->> Gateway: POST /api/transactions/authorize (test card) Gateway ->> Gateway: Decrypt PAN → look up scenario alt Frictionless test card Gateway -->> Server: result (approved / declined) else Redirect test card Gateway -->> Server: redirect.url (gateway ACS page) Server -->> Browser: redirect browser to URL Browser ->> Gateway: Approve / Decline button Gateway -->> Browser: redirect to return_url else Form-submit test card Gateway -->> Server: form_submit (url + data) Server -->> Browser: auto-submit form Browser ->> Gateway: POST trans_status (Y / N) Gateway -->> Browser: redirect to return_url end ``` ### Network Token For merchants who decrypt wallet tokens on their own infrastructure. Send the resulting DPAN, cryptogram and ECI from Apple Pay, Google Pay or a Token Service Provider through the gateway. - payment_method.type: `network-token` - Encrypted: Token number (DPAN), expiration month, expiration year, cryptogram - Plain text: `eci`, `source` - 3D Secure: Skipped when a valid cryptogram and ECI are provided #### 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` | [Overview](../network-token/index.md) [Authorize](../network-token/authorize.md) === "Apple Pay" ```json title="POST /api/transactions/authorize" { "reference": "ORDER-NT-001", "terminal_id": "TERM001", "description": "Apple Pay payment", "currency": "EUR", "amount": 10000, "transaction_type": "PURCHASE", "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": { ... }, "browser_info": { ... }, "metadata": {}, "return_url": "https://merchant.example.com/return" } ``` === "Google Pay · PAN_ONLY" ```json title="POST /api/transactions/authorize" { "reference": "ORDER-NT-002", "terminal_id": "TERM001", "description": "Google Pay payment", "currency": "EUR", "amount": 5000, "transaction_type": "PURCHASE", "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 authorization You decrypt the wallet token and send the DPAN, cryptogram and ECI. 3DS is skipped when a valid cryptogram and ECI are provided. [Network Token reference](../network-token/index.md#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 ``` ### Google Pay Customers pay with the cards stored in their Google Account. Once your account manager enables Google Pay on your terminal, it appears on the hosted payment form alongside the other payment methods. With API integration, you send the Google Pay token to the gateway. - payment_method.type: `google-pay` - Token: `payment_method.data.google_pay_token`, Base64-encoded - 3D Secure: Initiated for `PAN_ONLY` tokens, skipped for `CRYPTOGRAM_3DS` tokens - Card networks: Visa, Mastercard #### Integration methods | Method | Description | Additional development | | ------ | ----------- | ---------------------- | | Hosted Payment Page | Google Pay is displayed as a payment option on the hosted payment form | None required | | API Integration | Merchant integrates directly with the Google Pay API and sends the payment token to the gateway | Yes — Google Pay API + gateway API | [Overview](../google-pay/index.md) [Request example (PAN_ONLY)](../../integration-types/api-integration.md#request-example-google-pay-pan_only) [Wallet Redirect](../wallet-redirect/index.md) === "API · google-pay" ```json title="POST /api/transactions/authorize" { "reference": "ORDER-GP-001", "terminal_id": "TERM001", "description": "Google Pay payment", "currency": "EUR", "amount": 10000, "transaction_type": "PURCHASE", "payment_method": { "type": "google-pay", "data": { "google_pay_token": "" } }, "customer": { ... }, "browser_info": { ... }, "return_url": "https://merchant.example.com/return" } ``` === "Network Token · PAN_ONLY" ```json title="POST /api/transactions/authorize" { "reference": "ORDER-NT-002", "terminal_id": "TERM001", "description": "Google Pay payment", "currency": "EUR", "amount": 5000, "transaction_type": "PURCHASE", "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" } ``` #### Google Pay on the Hosted Payment Page Once your account manager enables Google Pay, it appears on the hosted payment form alongside the other payment methods. [Google Pay reference](../google-pay/index.md#hosted-payment-page) ```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) ``` #### Google Pay via API integration Send the Base64-encoded Google Pay token as `payment_method.data.google_pay_token`. 3DS is initiated for `PAN_ONLY` tokens and skipped for `CRYPTOGRAM_3DS` tokens. [Google Pay reference](../google-pay/index.md#api-integration) ```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 ``` ### Apple Pay Apple Pay lets customers pay with the cards stored in their Apple Wallet on iPhone, iPad, Apple Watch and Mac. Authentication happens on the customer's device through Face ID, Touch ID or a passcode, so no card details are typed into your checkout. - Card brands: Visa, Mastercard, Maestro, American Express, Discover and JCB, where enabled on your terminal and supported by your payment provider - 3D Secure: Treated as `CRYPTOGRAM_3DS`. No additional 3DS challenge. - Activation: Request Apple Pay on your terminal from your account manager #### Integration methods | Method | Who decrypts the Apple Pay token | Additional development | |---|---|---| | Wallet Redirect (`apple-pay-redirect`) | The PSP — the gateway never sees the token | None to minimal | | Network Token (`network-token`) | You, on your own infrastructure | Yes — Apple Pay on the Web plus token decryption | [Overview](../apple-pay/index.md) [Request example (Apple Pay)](../../integration-types/api-integration.md#request-example-apple-pay) [Wallet Redirect](../wallet-redirect/index.md) [Network Token](../network-token/index.md) === "Network Token" ```json title="POST /api/transactions/authorize" { "payment_method": { "type": "network-token", "data": { "encrypted_token_number": "", "encrypted_expiration_month": "", "encrypted_expiration_year": "", "encrypted_cryptogram": "", "eci": "05", "source": "apple-pay" } } } ``` === "Wallet Redirect" ```json title="POST /api/transactions/authorize" { "payment_method": { "type": "apple-pay-redirect", "data": {} } } ``` ### Wallet Redirect Accept Google Pay and Apple Pay without integrating the wallet SDKs on your own site. The gateway returns a redirect URL to a PSP that renders the wallet button and authorizes the payment end to end, so the gateway never sees card data, the wallet token or the decrypted PAN. - payment_method.type: `google-pay-redirect` or `apple-pay-redirect` - payment_method.data: Empty object, `{}` - Transaction types: `PURCHASE` only #### Integration methods | Method | Effort | Notes | | ------ | ------ | ----- | | Hosted Payment Page | None | Google Pay / Apple Pay appear automatically on the HPP once enabled on your terminal | | Embedded Fields | Minimal | A stub script (`google-pay-redirect` / `apple-pay-redirect`) registers a factory; your page renders its own "Pay" button | | API Integration | Low | Send a single `authorize` request with `payment_method.type` set to `google-pay-redirect` or `apple-pay-redirect` and follow the returned redirect | [Overview](../wallet-redirect/index.md) === "Google Pay" ```json title="POST /api/transactions/authorize" { "payment_method": { "type": "google-pay-redirect", "data": {} } } ``` === "Apple Pay" ```json title="POST /api/transactions/authorize" { "payment_method": { "type": "apple-pay-redirect", "data": {} } } ``` ### Bank Redirect Bank Redirect lets you accept payments made directly from a customer's bank account. You select the payment method on your platform and the gateway returns a redirect URL to a PSP-hosted page where the customer signs in to their bank and approves the payment. - payment_method.type: `interac-redirect` or `openbanking-redirect` - 3D Secure: None. The customer's bank performs its own strong customer authentication. - Transaction types: `PAYMENT` only - Availability: Interac for customers banking in Canada; Open Banking coverage depends on your PSP and the customer's country [Overview](../bank-redirect/index.md) ### Paysafe Redirect Paysafe Redirect gives access to a wide range of alternative payment methods through a single integration. Customers are redirected to a secure environment to complete their payment with the method they selected. - payment_method.type: `paysafe-redirect-{code}`, all lowercase, for example `paysafe-redirect-psc` for PaySafeCard - Required data: `email`, `first_name`, `last_name` - Integration: Hosted Payment Page or Direct API [Overview](../paysafe-redirect/index.md) [Direct API Integration](../paysafe-redirect/direct-api-integration.md) [Customization Options](../paysafe-redirect/customization-options.md) ### Neogated (BLIK) 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. - payment_method.type: `neogated-blik` - Currency: PLN only - Transaction types: `PURCHASE` only - Required data: `email`, `first_name`, `last_name` [Overview](../neogated/index.md) [Direct API Integration](../neogated/direct-api-integration.md) ## Processing flows How requests move between your systems, Altitude Pay and third parties for each payment type. #### Payments (deposits) Alternative payment methods go through `/api/transactions/payment`. Most methods redirect the customer to the provider, and the final status arrives by webhook. [Payments reference](#processing-flow) #### OCT (Original Credit Transaction) A card payout pushes funds to a cardholder's card instead of pulling them. It is authorized with `transaction_type` `OCT` and needs no 3DS. [OCT reference](../oct/index.md#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 ``` ## Transaction statuses Payments are asynchronous. Wait for the webhook, then confirm the final status with the Transaction Status API. | 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 | Refunds depend on the payment provider and method. Capture and void do not apply to payments. For the full transaction lifecycle, see [Supported Transaction Statuses](../../transaction-api/supported-transaction-statuses.md). ## 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). *** ### 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, as an integer (e.g., `10000` = 100.00 EUR). Decimal or fractional values such as `100.00` or `"100.00"` are rejected at request validation. | | `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 | | `originator_url` | No | URL of the website on which the customer started the purchase (defaults to the terminal website). Kept on retries and forwarded to providers that require it | *** ### 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 | *** ### 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: Payment methods › Alternative › Direct API Integration URL: https://docs.altitudepay.solutions/references/payments/direct-api-integration.html # Initiate alternative payments directly from your platform Send the customer’s chosen `method` to `/api/transactions/payment` and redirect them to the provider when the response contains a `redirect`. Webhooks deliver the final status. How it works: 1. **Your platform** — Presents the payment methods 2. **Altitude Pay** — `POST /api/transactions/payment` 3. **Provider page** — Customer completes or cancels the payment 4. **Your webhook endpoint** — Receives the final status ## 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` | enum | Yes | Payment method type. One of `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, as an integer (e.g., `10000` = 100.00). Decimal or fractional values such as `100.00` or `"100.00"` are rejected at request validation. | | `description` | string | Yes | Human-readable description (1–100 chars) | | `country` | string | Yes | ISO 3166-1 alpha-2 country code (e.g., `TH`, `IN`) | | `customer_ip` | string | Yes | Customer IP address | | `return_url` | string | Yes | Redirect URL after a successful payment | | `error_url` | string | Yes | Redirect URL after a failed payment | | `cancel_url` | string | Yes | Redirect URL after a canceled payment | | `customer` | object | No | Customer details | | `metadata` | object | No | Arbitrary key-value metadata | | `options` | object | No | Provider-specific options | | `originator_url` | string | No | URL of the website or page on which the customer started the purchase. `http`/`https` only, a `#` fragment is allowed. If omitted or empty, the terminal website is used. Kept when the transaction is retried or routed to another provider, and forwarded to providers that require it. | *** ### 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", "error_url": "https://merchant.example.com/error", "cancel_url": "https://merchant.example.com/cancel", "originator_url": "https://shop.merchant.com/checkout" } ``` > The `amount` is in **minor units** and must be an integer. For EUR, `10000` represents **100.00 EUR**. Decimal or fractional values (e.g., `100.00` or `"100.00"`) are rejected at request validation. *** ### 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: Payment methods › Cards › Cards URL: https://docs.altitudepay.solutions/references/cards/ # Authorize card payments and test outcomes with the card simulator Encrypt card data with your merchant public key and run the transaction against a `terminal_id`. When your account uses the card-simulator provider, `metadata` fields set the outcome. Simulator metadata: - `response_code` → `result`: Processing code to return - `simulate_redirect` → `redirect`: To your URL - `simulate_form_submit` → `form_submit`: Your URL and form data - `simulate_authentication_required` → `action`: Action required With no metadata fields set, the simulator returns TRX_STATUS_APPROVED. ### 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: Payment methods › Cards › Card Authorize URL: https://docs.altitudepay.solutions/references/cards/authorize.html # Authorize a card transaction with encrypted card details Call `/api/transactions/authorize` with a Bearer token and card fields encrypted with your public key. Set `payment_method.type` to `card`, `saved_card` or `network-token`. How it works: 1. **Your backend** — Encrypts the card fields with your public key 2. **Altitude Pay API** — `POST /api/transactions/authorize` 3. **Your backend** — Handles result, action, redirect or form_submit [OAD(../../assets/openapi/openapi-transaction-authorize.yaml)] --- ## Source: Payment methods › Cards › Encryption example URL: https://docs.altitudepay.solutions/references/cards/encryption-example.html # Encrypt card fields with your RSA public key before you send them Encrypt the card number, CVV, expiration month and expiration year one by one with RSA-OAEP (SHA-256), then Base64-encode each result. Your RSA public key is under Merchant details in your merchant dashboard. > **Server-side only.** Card encryption must happen on your server. Never expose the RSA public key or raw card data in the browser. How it works: 1. **Merchant details** — Your RSA public key in the merchant dashboard 2. **Your server** — Encrypts each field · RSA-OAEP (SHA-256) 3. **Altitude Pay API** — Receives the four Base64 values in the authorize request 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": "" } } } ``` ## Field Format Requirements Encrypt the plain-text value before Base64-encoding. The table below shows the expected plain-text format for each field. | Field | Plain-text format | Example values | Notes | |-------|-------------------|----------------|-------| | `encrypted_card_number` | Digits only, no spaces | `4111111111111111` | | | `encrypted_cvv` | Digits only | `123`, `0987` | | | `encrypted_expiration_month` | Integer, no leading zero | `1`, `12` | Range: 1–12 | | `encrypted_expiration_year` | 2-digit **or** 4-digit year | `26`, `2026` | Must be current or future year | ### Expiration Year Formats Both 2-digit and 4-digit year formats are accepted: !!! example "Valid expiry year values" ``` 26 2-digit short year 2026 4-digit full year ``` !!! failure "Invalid expiry year values" ``` 2026-01 Date string — not accepted 01/26 MM/YY format — not accepted ``` !!! 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: Payment methods › Cards › 3D Secure URL: https://docs.altitudepay.solutions/references/3ds/ # Authenticate cardholders with 3D Secure before authorization Handle the `action`, `redirect` or `form_submit` that `/api/transactions/authorize` returns until the issuer has authenticated the cardholder. Many transactions are approved frictionlessly, with no customer interaction. How it works: 1. **Your server** — `POST /api/transactions/authorize` 2. **Issuer ACS** — Device fingerprint in a hidden iframe 3. **Your server** — Requests authentication · result or a challenge 4. **Issuer ACS** — Challenge, if the issuer requires one 5. **Altitude Pay** — Processes the authentication and authorizes 6. **Your return_url** — After a challenge · verify the status via API or webhook ## 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. *** ## Challenge Notification URL After the customer completes the 3DS challenge, the issuer's ACS sends the result (CRes) back by POSTing an `application/x-www-form-urlencoded` request to a **notification URL**. How this URL is resolved depends on your integration type: | Integration | Default notification URL | Who handles the ACS callback | |---|---|---| | Hosted Payment Page (HPP) | `https://api.altitudepay.solutions/three-ds/{id}/hosted-payment-page/challenge-notification-url` | Gateway — no action required | | Embedded Fields (EF) | `https://api.altitudepay.solutions/three-ds/{id}/embedded-fields/challenge-notification-url` | Gateway — no action required | | Direct API | `https://api.altitudepay.solutions/three-ds/{id}/challenge-notification-url` **(overridable)** | Gateway by default; **you** if you supply `options.three_ds.notification_url` | ### Overriding the Notification URL (API Only) For **Direct API** integrations, you can terminate the ACS callback on your own backend by supplying `options.three_ds.notification_url` in the authorize request. This is useful when you want to run server-side code immediately after the challenge completes — for example, to log the result, update an order, or apply custom routing before finalizing the transaction. ```json { "terminal_id": "{TERMINAL_ID}", "reference": "ORDER-123456", "currency": "EUR", "amount": 10000, "transaction_type": "PURCHASE", "payment_method": { ... }, "customer": { ... }, "browser_info": { ... }, "options": { "three_ds": { "notification_url": "https://your-domain.com/3ds/challenge-complete" } } } ``` !!! warning "API integration only" The override is honored only when the transaction's integration type is `DIRECT_API`. For Hosted Payment Page and Embedded Fields the field is ignored and the gateway-managed URL is always used — do not send it for those flows. ### Validation Rules The supplied URL is validated at the HTTP boundary. Invalid values cause `/api/transactions/authorize` to return `400 Bad Request` before any transaction is created: - Must be `https://` - Must be a well-formed URL with a host - Must not exceed **256 characters** (EMV 3DS 2.x `notificationURL` limit) ### Required Follow-up: `authentication-completed` When you override the notification URL, **the gateway no longer learns that the challenge has finished** — the ACS is posting to your server, not ours. You are responsible for calling the `authentication-completed` endpoint to let the gateway finalize the authorization. **1. Receive the ACS callback.** The ACS sends an `application/x-www-form-urlencoded` POST to the URL you supplied, with a `cres` parameter containing the Base64URL-encoded challenge response: ``` POST https://your-domain.com/3ds/challenge-complete Content-Type: application/x-www-form-urlencoded cres=eyJ0aHJlZURTU2VydmVyVHJhbnNJRCI6Ii4uLiIsInRyYW5zU3RhdHVzIjoiWSJ9 ``` **2. Decode the CRes** to extract `transStatus` and `threeDSServerTransID`: ```javascript // Node.js 16+ — 'base64url' encoding is built-in const cresBase64Url = req.body.cres; const cresJson = Buffer.from(cresBase64Url, 'base64url').toString('utf8'); const cres = JSON.parse(cresJson); // cres = { threeDSServerTransID: "...", transStatus: "Y", ... } ``` On runtimes without native Base64URL support, convert to standard Base64 first (`-` → `+`, `_` → `/`, re-pad with `=`) and then decode — for example with `atob` in the browser or the Web Crypto-compatible APIs in Deno/Bun. **3. Call `authentication-completed`** from your server with the `session_id` (the 3DS server transaction ID) and `trans_status`: ```http POST /api/transactions/three-ds/authentication-completed Authorization: Bearer Content-Type: application/json ``` ```json { "session_id": "", "trans_status": "", "payment_data": "" } ``` All three fields are strings. `payment_data` is the opaque blob returned on the original challenge `action` response (the same value you decoded and forwarded to the ACS when building the CReq) — pass it through unchanged. The response follows the same shape as `/api/transactions/authorize` — inspect `result`, `action`, `redirect`, `form_submit` to determine the next step. See [Handling Repeated Authentication](#handling-repeated-authentication). **4. Navigate the customer.** Because the ACS callback lands on your server (not on a page the customer sees), your endpoint must also return an HTML response that moves the browser on — typically an auto-submitting form, a meta-refresh, or a `302` redirect to the next step in your checkout. ```html
``` !!! info "When you do **not** override the notification URL" With the default gateway-managed URL you do not need to call `authentication-completed` yourself — the gateway receives the CRes, finalizes the authorization, and the browser is redirected to your `return_url` (HPP) or delivered a `window.postMessage` (EF). *** ## 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 You can test 3DS authentication flows without connecting to a real 3DS Directory Server or Access Control Server. Two simulators are available: - **[Simplified Authentication Simulator](simplified-simulator.md)** — the **default, recommended** option. Pick the outcome with a [test card number](simplified-simulator.md#test-cards); no metadata required. Use this for everyday integration testing. - **Full 3DS simulator** *(advanced)* — drive the complete EMVCo fingerprint + challenge state machine via the `simulated_flow` metadata field, documented below. !!! tip "Start with the test cards" Most integrations only need the [Simplified Authentication Simulator](simplified-simulator.md). Reach for the metadata-driven flows below only when you need to exercise the full 3DS 2.x fingerprint/challenge sequence or its timeout edge cases. *** ## Advanced: Metadata-Driven Simulator (Full 3DS) The full 3DS authentication simulator lets you exercise the complete authentication state machine. 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: Payment methods › Cards › 3DS Authentication Simulator URL: https://docs.altitudepay.solutions/references/3ds/simplified-simulator.html # Test 3D Secure outcomes by choosing a test card number Send one of the test card numbers and Altitude Pay returns a frictionless result, a `redirect` or a `form_submit`, with no metadata required. Responses use the same fields as a real 3DS transaction, so only the card number changes. Visa test cards: - `4000000000000002` → `result`: Frictionless · approved - `4000000000000010` → `redirect`: Approve or Decline page - `4000000000000028` → `form_submit`: Authenticated · approved - `4000000000000036` → `form_submit`: Authentication declined - `4000000000000044` → `result`: Authenticated · declined for insufficient funds Each scenario also has a Mastercard PAN. Spaces in the PAN are ignored. The **Simplified Authentication Simulator** is the **default, recommended way to test 3D Secure** during integration. Instead of hand-crafting `simulated_flow` metadata for every scenario, you simply send one of a small set of **test card numbers** and the gateway resolves a deterministic outcome — frictionless approval, an interactive approve/decline page, an auto-submitting form, or a processing decline. !!! tip "This is the default integration option" New integrations should test 3DS with the test cards on this page. They cover the scenarios most integrations need (frictionless, redirect, form-submit, decline) without any metadata. The metadata-driven [full 3DS simulator](index.md#advanced-metadata-driven-simulator-full-3ds) remains available for advanced cases that need to exercise the complete EMVCo fingerprint + challenge state machine. *** ## How it works When your merchant account is configured with the **`simplified-authentication-simulator`** authentication service (paired with the **`simplified-card-simulator`** card provider on the terminal), the gateway: 1. Decrypts the submitted card number (PAN). 2. Looks the PAN up in the [test-card table](#test-cards) below. 3. Drives the matching outcome — and, for frictionless decline scenarios, stamps a processing code so the downstream authorization is declined too. The outcome is delivered through the **same `result` / `action` / `redirect` / `form_submit` response fields** as a real 3DS transaction, so your integration code does not change between the simulator and production — only the card number does. See [Response Handling](index.md#response-handling) for how to branch on those fields. The redirect and form-submit outcomes complete against a **gateway-controlled** ACS-style page (the transaction id is carried in the URL path), so the flow always finalizes within your test environment — no external Directory Server or Access Control Server is involved. ```mermaid sequenceDiagram participant Browser participant Server as Your Server participant Gateway as Payment Gateway Server ->> Gateway: POST /api/transactions/authorize (test card) Gateway ->> Gateway: Decrypt PAN → look up scenario alt Frictionless test card Gateway -->> Server: result (approved / declined) else Redirect test card Gateway -->> Server: redirect.url (gateway ACS page) Server -->> Browser: redirect browser to URL Browser ->> Gateway: Approve / Decline button Gateway -->> Browser: redirect to return_url else Form-submit test card Gateway -->> Server: form_submit (url + data) Server -->> Browser: auto-submit form Browser ->> Gateway: POST trans_status (Y / N) Gateway -->> Browser: redirect to return_url end ``` *** ## Test cards A mapped card fully determines the outcome — no metadata is required. The same scenario is available on both a Visa and a Mastercard PAN. | Scenario | Visa | Mastercard | What happens | |---|---|---|---| | **Frictionless — approved** | `4000000000000002` | `5200000000000007` | Authenticated without interaction (`trans_status: Y`); authorization is **approved**. | | **Redirect — interactive approve/decline** | `4000000000000010` | `5200000000000015` | Response contains a `redirect`. The customer's browser is sent to a gateway-hosted ACS-style page with **Approve** and **Decline** buttons; the chosen result finalizes the transaction. | | **Form submit — authenticated** | `4000000000000028` | `5200000000000023` | Response contains a `form_submit` that auto-posts `trans_status: Y` to the gateway completion endpoint. Authentication succeeds; authorization is **approved**. | | **Form submit — authentication declined** | `4000000000000036` | `5200000000000031` | Response contains a `form_submit` that auto-posts `trans_status: N`. Authentication **fails**; the transaction is **declined**. | | **Frictionless authenticated, then processing decline** | `4000000000000044` | `5200000000000049` | Authenticated frictionlessly (`trans_status: Y`), but the authorization step is **declined with insufficient funds** (`TRX_STATUS_DECLINED_INSUFFICIENT_FUNDS`). | !!! info "Card numbers are matched after stripping whitespace" You may send the PAN with or without spaces. Only the digits are used for lookup, so `4000 0000 0000 0002` and `4000000000000002` resolve to the same scenario. The CVV, expiry, and cardholder name are not part of the lookup — use any valid-format values. *** ## Handling each outcome You handle the simulator's responses exactly as you would a real 3DS transaction. ### Frictionless The authorize response contains a populated `result` (and `action`, `redirect`, `form_submit` are all `null`). The transaction is already finalized — read its status from `result` or verify it via the [Transaction Status API](../../transaction-api/transaction-status-api.md). ### Redirect ```json { "result": null, "action": null, "redirect": { "transaction_id": "vvIVFmPuwYosePLsoDsW", "url": "https:///simulator/simplified/three-ds/vvIVFmPuwYosePLsoDsW" }, "form_submit": null } ``` Redirect the customer's browser to `redirect.url`. The gateway serves an ACS-style page with **Approve** and **Decline** buttons: - **Approve** posts `trans_status: Y` → authentication succeeds. - **Decline** posts `trans_status: N` → authentication fails and the transaction is declined. After the button is pressed the gateway finalizes the transaction and redirects the browser to your `return_url`. Verify the final outcome with the [Transaction Status API](../../transaction-api/transaction-status-api.md) or a [webhook](../webhooks/index.md). ### Form submit ```json { "result": null, "action": null, "redirect": null, "form_submit": { "transaction_id": "vvIVFmPuwYosePLsoDsW", "url": "https:///simulator/simplified/three-ds/vvIVFmPuwYosePLsoDsW/authentication-completed", "data": { "trans_status": "Y" } } } ``` Auto-submit a hidden form to `form_submit.url` with the fields from `form_submit.data` (see the [`submitHiddenForm` helper](../../recipes/3ds-card-payment.md#helper-submithiddenform)). The form posts the predetermined `trans_status` to the gateway completion endpoint, which finalizes the transaction and redirects the browser to your `return_url`. !!! note "The form always posts to the gateway" The form-submit URL is always the gateway's own completion endpoint — it is never an external URL. This keeps the whole flow inside your test environment. *** ## Cards not in the table (escape hatch) When the submitted PAN is **not** in the [test-card table](#test-cards) (or cannot be decrypted), the simulator falls back to metadata, so you can still drive a redirect or form-submit with an arbitrary card: | Metadata key | Effect | |---|---| | `simulate_redirect` | Returns a `redirect`. The redirect always targets the gateway completion page (Approve/Decline). | | `simulate_form_submit` | Returns a `form_submit` that auto-posts to the gateway completion endpoint. Set `{"trans_status":"N"}` as the value to complete with a declined authentication; defaults to `Y`. | | *(neither present)* | **3DS is skipped** (delegated) and the flow proceeds straight to authorization, where the [simplified card simulator](../cards/index.md#simulator) produces the result from the `response_code` metadata. | ```json { "metadata": { "simulate_form_submit": "{\"trans_status\":\"N\"}" } } ``` !!! tip "Combine with the card simulator's response codes" Because an unmapped card skips straight to authorization, you can pair any non-table card with the [card simulator metadata](../cards/index.md#controlling-the-response) (e.g. `response_code`) to test authorization outcomes independently of the authentication leg. *** ## Simplified vs. full simulator | | Simplified Authentication Simulator | [Full 3DS simulator](index.md#advanced-metadata-driven-simulator-full-3ds) | |---|---|---| | Selected by | Card number | `simulated_flow` metadata | | Fingerprint / method-URL step | Skipped | Configurable (incl. timeout scenarios) | | Challenge state machine | Single redirect / form-submit completion | Full EMVCo fingerprint → challenge flow | | Best for | Everyday integration testing **(default)** | Exercising the complete 3DS 2.x flow and edge cases | *** ## Related Documentation - [3D Secure Reference](index.md) — Full protocol details, response handling, and the metadata-driven simulator - [Recipe: 3DS Card Payment](../../recipes/3ds-card-payment.md) — End-to-end integration walkthrough - [Card Simulator](../cards/index.md#simulator) — Control the authorization outcome via metadata - [Transaction Statuses](../../transaction-api/supported-transaction-statuses.md) — All transaction lifecycle states - [Webhooks](../webhooks/index.md) — Receive real-time payment status notifications --- ## Source: Payment methods › Cards › Network Token URL: https://docs.altitudepay.solutions/references/network-token/ # Authorize payments with network tokens you decrypt yourself Decrypt the Apple Pay, Google Pay or TSP token on your own infrastructure, then send the DPAN, cryptogram and ECI with `payment_method.type` set to `network-token`. No CVV is required, because the cryptogram proves authentication. How it works: 1. **Customer wallet** — Apple Pay · Google Pay or TSP token 2. **Your backend** — Decrypts the token · encrypts the fields 3. **Altitude Pay** — `POST /api/transactions/authorize` 4. **Acquirer** — Processes the transaction ## 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 - [Apple Pay](../apple-pay/index.md) — Accepting Apple Pay, including this network-token path - [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: Payment methods › Cards › Network Token Authorize URL: https://docs.altitudepay.solutions/references/network-token/authorize.html # Authorize a payment with decrypted network token data Set `payment_method.type` to `network-token` and send the DPAN, expiry, cryptogram and ECI to `/api/transactions/authorize`. Encrypt the sensitive fields with your merchant public key first. How it works: 1. **Your backend** — Encrypts the DPAN, expiry and cryptogram 2. **Altitude Pay API** — `POST /api/transactions/authorize` 3. **Your backend** — Handles result, action, redirect or form_submit [OAD(../../assets/openapi/openapi-transaction-authorize.yaml)] --- ## Source: Payment methods › Wallets › Google Pay URL: https://docs.altitudepay.solutions/references/google-pay/ # Accept Google Pay™ on the hosted page or through the API Enable Google Pay on your terminal and it appears on the Hosted Payment Page with no extra development. With the API, send the Base64-encoded token to `/api/transactions/authorize` and Altitude Pay decrypts it. > **Handle 3DS for every Google Pay payment.** You cannot tell whether a credential is PAN_ONLY or CRYPTOGRAM_3DS, because Altitude Pay decrypts the token. How it works: 1. **Google Pay dialog** — Customer selects a card and confirms 2. **Your backend** — Base64-encodes the token 3. **Altitude Pay** — `POST /api/transactions/authorize` ## Introduction Google Pay™ lets users pay with their cards stored in their Google Account, including cards added through Google Play, YouTube, Chrome, or an Android device. Customers can use these cards 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 !!! important "Google Pay branding" If you provide Google Pay as a payment method to your customers, you must use the official Google Pay logo and button assets in compliance with the [Google Pay Android brand guidelines](https://developers.google.com/pay/api/android/guides/brand-guidelines) and [Google Pay web brand guidelines](https://developers.google.com/pay/api/web/guides/brand-guidelines), without modifications to the Google Pay asset colors, proportions, or appearance. ### 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. **Adhere to Google policies** — all merchants are required to follow the [Google Pay and Wallet API Acceptable Use Policy](https://payments.developers.google.com/terms/aup) and accept the terms defined in the [Google Pay API Terms of Service](https://payments.developers.google.com/terms/sellertos) 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 Our hosted checkout integration with Google Pay simplifies your ability to offer Google Pay as a payment option to your customers. 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 As a supported payment service provider with the Google Pay API, our integration lets you use the Google Pay API's `gateway` integration type — we handle all token decryption on your behalf, so your servers never process raw card credentials. When you integrate with the Google Pay API, set the following values in the `tokenizationSpecification` object: - `gateway` — the [gateway ID value](https://developers.google.com/pay/api/processors/guides/implementation/prepare-your-key#generate-gateway-value) assigned during technical onboarding with Google (see the table below for your brand's value) - `gatewayMerchantId` — your merchant identifier in the Payment Gateway, provided by your account manager 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": "", "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` | `""` | Gateway identifier for Altitude Pay | | `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/) !!! note "Placeholder Values" In JSON examples such as the one above, properties like `gatewayMerchantId` and `merchantId` should not be mapped to empty strings (`""`). We recommend using clear, descriptive placeholder text instead (e.g. `"[YOUR_GATEWAY_MERCHANT_ID]"`), as this helps prevent configuration errors during merchant setup. #### Billing Address If a billing address is required for Google Pay, ensure that you include the required parameters in your Google Pay API request: set `billingAddressRequired` to `true` and configure `billingAddressParameters` on the `CARD` payment method. For more details, see [`BillingAddressParameters`](https://developers.google.com/pay/api/web/reference/request-objects#BillingAddressParameters). Billing address request example: ```json { "allowedPaymentMethods": [ { "type": "CARD", "parameters": { "billingAddressRequired": true, "billingAddressParameters": { "phoneNumberRequired": true, "format": "MIN" }, "allowedCardNetworks": [...], "allowedAuthMethods": [...] }, "tokenizationSpecification": {...} } ] } ``` *** ### 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": "PURCHASE", "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 - [Apple Pay](../apple-pay/index.md) — The equivalent Apple Pay integration - [Webhooks](../webhooks/index.md) — Receive payment status notifications --- ## Source: Payment methods › Wallets › Apple Pay URL: https://docs.altitudepay.solutions/references/apple-pay/ # Accept Apple Pay by PSP redirect or with tokens you decrypt Choose Wallet Redirect (`apple-pay-redirect`) and a PSP renders the Apple Pay sheet, or decrypt the token yourself and send it as a `network-token`. Customers authenticate on their device, so no card details are typed into your checkout. How it works: 1. **Your checkout** — Customer selects Apple Pay 2. **Altitude Pay** — Returns a redirect URL to the PSP 3. **PSP** — Renders the Apple Pay sheet and authorizes ## Introduction Apple Pay lets customers pay with the cards stored in their Apple Wallet on iPhone, iPad, Apple Watch and Mac. Authentication happens on the customer's device through Face ID, Touch ID or a passcode, so no card details are typed into your checkout. **Benefits:** - Customers select a card from Apple Wallet — no manual card entry - Device-level authentication reduces fraud and cart abandonment - Card details are replaced by a device-specific token, so the real card number is never exposed !!! important "Apple Pay branding and acceptable use" If you offer Apple Pay to your customers, you must use the official Apple Pay marks and button assets in compliance with the [Apple Pay Marketing Guidelines](https://developer.apple.com/apple-pay/marketing/), the [Apple Pay Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/apple-pay) and the [Acceptable Use Guidelines for Apple Pay on the Web](https://developer.apple.com/apple-pay/acceptable-use-guidelines-for-websites/), without modifying the assets' colors, proportions or appearance. !!! note "Apple Pay availability differs by browser and device" The Apple Pay button only renders in Safari and other Apple Pay-capable browsers on Apple devices. On platforms where Apple Pay is unavailable, present your other payment methods instead — do not show a non-functional button. ### Tokenization Apple Pay does not transmit the real card number. Each transaction carries: | Element | Description | |---|---| | **DPAN** | A device-specific account number that substitutes the real PAN. Structurally similar to a card number and specific to the device and wallet. | | **Cryptogram** | A one-time value (also called TAVV) generated during device authentication, proving the cardholder authenticated on their device. | | **ECI** | The Electronic Commerce Indicator, describing the authentication level achieved. | Because the cryptogram already proves cardholder authentication, Apple Pay transactions are treated as **`CRYPTOGRAM_3DS`** — the equivalent of a fully authenticated transaction. See [Secure Customer Authentication](#secure-customer-authentication-sca3ds-and-psd2) below. **Supported card brands:** the Payment Gateway recognises **Visa**, **Mastercard**, **Maestro**, **American Express**, **Discover** and **JCB** for Apple Pay. The brands actually offered to your customers are those enabled on your terminal *and* supported by your payment provider — the gateway determines the brand from the token's BIN and validates it against your terminal configuration. Note that Apple Pay supports **Maestro**, which Google Pay does not. *** ## Integration Methods There are two ways to accept Apple Pay through the Payment Gateway. | Method | Who decrypts the Apple Pay token | Additional development | |---|---|---| | [Wallet Redirect](#wallet-redirect) (`apple-pay-redirect`) | The PSP — the gateway never sees the token | None to minimal | | [Network Token](#network-token) (`network-token`) | You, on your own infrastructure | Yes — Apple Pay on the Web plus token decryption | !!! info "Rendering the Apple Pay button on your own checkout" If you need the Apple Pay button inside your own checkout **and** you want the gateway to decrypt the Apple Pay token on your behalf, contact your account manager — that integration path is not generally available yet. Today, either the PSP renders the wallet sheet ([Wallet Redirect](#wallet-redirect)) or you decrypt the token yourself ([Network Token](#network-token)). ### Activation Prerequisites Before accepting Apple Pay payments, you must: 1. Submit a request to your account manager to enable Apple Pay on your terminal 2. **Adhere to Apple's policies** — comply with the [Acceptable Use Guidelines for Apple Pay on the Web](https://developer.apple.com/apple-pay/acceptable-use-guidelines-for-websites/) and the [Apple Pay Marketing Guidelines](https://developer.apple.com/apple-pay/marketing/) 3. For the [Network Token](#network-token) method only: enrol with Apple as a merchant, register and verify your domains, and provision your Apple Pay certificates — see [Apple Pay on the Web](https://developer.apple.com/documentation/apple_pay_on_the_web) *** ## Wallet Redirect **Wallet Redirect** is the simplest option. You select Apple Pay on your platform and the gateway returns a redirect URL to a PSP that renders the Apple Pay sheet and authorizes the payment end to end. The gateway never receives the Apple Pay token, so **no Apple merchant enrolment, certificates or domain verification are required on your side** — the PSP owns those. Set `payment_method.type` to `apple-pay-redirect` and send `payment_method.data` as an empty object. Available through all three integration types: | Integration type | Effort | Notes | |---|---|---| | [Hosted Payment Page](../../integration-types/hosted-payment-page.md) | None | Apple Pay appears automatically on the HPP once enabled on your terminal | | [Embedded Fields](../../integration-types/embedded-fields.md) | Minimal | The gateway exposes an `apple-pay-redirect` stub script; your page renders its own "Pay" button | | [API Integration](../../integration-types/api-integration.md) | Low | Send one `authorize` request with `payment_method.type` set to `apple-pay-redirect` and follow the returned redirect | Wallet Redirect supports `PURCHASE` only. For the full request and response contract, the flow diagrams and the limitations, see [Wallet Redirect](../wallet-redirect/index.md). *** ## Network Token If you already run [Apple Pay on the Web](https://developer.apple.com/documentation/apple_pay_on_the_web) and decrypt the `PKPaymentToken` on your own infrastructure, submit the decrypted values through the `network-token` payment method instead. Send the DPAN, expiry, cryptogram and ECI, encrypted with your merchant public key, and set `source` to `apple-pay` so the origin of the token is recorded: ```json { "payment_method": { "type": "network-token", "data": { "encrypted_token_number": "", "encrypted_expiration_month": "", "encrypted_expiration_year": "", "encrypted_cryptogram": "", "eci": "05", "source": "apple-pay" } } } ``` No CVV is required — the cryptogram is the authentication proof. For the complete field reference, the ECI value table and the encryption details, see [Network Token](../network-token/index.md). *** ## Secure Customer Authentication (SCA/3DS) and PSD2 Apple Pay transactions arrive already authenticated: the customer authenticated on their device with Face ID, Touch ID or a passcode, and the resulting cryptogram carries that proof. The gateway therefore treats Apple Pay as `CRYPTOGRAM_3DS` and **does not trigger an additional 3DS challenge**, which satisfies SCA under PSD2 without a second authentication step for the customer. !!! note "Authentication still depends on the data supplied" For the [Network Token](#network-token) method, the cryptogram and ECI you send are what establish the authenticated status. If they are missing, the transaction is treated as unauthenticated and may be declined or routed through 3DS by the issuer. *** ## Related Documentation - [Wallet Redirect](../wallet-redirect/index.md) — PSP-rendered Apple Pay and Google Pay - [Network Token](../network-token/index.md) — For merchants who decrypt Apple Pay tokens themselves - [Google Pay](../google-pay/index.md) — The equivalent Google Pay integration - [Hosted Payment Page](../../integration-types/hosted-payment-page.md) — HPP integration guide - [API Integration](../../integration-types/api-integration.md) — Direct API integration guide - [Handling Declines](../handling-declines/index.md) — Processing codes and decline handling - [Webhooks](../webhooks/index.md) — Receive payment status notifications --- ## Source: Payment methods › Wallets › Wallet Redirect URL: https://docs.altitudepay.solutions/references/wallet-redirect/ # Accept Google Pay and Apple Pay without integrating wallet SDKs Set `payment_method.type` to `google-pay-redirect` or `apple-pay-redirect` and send the customer to the returned PSP page, which renders the wallet and authorizes the payment. Altitude Pay never sees card data or the wallet token. > **PURCHASE only.** AUTHORIZE-then-capture and standalone refunds initiated via the wallet are not available. How it works: 1. **Your backend** — `POST /api/transactions/authorize` 2. **Altitude Pay** — Returns the PSP redirect URL 3. **PSP wallet page** — Customer authenticates in the wallet sheet 4. **Your webhook endpoint** — Receives the final status ## Overview **Wallet Redirect** lets you accept **Google Pay** and **Apple Pay** without integrating the wallet SDKs on your own site. Instead of rendering the Google Pay / Apple Pay button yourself and forwarding an encrypted token to the gateway, you select the payment method on your platform and the gateway returns a **redirect URL** to a PSP that renders the wallet button and authorizes the payment end-to-end. | Payment method type | Description | |---------------------|----------------------------------------| | `google-pay-redirect` | Google Pay rendered and authorized by the PSP | | `apple-pay-redirect` | Apple Pay rendered and authorized by the PSP | Both are classified as **alternative payment methods** even though the underlying instrument is a card — the gateway never sees card data, the wallet token, or the decrypted PAN. The PSP returns the transaction outcome and the gateway records it. !!! note "When to use Wallet Redirect vs. native Google Pay" Use Wallet Redirect when you want the simplest possible integration and are happy for the wallet UI to be rendered by the PSP after a redirect. If you need the Google Pay button rendered **inside your own checkout** with no redirect, use the native [Google Pay](../google-pay/index.md) integration instead, which requires you to integrate the Google Pay API and forward the encrypted token. *** ## Integration Methods Wallet Redirect is available through all three integration types: | Method | Effort | Notes | |---------------------------------------------------------------------|---------|-------------------------------------------------------------------------------------------------------------| | [Hosted Payment Page](../../integration-types/hosted-payment-page.md) | None | Google Pay / Apple Pay appear automatically on the HPP once enabled on your terminal | | [Embedded Fields](../../integration-types/embedded-fields.md) | Minimal | The gateway exposes a stub script (`google-pay-redirect` / `apple-pay-redirect`) that registers a factory; your page renders its own "Pay" button | | [API Integration](../../integration-types/api-integration.md) | Low | Send a single `authorize` request with `payment_method.type` set to `google-pay-redirect` or `apple-pay-redirect` and follow the returned redirect | ### Activation Prerequisites Before accepting Wallet Redirect payments: 1. Request activation of `google-pay-redirect` and/or `apple-pay-redirect` on your terminal from your account manager. 2. No wallet-specific developer credentials (Google merchant ID, Apple Pay merchant ID, domain verification, etc.) are required on your side — the PSP owns those. *** ## Hosted Payment Page If you are already integrated with the [Hosted Payment Page](../../integration-types/hosted-payment-page.md), **no additional development is required**. Once `google-pay-redirect` and/or `apple-pay-redirect` are enabled on your terminal, they appear as payment options on the HPP alongside cards and other APMs. After the customer selects one, the HPP redirects them to the PSP-hosted wallet page. ```mermaid sequenceDiagram participant Customer participant Merchant Frontend participant Payment Gateway participant PSP Customer->>Merchant Frontend: Start checkout Merchant Frontend->>Payment Gateway: Create HPP session Payment Gateway-->>Merchant Frontend: HPP URL Merchant Frontend->>Customer: Redirect to HPP Customer->>Payment Gateway: View HPP and select Google Pay / Apple Pay Payment Gateway->>PSP: Initiate wallet redirect PSP-->>Payment Gateway: Redirect URL Payment Gateway-->>Customer: Redirect to PSP wallet page Customer->>PSP: Tap Google Pay / Apple Pay and authenticate PSP-->>Payment Gateway: Notify payment result Payment Gateway-->>Merchant Frontend: Notify result (webhook + return URL) ``` *** ## API Integration In the **Direct API** flow the payment method is already selected on your platform — typically because your own checkout rendered a "Google Pay" or "Apple Pay" button. You submit a standard `authorize` request and the gateway returns a redirect URL that the browser must follow to complete the wallet authorization. ### Authorize Request ```http POST /api/transactions/authorize Authorization: Bearer Content-Type: application/json ``` ```json { "reference": "ORDER-WR-001", "terminal_id": "TERM001", "description": "Wallet Redirect payment", "currency": "EUR", "amount": 1000, "transaction_type": "PURCHASE", "payment_method": { "type": "google-pay-redirect", "data": {} }, "customer": { "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "country": "BA" }, "return_url": "https://merchant.example.com/return", "error_url": "https://merchant.example.com/error", "cancel_url": "https://merchant.example.com/cancel" } ``` For Apple Pay, set `payment_method.type` to `apple-pay-redirect`. The `payment_method.data` object is currently unused for both methods and may be sent as an empty object `{}`. | Field | Value | Notes | |-------------------------|----------------------------------------------------|------------------------------------------------------------------------------------| | `payment_method.type` | `google-pay-redirect` or `apple-pay-redirect` | Required | | `payment_method.data` | `{}` | No wallet token, encrypted PAN, or device data is required — the PSP collects it | | `return_url` | URL on your site | Where the PSP sends the customer after a successful authorization | | `error_url` | URL on your site | Where the PSP sends the customer after a declined or failed authorization | | `cancel_url` | URL on your site | Where the PSP sends the customer if they abandon the wallet sheet | ### Authorize Response ```json { "transaction_id": "txn_01HX...", "status": "PENDING", "redirect": { "transaction_id": "txn_01HX...", "url": "https://psp.example.com/wallet/google-pay-redirect/..." } } ``` Redirect the customer's browser to `redirect.url`. After the customer completes the wallet sheet at the PSP, they are returned to one of the URLs you supplied (`return_url`, `error_url`, or `cancel_url`) and the gateway emits the standard [transaction-processed webhook](../webhooks/transaction-processed-webhook.md) with the final status. ### Flow Diagram ```mermaid sequenceDiagram participant Customer participant Merchant Frontend participant Merchant Backend participant Payment Gateway participant PSP Customer->>Merchant Frontend: Click Google Pay / Apple Pay Merchant Frontend->>Merchant Backend: Place order Merchant Backend->>Payment Gateway: POST /api/transactions/authorize
(type = google-pay-redirect | apple-pay-redirect) Payment Gateway->>PSP: Initiate wallet session PSP-->>Payment Gateway: Redirect URL Payment Gateway-->>Merchant Backend: redirect.url Merchant Backend-->>Merchant Frontend: redirect.url Merchant Frontend->>Customer: Redirect to PSP Customer->>PSP: Authenticate in Google Pay / Apple Pay sheet PSP-->>Payment Gateway: Final result PSP-->>Customer: Redirect to return_url / error_url / cancel_url Payment Gateway-->>Merchant Backend: Transaction-processed webhook ``` *** ## Embedded Fields For [Embedded Fields](../../integration-types/embedded-fields.md), the gateway exposes a small stub script for each wallet redirect method that registers a factory on `window`: | Method | Script key | |-----------------------|-------------------------------------| | `google-pay-redirect` | `window['google-pay-redirect'].factory` | | `apple-pay-redirect` | `window['apple-pay-redirect'].factory` | The script is intentionally **not** a wallet SDK — it does not render a Google Pay / Apple Pay button and does not perform the redirect. Your embedded checkout renders its own "Pay" button; when the customer clicks it, the backend `authorize` response carries the `redirect_url` and the embedded checkout host performs the navigation. *** ## Limitations - **No tokenization on your side.** The gateway never receives a Google Pay / Apple Pay token. If you need to decrypt tokens yourself or render the wallet button on your own checkout without a redirect, integrate the native [Google Pay](../google-pay/index.md) flow instead. For the Apple Pay options, see [Apple Pay](../apple-pay/index.md). - **No customer-data passthrough to the wallet.** The PSP collects everything it needs inside the wallet sheet; the `payment_method.data` object is currently unused. - **One transaction type.** Wallet Redirect supports `PURCHASE` only — `AUTHORIZE`-then-capture and standalone refunds initiated via the wallet are not available. *** ## Related Documentation - [Google Pay](../google-pay/index.md) — Native Google Pay integration where you forward the encrypted wallet token - [Apple Pay](../apple-pay/index.md) — Apple Pay overview and the available integration options - [Hosted Payment Page](../../integration-types/hosted-payment-page.md) - [Embedded Fields](../../integration-types/embedded-fields.md) - [API Integration](../../integration-types/api-integration.md) - [Webhooks](../webhooks/index.md) --- ## Source: Payment methods › Redirects › Bank Redirect URL: https://docs.altitudepay.solutions/references/bank-redirect/ # Accept payments straight from a customer’s bank account The gateway returns a redirect URL to a PSP-hosted page where the customer signs in to their bank and approves the payment. No card, card network or 3-D Secure step is involved. > **Pending until the bank confirms.** Treat the transaction-processed webhook as the source of truth and never release goods on the redirect alone. How it works: 1. **Your checkout** — Customer chooses Interac or Open Banking 2. **Altitude Pay** — Returns the PSP redirect URL 3. **PSP bank page** — Customer signs in and approves the payment 4. **Your webhook endpoint** — Receives the final status ## Overview **Bank Redirect** lets you accept payments made directly from a customer's bank account. You select the payment method on your platform and the gateway returns a **redirect URL** to a PSP-hosted page where the customer signs in to their bank and approves the payment. | Payment method type | Description | |---------------------|-------------| | `interac-redirect` | Payment through the Interac network, where the customer approves the transfer in their Canadian bank's online banking | | `openbanking-redirect` | Account-to-account payment initiation, where the customer authorizes the payment in their own bank | Both are **alternative payment methods**. Unlike [Wallet Redirect](../wallet-redirect/index.md), there is no card involved at any point — no PAN, no card network, and no 3-D Secure step. The customer authenticates with their bank, and the bank's approval *is* the authorization. !!! note "Availability depends on your PSP and the customer's country" Interac is a Canadian interbank network, so `interac-redirect` is only usable for customers banking in Canada. Open Banking coverage depends on which banks your PSP supports in the customer's country. Ask your account manager which of the two are available on your terminal. *** ## Integration Methods Bank Redirect is available through all three integration types: | Method | Effort | Notes | |--------|--------|-------| | [Hosted Payment Page](../../integration-types/hosted-payment-page.md) | None | Interac / Open Banking appear automatically on the HPP once enabled on your terminal | | [Embedded Fields](../../integration-types/embedded-fields.md) | Minimal | The gateway exposes a stub script (`interac-redirect` / `openbanking-redirect`) that registers a factory; your page renders its own "Pay" button | | [API Integration](../../integration-types/api-integration.md) | Low | Initiate a [payment](../payments/index.md) on a terminal enabled for `TRX_TYPE_PAYMENT` and follow the returned redirect | ### Activation Prerequisites Before accepting Bank Redirect payments: 1. Request activation of `interac-redirect` and/or `openbanking-redirect` on your terminal from your account manager. 2. Your PSP must have the corresponding product provisioned on your account, with an acquiring connection behind it. No bank credentials, API keys, or consent flows are required on your side — the PSP owns the connection to the customer's bank. *** ## Hosted Payment Page If you are already integrated with the [Hosted Payment Page](../../integration-types/hosted-payment-page.md), **no additional development is required**. Once the methods are enabled on your terminal, they appear as payment options on the HPP alongside cards and other APMs. After the customer selects one, the HPP redirects them to the PSP-hosted bank selection and login page. ```mermaid sequenceDiagram participant Customer participant Merchant Frontend participant Payment Gateway participant PSP participant Bank Customer->>Merchant Frontend: Start checkout Merchant Frontend->>Payment Gateway: Create HPP session Payment Gateway-->>Merchant Frontend: HPP URL Merchant Frontend->>Customer: Redirect to HPP Customer->>Payment Gateway: View HPP and select Interac / Open Banking Payment Gateway->>PSP: Initiate bank redirect PSP-->>Payment Gateway: Redirect URL Payment Gateway-->>Customer: Redirect to PSP bank page Customer->>Bank: Sign in and approve the payment Bank-->>PSP: Approval PSP-->>Payment Gateway: Notify payment result Payment Gateway-->>Merchant Frontend: Notify result (webhook + return URL) ``` *** ## API Integration Both methods run on the **payment** transaction type, not the card one. That has two consequences for a Direct API integration: - The terminal must carry the `TRX_TYPE_PAYMENT` feature. A terminal enabled only for card purchases cannot process them. - They are initiated through the [Payments](../payments/index.md) endpoint, `POST /api/transactions/payment`, not through `/api/transactions/authorize`. There is no `transaction_type` field to set — it is always `PAYMENT` — and no `payment_method` object, because no card or account data is collected. See [Payments – Direct API Integration](../payments/direct-api-integration.md) for the full request and response contract, including the required `return_url`, `error_url`, `cancel_url` and `customer_ip` fields. !!! note "Selecting the method" The `method` field on the payment endpoint currently accepts `OPEN_BANKING`, `UPI`, `WALLETS`, `BANK_TRANSFER` and `QR_CODE`. Interac and Open Banking Redirect are selected by the customer on the [Hosted Payment Page](../../integration-types/hosted-payment-page.md) or by your own checkout through [Embedded Fields](../../integration-types/embedded-fields.md), where the payment method type is chosen directly. Ask your account manager which route applies to your terminal. ### Response The payment response is asynchronous and carries a redirect: ```json { "result": { "id": "PyTxAbCdEfGhIjKlMnOp", "terminal_id": "TERM001", "reference": "PAY-20260331-001", "currency": "EUR", "amount": 10000, "transaction_type": "PAYMENT", "status": "PENDING", "approved": false, "pending": true, "errors": [] }, "redirect": { "transaction_id": "PyTxAbCdEfGhIjKlMnOp", "url": "https://psp.example.com/payment/form/init/..." } } ``` Redirect the customer's browser to `redirect.url`. After they approve the payment at their bank, they are returned to one of the URLs you supplied (`return_url`, `error_url`, or `cancel_url`) and the gateway emits the standard [transaction-processed webhook](../webhooks/transaction-processed-webhook.md) with the final status. !!! warning "The transaction stays PENDING until the bank confirms" The initial response is always `PENDING` — it only means the redirect was created. Bank approval is asynchronous and can take noticeably longer than a card authorization. Treat the [transaction-processed webhook](../webhooks/transaction-processed-webhook.md) as the source of truth and never release goods on the redirect alone. A transaction that the customer abandons is **not** cancelled automatically. Bank journeys can outlast any timeout that would be safe to apply, and cancelling is not reversible — a late approval arriving after a cancellation would be dropped and leave you short a settled payment. Such a transaction therefore stays `PENDING` until the bank actually reports an outcome, which for an abandoned payment may be never. Use the [Transaction Status API](../../transaction-api/transaction-status-api.md) to check where one stands rather than assuming it will resolve itself. ### Flow Diagram ```mermaid sequenceDiagram participant Customer participant Merchant Frontend participant Merchant Backend participant Payment Gateway participant PSP participant Bank Customer->>Merchant Frontend: Choose Interac / Open Banking Merchant Frontend->>Merchant Backend: Place order Merchant Backend->>Payment Gateway: POST /api/transactions/payment Payment Gateway->>PSP: Initiate bank session PSP-->>Payment Gateway: Redirect URL Payment Gateway-->>Merchant Backend: redirect.url Merchant Backend-->>Merchant Frontend: redirect.url Merchant Frontend->>Customer: Redirect to PSP Customer->>Bank: Sign in and approve the payment Bank-->>PSP: Approval PSP-->>Payment Gateway: Final result PSP-->>Customer: Redirect to return_url / error_url / cancel_url Payment Gateway-->>Merchant Backend: Transaction-processed webhook ``` *** ## Embedded Fields For [Embedded Fields](../../integration-types/embedded-fields.md), the gateway exposes a small stub script for each method that registers a factory on `window`: | Method | Script key | |--------|------------| | `interac-redirect` | `window['interac-redirect'].factory` | | `openbanking-redirect` | `window['openbanking-redirect'].factory` | The script does not render a bank selector and does not perform the redirect. Your embedded checkout renders its own "Pay" button; when the customer clicks it, the backend `authorize` response carries the `redirect_url` and the embedded checkout host performs the navigation. *** ## Limitations - **Payment transaction type only.** These run as `PAYMENT` transactions. The funds move as a single transfer, so there is no hold to capture or void later and `AUTHORIZE`-then-capture is not available. - **No stored payment method.** Nothing is tokenized and nothing can be reused — every payment sends the customer to their bank again. Recurring and one-click flows are not supported. - **No 3-D Secure.** The customer's bank performs its own strong customer authentication; the gateway runs no 3DS step and returns no ECI or CAVV. - **Asynchronous outcome.** The result arrives by webhook after the customer approves at their bank, not in the `authorize` response — and an abandoned payment may never produce one, leaving the transaction `PENDING`. *** ## Related Documentation - [Wallet Redirect (Google Pay & Apple Pay)](../wallet-redirect/index.md) — the equivalent redirect flow for wallets, where the underlying instrument is a card - [Hosted Payment Page](../../integration-types/hosted-payment-page.md) - [Embedded Fields](../../integration-types/embedded-fields.md) - [API Integration](../../integration-types/api-integration.md) - [Webhooks](../webhooks/index.md) --- ## Source: Payment methods › Redirects › Paysafe Redirect URL: https://docs.altitudepay.solutions/references/paysafe-redirect/ # Offer localized payment methods through a single Paysafe integration Customers are redirected to Paysafe to pay with methods such as Trustly, PaySafeCard or iDEAL. Let the Hosted Payment Page show the methods, or pick one on your platform and send its code through the Direct API. How it works: 1. **Your checkout** — Starts checkout or sends the chosen method 2. **Altitude Pay** — Initiates the redirect to Paysafe 3. **Paysafe** — Customer completes the payment 4. **Your platform** — Notified of the payment result ## 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 | | IDL | iDEAL | > **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: Payment methods › Redirects › Paysafe Direct API Integration URL: https://docs.altitudepay.solutions/references/paysafe-redirect/direct-api-integration.html # Redirect customers straight to Paysafe with the method already chosen Call `/api/transactions/authorize` with the payment method type `paysafe-redirect-{code}` and the customer’s `email`, `first_name` and `last_name`. The customer goes straight to Paysafe, with no selection screen. > **The prefix is mandatory.** An incorrect or missing prefix causes request failures or improper routing. How it works: 1. **Your platform** — Presents methods · captures the choice 2. **Altitude Pay API** — `POST /api/transactions/authorize` 3. **Paysafe** — Customer completes the payment 4. **Your backend** — Handles the asynchronous notification ## 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** * `paysafe-redirect-idl` → for **iDEAL** 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: Payment methods › Redirects › Paysafe Customization Options URL: https://docs.altitudepay.solutions/references/paysafe-redirect/customization-options.html # Customize the Paysafe redirect with optional parameters Add key-value pairs to `options.paysafe_redirect.params` in the authorization request. They are forwarded to Paysafe as-is, and any you leave out fall back to your Paysafe configuration. This section extends the [Direct API Integration](direct-api-integration.md) 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: Payment methods › Redirects › Neogated (BLIK) URL: https://docs.altitudepay.solutions/references/neogated/ # Accept BLIK payments from Polish customers through Neogated Customers are redirected to Neogated and approve the payment in their mobile banking app. Offer BLIK on the Hosted Payment Page, through Embedded Fields or with the Direct API. > **PLN and PURCHASE only.** Requests in other currencies are rejected, and refund, capture and void are not supported. How it works: 1. **Your checkout** — Customer selects BLIK 2. **Altitude Pay** — Initiates the payment with Neogated 3. **Neogated and the bank app** — Customer approves in the mobile banking app 4. **Your return URL** — Customer returns · verify the final status ## 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/transaction-status-api.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: Payment methods › Redirects › Neogated Direct API Integration URL: https://docs.altitudepay.solutions/references/neogated/direct-api-integration.html # Start a BLIK payment directly from your own checkout Call `/api/transactions/authorize` with the payment method type `neogated-blik` and the customer’s `email`, `first_name` and `last_name`. The customer is then redirected to Neogated to complete BLIK authentication. > **Exact type required.** Incorrect payment method types will result in request failures. How it works: 1. **Your checkout** — Presents BLIK · collects email and name 2. **Altitude Pay API** — `POST /api/transactions/authorize` 3. **Neogated** — Customer completes BLIK authentication 4. **Your backend** — Handles the asynchronous callback ## 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 in E.164 format (e.g., `+12125551234`). Validated using Google libphonenumber `isPossibleNumber` — the number must be a plausible length for its 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: Payouts › Non-card Payouts › Overview URL: https://docs.altitudepay.solutions/references/payout/ # Send funds to a beneficiary’s bank account or mobile wallet Use payouts for disbursements, withdrawals, seller payments and payroll. Requests go to `/api/transactions/payout` and are processed asynchronously. > **Direct API only.** Hosted Payment Page and Embedded Fields do not support payout transactions. How it works: 1. **Your backend** — `POST /api/transactions/payout` 2. **Altitude Pay** — Validates the terminal, resolves the provider 3. **Payment provider** — Settles the payout asynchronously 4. **Your webhook endpoint** — Receives APPROVED or DECLINED ## Build a payout request A payout needs a beneficiary instead of card details. No 3-D Secure, so `browser_info` is not required. Required fields | Field | Description | | ----- | ----------- | | `terminal_id` | Must support payout transactions (`TRX_TYPE_PAYOUT`). | | `reference` | Unique per terminal, max 40 characters. Idempotent. | | `description` | Max 100 characters. | | `currency / amount` | ISO 4217 code, and an integer amount in minor units. | | `beneficiary.type` | `bank_account` or `mobile_money`. | Beneficiary · bank account (INR) | Field | Description | | ----- | ----------- | | `account_number` | Beneficiary bank account number. | | `account_name` | Name of the account holder. | | `ifsc` | Indian Financial System Code. Identifies the bank branch. | Optional `bank_code` `bank_name` `bank_branch` `bank_address` `bank_city` `bank_state` `mobile` === "cURL" ```bash title="POST /api/transactions/payout" curl -X POST https://api.altitudepay.solutions/api/transactions/payout \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "terminal_id": "TERM001", "reference": "PAYOUT-000123", "description": "Seller payout", "amount": 10000, "currency": "INR", "beneficiary": { "type": "bank_account", "data": { "account_number": "000123456789", "account_name": "Asha Rao", "ifsc": "HDFC0001234" } } }' ``` === "Node.js" ```javascript title="POST /api/transactions/payout" const res = await fetch('https://api.altitudepay.solutions/api/transactions/payout', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ terminal_id: 'TERM001', reference: 'PAYOUT-000123', description: 'Seller payout', amount: 10000, // 100.00 INR currency: 'INR', beneficiary: { type: 'bank_account', data: { account_number: '000123456789', account_name: 'Asha Rao', ifsc: 'HDFC0001234', }, }, }), }); const { status } = await res.json(); // often PENDING ``` ## Track the payout - `PENDING` — Submitted, awaiting provider confirmation → - `APPROVED` — Processed and funds transferred - `DECLINED` — Rejected by the provider or bank Before you go live - Subscribe to webhooks. The final status arrives in a [Transaction Processed Webhook](../webhooks/transaction-processed-webhook.md). - Confirm the final status with the [Transaction Status API](../../transaction-api/transaction-status-api.md). - For INR payouts, send a valid `ifsc` for the destination branch. - Account numbers are masked in API responses. Only the last 4 digits are visible. ## Non-card Payout Payment Method A **Non-card 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 non-card payout pushes funds to the recipient. Common use cases include disbursements, withdrawals, seller payments, and payroll. Non-card 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" Non-card payouts are only supported via the **Direct API** integration. **Hosted Payment Page (HPP)** and **Embedded Fields (EF)** integration types are **not supported** for non-card payout transactions. *** ### Key Characteristics 1. **Push Payment** * A non-card 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** * Non-card 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 non-card payout transactions (feature: `TRX_TYPE_PAYOUT`). 5. **Asynchronous Processing** * Non-card 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 non-card payout transactions | | `reference` | Yes | Unique non-card payout reference per terminal (max 40 chars). Idempotent — duplicate references with the same amount/currency will return the existing transaction. | | `description` | Yes | Human-readable non-card payout description (max 100 chars) | | `currency` | Yes | ISO 4217 currency code (e.g., `INR`, `EUR`, `USD`) | | `amount` | Yes | Amount in minor units, as an integer (e.g., `10000` = 100.00 INR). Decimal or fractional values such as `100.00` or `"100.00"` are rejected at request validation. | | `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 non-card payout completes (approved, declined, canceled, or error) | | `options` | No | Provider-specific options | *** ### Beneficiary Data — Bank Account (INR) When processing INR non-card 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 non-card payouts | | CAPTURE | No | Not applicable (single-step flow) | | VOID | No | Not supported | *** ### Supported Currency | Currency | Code | Description | | ------------- | ---- | ----------- | | Indian Rupee | INR | Supported for bank account non-card 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 | Non-card payout submitted and awaiting provider confirmation | No | | APPROVED | Non-card payout successfully processed and funds transferred | Yes | | DECLINED | Non-card 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 non-card 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 non-card payout status, as non-card payouts are typically processed asynchronously. ## Go further **Payout Direct API** Full request and response reference. [View reference](direct-api-integration.md) **OCT (Original Credit Transaction)** Push funds directly to a cardholder’s card. [Read guide](../oct/index.md) **Webhooks** Get notified when a payout changes status. [View reference](../webhooks/index.md) --- ## Source: Payouts › Non-card Payouts › Direct API Integration URL: https://docs.altitudepay.solutions/references/payout/direct-api-integration.html # Transfer funds to a beneficiary’s bank account from your backend Send the beneficiary’s bank details to `/api/transactions/payout`; INR payouts need `account_number`, `account_name` and `ifsc`. The first response may be `PENDING`, with the final status delivered by webhook. How it works: 1. **Your backend** — `POST /api/transactions/payout` 2. **Altitude Pay** — May respond PENDING first 3. **Your webhook endpoint** — Receives the final status 4. **Your backend** — Verifies it with the Transaction Status API ## Non-card Payouts – API Integration This page provides guidance for integrating **Non-card Payouts** using the **Direct API approach**. Non-card 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 non-card 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 non-card payout request, specify the beneficiary type in the `beneficiary.type` field: | Type | Description | |------|-------------| | `bank_account` | Bank account transfer (required for INR non-card payouts) | *** ### Required Beneficiary Data (INR Bank Account) Each INR non-card 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** and must be an integer. For INR, `10000` represents **100.00 INR**. Decimal or fractional values (e.g., `100.00` or `"100.00"`) are rejected at request validation. *** ### 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: Payouts › OCT (Original Credit Transaction) › Overview URL: https://docs.altitudepay.solutions/references/oct/ # Push funds to a cardholder’s card with a card payout Send `transaction_type` `OCT` to `/api/transactions/authorize` with only the encrypted card number. No CVV, expiration date, 3D Secure or `browser_info` is needed. > **Direct API only.** Hosted Payment Page and Embedded Fields do not support card payout (OCT) transactions. How it works: 1. **Your backend** — `POST /api/transactions/authorize` 2. **Altitude Pay** — Decrypts the card number · routes to a provider 3. **Payment provider** — Processes the OCT 4. **Your backend** — Receives the transaction response ### 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, a card payout (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" Card payout (OCT) transactions are only supported via the **Direct API** integration. **Hosted Payment Page (HPP)** and **Embedded Fields (EF)** integration types are **not supported** for card payout (OCT) transactions. ### Key Characteristics 1. **Push Payment** * A card payout (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** * Card payout (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 card payout (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, as an integer (e.g., `10000` = 100.00). Decimal or fractional values such as `100.00` or `"100.00"` are rejected at request validation. | | `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 (recipient) details — see [Customer Fields](#customer-fields) below | | `customer_ip` | No | Customer IP address | | `metadata` | No | Optional key-value pairs | #### Customer Fields Card payouts (OCT) require the **recipient's** identification and address details. All fields below are required: | Field | Required | Notes | |-------|----------|-------| | `customer.first_name` | Yes | Recipient's first name | | `customer.last_name` | Yes | Recipient's last name | | `customer.email` | Yes | Recipient's email address | | `customer.phone` | Yes | Recipient's phone number in [E.164 format](https://en.wikipedia.org/wiki/E.164) (e.g., `+1234567890`) | | `customer.address` | Yes | Street address | | `customer.city` | Yes | City | | `customer.country` | Yes | ISO 3166-1 alpha-2 country code (e.g., `BA`, `DE`, `US`) | | `customer.postal_code` | Yes | Postal / ZIP code | Example: ```json "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" } ``` ### 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 card payout (OCT) transactions.** Card payouts (OCT) must be processed via the [API integration](../../integration-types/api-integration.md) only. --- ## Source: Payouts › OCT (Original Credit Transaction) › Authorize URL: https://docs.altitudepay.solutions/references/oct/authorize.html # Send funds to a cardholder’s card with one OCT request Call `POST /api/transactions/authorize` with `transaction_type` set to `OCT` and only the encrypted card number. No 3D Secure runs, so `action`, `redirect` and `form_submit` are always `null`. [OAD(../../assets/openapi/openapi-oct-authorize.yaml)] --- ## Source: Payouts › Account Funding Transactions (AFT) URL: https://docs.altitudepay.solutions/references/aft/ # Send VISA and Mastercard funding data with a card payment Add an `options.funding` object to a `PURCHASE` or `AUTHORIZE` request on `/api/transactions/authorize`. VISA requires `business_application_identifier` and Mastercard requires `identifier_type`. ## Overview An Account Funding Transaction (AFT) is a card transaction used to fund an account — for example, person-to-person transfers or funds disbursements. VISA and Mastercard require additional data fields when processing AFT transactions. To process an AFT, include the `options.funding` object in your `/api/transactions/authorize` request alongside a standard `PURCHASE` or `AUTHORIZE` transaction. *** ## Request Structure Add a `funding` object inside the `options` field of the standard `/api/transactions/authorize` request: ```json { "reference": "your-reference", "terminal_id": "your-terminal-id", "transaction_type": "PURCHASE", "currency": "EUR", "amount": 10000, "payment_method": { "type": "card", "data": { "encrypted_card_number": "...", "encrypted_cvv": "...", "encrypted_expiration_month": "...", "encrypted_expiration_year": "..." } }, "customer": { ... }, "browser_info": { ... }, "metadata": {}, "return_url": "https://merchant.example.com/return", "options": { "funding": { "business_application_identifier": "funds_disbursement", "receiver": { "first_name": "John", "last_name": "Doe", "country": "US", "account_number": "1234567890", "account_number_type": "card_account", "address": "123 Main St", "city": "New York", "state": "NY" }, "sender": { "name": "Jane Smith", "reference_number": "INV-12345", "country": "GB", "address": "456 High St", "city": "London", "state": "LDN" } } } } ``` *** ## Funding Parameters ### Top-Level Fields | Field | Type | Required | Description | |---|---|---|---| | `business_application_identifier` | `string` | Yes (VISA) | Type of VISA Funding Transaction. See [BAI values](#business-application-identifier-values) below | | `identifier_type` | `string` | Yes (Mastercard) | Type of Mastercard Funding Transaction. See [Identifier Type values](#identifier-type-values) below | !!! note "Scheme-specific requirements" VISA requires `business_application_identifier`. Mastercard requires `identifier_type`. Include the appropriate field based on the card scheme being used. *** ### Receiver Object Details about the person or account receiving the funds. | Field | Type | Required | Description | |---|---|---|---| | `first_name` | `string` | Conditionally | Receiver's first name | | `last_name` | `string` | Conditionally | Receiver's last name | | `country` | `string(2)` | Conditionally | Receiver's country code in ISO 3166 | | `account_number` | `string` | Conditionally | Receiver's account number. Required for both VISA and Mastercard | | `account_number_type` | `string` | Conditionally (Mastercard) | Receiver's account number type. See [Account Number Type values](#receiver-account-number-type-values) below | | `address` | `string` | Conditionally (VISA) | Receiver's address | | `city` | `string` | Conditionally (VISA) | Receiver's city. Required for VISA with Canadian cards | | `state` | `string` | Conditionally (VISA) | Receiver's state. Required for VISA with Canadian cards | *** ### Sender Object Details about the person or entity sending the funds. | Field | Type | Required | Description | |---|---|---|---| | `name` | `string` | Conditionally (VISA) | Sender's name. Required for VISA | | `reference_number` | `string(16)` | Conditionally (VISA) | Sender's reference number (e.g. invoice number). Required for VISA. Limited to 16 characters | | `country` | `string` | Conditionally (VISA) | Sender's country. Required for VISA. If not provided, billing address is used | | `address` | `string` | Conditionally (VISA) | Sender's address. Required for VISA. If not provided, billing address is used | | `city` | `string` | Conditionally (VISA) | Sender's city. Required for VISA. If not provided, billing address is used | | `state` | `string` | Conditionally (VISA) | Sender's state. Required for VISA with Canadian cards. If not provided, billing address is used | *** ## Allowed Values ### Business Application Identifier Values VISA `business_application_identifier` accepted values: | Value | |---| | `funds_disbursement` | | `pension_disbursement` | | `account_to_account` | | `bank_initiated` | | `fund_transfer` | | `person_to_person` | | `prepaid_card_load` | | `wallet_transfer` | | `liquid_assets` | ### Identifier Type Values Mastercard `identifier_type` accepted values: | Value | Required MCCs | |---|---| | `general_person_to_person` | 4829, 6538, 6540 | | `person_to_person_card_account` | 4829 | | `own_account` | 4829, 6051, 6211, 6538, 6540, 7800, 7801, 7802, 7994, 7995, 9406 | | `own_credit_card_bill` | 4829, 6538 | | `business_disbursement` | any | | `government_or_non_profit_disbursement` | any | | `rapid_merchant_settlement` | any | | `general_business_to_business` | any | | `own_staged_digital_wallet_account` | any | | `own_debit_or_prepaid_card_account` | any | ### Receiver Account Number Type Values `account_number_type` accepted values: | Value | Description | |---|---| | `rtn_and_bank_account_number` | Routing Transit Number and Bank Account Number | | `iban` | International Bank Account Number | | `card_account` | Card Account | | `email` | Electronic Mail | | `phone_number` | Phone Number | | `bank_account_number_and_bic` | Bank Account Number and Business Identifier Code | | `wallet_id` | Wallet ID | | `social_network_id` | Unique Identifier for Social Network Application | | `other` | Any other type | *** ## Examples ### VISA AFT — Funds Disbursement ```json { "options": { "funding": { "business_application_identifier": "funds_disbursement", "receiver": { "first_name": "John", "last_name": "Doe", "country": "US", "account_number": "1234567890", "account_number_type": "card_account" }, "sender": { "name": "Jane Smith", "reference_number": "INV-12345", "country": "GB" } } } } ``` ### Mastercard AFT — Person-to-Person ```json { "options": { "funding": { "identifier_type": "general_person_to_person", "receiver": { "first_name": "Bob", "last_name": "Smith", "country": "DE", "account_number": "DE89370400440532013000", "account_number_type": "iban" } } } } ``` *** ## Supported Transaction Types AFT parameters can be included on the following transaction types: | Transaction Type | Supported | |---|---| | `PURCHASE` | Yes | | `AUTHORIZE` | Yes | *** ## Notes - If the `options` or `options.funding` object is omitted, the transaction is processed as a standard (non-AFT) transaction. - Fields marked as "Conditionally" required depend on the card scheme (VISA vs Mastercard) and the issuing country of the card. - For VISA sender fields: if `country`, `address`, `city`, or `state` are not provided, the billing address from the transaction is used as fallback. --- ## Source: Operate › Transaction Management › Overview URL: https://docs.altitudepay.solutions/references/transaction-management/ # Capture, refund and void existing transactions Send follow-up operations to `/api/transactions/manage` with the original order reference. Webhooks tell you when a transaction changes status. Follow-up operations: - `AUTHORIZE` → `CAPTURE`: Collect held funds - `AUTHORIZE` → `VOID`: Release held funds - `PURCHASE` → `REFUND`: Return funds Availability depends on your PSP integration and the credentials issued to your merchant account. ## Manage a transaction `CAPTURE` on a `AUTHORIZE` transaction Collects funds from a previously authorized transaction. Supports partial or full capture up to the original authorized amount. Request rules | Field | Description | | ----- | ----------- | | `reference` | The original order reference. | | `amount` | Less than or equal to the original transaction amount. | | `currency` | Same as the original transaction. | | `parent_transaction_id` | Optional. Runs the operation against this specific transaction. | | `options` | Optional, for example `final_capture`. | === "cURL" ```bash title="POST /api/transactions/manage" curl -X POST https://api.altitudepay.solutions/api/transactions/manage \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "terminal_id": "TERM001", "reference": "ORDER-123456", "transaction_type": "CAPTURE", "amount": 4990, "currency": "EUR" }' ``` === "Node.js" ```javascript title="POST /api/transactions/manage" const res = await fetch('https://api.altitudepay.solutions/api/transactions/manage', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ terminal_id: 'TERM001', reference: 'ORDER-123456', // original order reference transaction_type: 'CAPTURE', amount: 4990, // full capture currency: 'EUR', }), }); const { status, approved } = await res.json(); ``` `REFUND` on a `PURCHASE` transaction Returns funds to the customer for a completed or captured transaction. Supports partial or full refund amounts. Request rules | Field | Description | | ----- | ----------- | | `reference` | The original order reference. | | `amount` | Less than or equal to the original transaction amount. | | `currency` | Same as the original transaction. | | `parent_transaction_id` | Optional. Runs the operation against this specific transaction. | | `options` | Optional, for example `final_capture`. | === "cURL" ```bash title="POST /api/transactions/manage" curl -X POST https://api.altitudepay.solutions/api/transactions/manage \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "terminal_id": "TERM001", "reference": "ORDER-123456", "transaction_type": "REFUND", "amount": 2000, "currency": "EUR" }' ``` === "Node.js" ```javascript title="POST /api/transactions/manage" const res = await fetch('https://api.altitudepay.solutions/api/transactions/manage', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ terminal_id: 'TERM001', reference: 'ORDER-123456', // original order reference transaction_type: 'REFUND', amount: 2000, // partial refund currency: 'EUR', }), }); const { status, approved } = await res.json(); ``` `VOID` on a `AUTHORIZE` transaction Cancels an authorized or pending transaction before capture, releasing the held funds back to the customer. Request rules | Field | Description | | ----- | ----------- | | `reference` | The original order reference. | | `amount` | Less than or equal to the original transaction amount. | | `currency` | Same as the original transaction. | | `parent_transaction_id` | Optional. Runs the operation against this specific transaction. | | `options` | Optional, for example `final_capture`. | === "cURL" ```bash title="POST /api/transactions/manage" curl -X POST https://api.altitudepay.solutions/api/transactions/manage \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "terminal_id": "TERM001", "reference": "ORDER-123456", "transaction_type": "VOID", "amount": 4990, "currency": "EUR" }' ``` === "Node.js" ```javascript title="POST /api/transactions/manage" const res = await fetch('https://api.altitudepay.solutions/api/transactions/manage', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ terminal_id: 'TERM001', reference: 'ORDER-123456', // original order reference transaction_type: 'VOID', amount: 4990, // original amount currency: 'EUR', }), }); const { status, approved } = await res.json(); ``` ## Receive webhooks Altitude Pay sends an HTTP POST to your endpoint when a transaction is processed. Every request is signed with a JWT. 1. **Register your endpoint** Webhook setup is not self-service. Send your endpoint URL to the support team; contact details are in your client portal. 2. **Verify the signature** Hash the raw body with SHA-512 and compare it to `SHA512` in the JWT `data` claim. Then verify the JWT with the merchant’s public key. 3. **Respond quickly** Return a `2xx` status, ideally within 2 seconds. Reject invalid requests with `401`. 4. **Handle retries** Failed deliveries retry with exponential backoff for up to 24 hours. Make processing idempotent. === "Node.js" ```javascript title="webhook.js · your server" app.post('/webhooks/payments', (req, res) => { const token = req.headers.authorization.split(' ')[1]; const { data } = jwt.decode(token); const digest = crypto.createHash('sha512') .update(req.rawBody).digest('hex'); if (digest !== data.SHA512) return res.sendStatus(401); try { jwt.verify(token, merchantPublicKey); } catch { return res.sendStatus(401); } res.sendStatus(200); handleEvent(req.body); // idempotent }); ``` ## Transaction Manage API Documentation Welcome to the **Transaction Manage API**, part of the Altitude Pay payment 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 #### Supported Transaction Types The gateway supports two categories of transaction types: **management transaction types** used to perform follow-up operations on existing transactions via this API, and **initial transaction types** used when creating a new transaction. ##### Management Transaction Types These transaction types are submitted to `/api/transactions/manage` to perform follow-up operations on an existing transaction: | Transaction Type | Description | | ---------------- | ----------- | | `CAPTURE` | Collects funds from a previously authorized (`AUTHORIZE`) transaction. Supports partial or full capture up to the original authorized amount. | | `REFUND` | Returns funds to the customer for a completed or captured transaction. Supports partial or full refund amounts. | | `VOID` | Cancels an authorized or pending transaction before capture, releasing the held funds back to the customer. | > **Note**: Availability of specific management transaction types depends on the underlying integration with the payment service provider (PSP) and the credentials issued to your merchant account. ##### Initial Transaction Types These transaction types are submitted at the point of authorization to create a new transaction: | Transaction Type | Endpoint | Description | | ---------------- | -------- | ----------- | | `PURCHASE` | `/api/transactions/authorize` | Combined authorization and immediate capture in a single step. Funds are reserved and collected at once. The most common type for standard card payments. | | `AUTHORIZE` | `/api/transactions/authorize` | Reserves (holds) funds on the customer's card without capturing them immediately. A separate `CAPTURE` management operation must follow to collect the funds. Use when the final charge amount may change (e.g. hotel reservations, car rentals). | | `OCT` | `/api/transactions/authorize` | Original Credit Transaction — pushes funds directly to a cardholder's card. Used for payouts, disbursements, and marketplace seller payments. | | `PAYOUT` | `/api/transactions/authorize` | Sends funds to a card recipient via a payout flow. | | `PAYMENT` | `/api/transactions/payment` | Alternative payment methods such as Open Banking, UPI, wallets, bank transfer, or QR code. The `transaction_type` is fixed to `PAYMENT` automatically for this endpoint. | --- #### 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 | | ----------- | --------------------------------------- | | UAT | `https://uat-api.altitudepay.solutions` | | Production | `https://api.altitudepay.solutions` | #### 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 ## Go further **Transaction Status API** Look up the current status of a transaction. [View reference](../../transaction-api/transaction-status-api.md) **Supported transaction statuses** The full transaction lifecycle. [View statuses](../../transaction-api/supported-transaction-statuses.md) **Handling Declines** Understand and respond to declined transactions. [Read guide](../handling-declines/index.md) --- ## Source: Operate › Transaction Management › Manage transaction API URL: https://docs.altitudepay.solutions/references/transaction-management/manage-transaction-api.html # Run a follow-up operation against an existing transaction Send `POST /api/transactions/manage` with the original order `reference`, the same `currency` and an `amount` no greater than the original. Set `parent_transaction_id` to run it against a specific transaction. [OAD(../../assets/openapi/openapi-manage-transaction.yaml)] --- ## Source: Operate › Webhooks › Overview URL: https://docs.altitudepay.solutions/references/webhooks/ # Receive real-time event notifications instead of polling Altitude Pay sends a signed HTTP `POST` with a JSON payload to your endpoint when an event occurs. Verify the JWT in the `Authorization` header and the SHA-512 digest of the body before you process it. > **Not self-service.** Contact Altitude Pay support with your endpoint URL to have webhook delivery configured. How it works: 1. **Altitude Pay** — Sends a signed POST when an event occurs 2. **Your webhook endpoint** — Checks the digest and signature · returns 2xx 3. **Altitude Pay** — Retries on 4xx, 5xx or timeout for up to 24 hours ### Introduction Webhooks allow your application to receive real-time notifications from Altitude Pay 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), Altitude Pay 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 Altitude Pay 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@altitudepay.solutions. ### Security and Signature Verification Every webhook request from Altitude Pay includes a **JWT token** in the `Authorization` header, as `Bearer `.\ The JWT is **signed** with RS256 and must be verified to ensure the authenticity of the webhook. Signature details: * The JWT payload contains a `data` claim: ```json { "SHA512": "" } ``` * The `` is the SHA-512 hash of the webhook request body, encoded as **lowercase hexadecimal** (128 characters). It is not Base64. * Hash the raw request body exactly as you received it, before you parse or re-serialise the JSON. Your digest must match the one in the JWT payload. * The JWT must also be cryptographically verified using the correct public key: see [Which key signs a webhook](#which-key-signs-a-webhook). #### Which key signs a webhook The key depends on the event, and the JWT's `sub` claim tells you which one was used: | Events | Signed with | `sub` claim | | ------ | ----------- | ----------- | | Every [Transaction Processed](transaction-processed-webhook.md) event, for example `transaction-svc:transaction:purchase:approved`
`tokenization-webhook:payment-method:tokenized` | Your merchant private key | Your merchant ID | | `transaction-svc:transaction:created`
`transaction-svc:account:transaction:created`
`transaction-svc:account:transaction:updated`
`transaction-svc:transaction:full:created`
`transaction-svc:transaction:full:updated` | The Altitude Pay private key | The Altitude Pay tenant ID | To choose the key, decode the JWT without verifying it and read `sub`: * If `sub` is your merchant ID, verify with your merchant public key, the same key you use to encrypt card data. * If `sub` is the Altitude Pay tenant ID, verify with the Altitude Pay public key. Ask Altitude Pay support for this key and the tenant ID when you set up webhooks. * If `sub` is anything else, reject the webhook. ### 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, Altitude Pay 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 Altitude Pay, 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 raw request body yourself using SHA-512, and encode the result as lowercase hex. 6. **Compare** your calculated hash to the `SHA512` value inside the `data` claim. 7. **Verify** the JWT's signature using the public key that matches its `sub` claim (see [Which key signs a webhook](#which-key-signs-a-webhook)). * `sub` is your merchant ID: use your **merchant public key**. * `sub` is the Altitude Pay tenant ID: use the **Altitude Pay public key**. 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`). #### Verify a webhook Check the digest, then the signature, and answer `2xx` only when both pass. Any other answer is retried for up to 24 hours. [Which key to use](#which-key-signs-a-webhook) ```mermaid sequenceDiagram participant Gateway participant Server as Your webhook endpoint Gateway->>Server: Webhook event, JWT in Authorization Server->>Server: Decode the JWT without verifying it Server->>Server: SHA-512 the raw body, compare with data.SHA512 Server->>Server: Verify the signature with the key for sub alt Both checks pass Server-->>Gateway: 2xx Server->>Server: Process the event else Either check fails Server-->>Gateway: 401 Unauthorized end ``` ## Summary * The digest comparison ensures **payload integrity** (body wasn't tampered). * The JWT signature ensures **origin authenticity** (Altitude Pay sent it, not someone else). * Both must pass. --- ## Source: Operate › Webhooks › Transaction Processed Webhook URL: https://docs.altitudepay.solutions/references/webhooks/transaction-processed-webhook.html # Sync your system with each transaction outcome without polling Altitude Pay sends a `POST` with the event and the full transaction payload when a transaction is approved, declined, voided, expires or fails. It is signed with your merchant private key and retried for up to 24 hours. > **Expect duplicates.** The same webhook can be delivered more than once, so make your handler idempotent. How it works: 1. **Payment provider** — Approves or declines the transaction 2. **Altitude Pay** — Sends the event with a signed JWT 3. **Your webhook endpoint** — Verifies the JWT and the SHA-512 digest ### 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 Altitude Pay 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": "PURCHASE", "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. * Altitude Pay retries failed webhook deliveries with exponential backoff for up to 24 hours. * Duplicate webhook deliveries are possible: ensure your webhook handlers are idempotent. --- ## Source: Operate › Transaction Status API URL: https://docs.altitudepay.solutions/transaction-api/transaction-status-api.html # Look up the current status of a transaction by its ID Send `GET /api/transactions/{id}/status` with a bearer token. The response tells you whether the transaction was `found` and returns it with its `status`. [OAD(../assets/openapi/transaction-status-api.yaml)] --- ## Source: Operate › Supported transaction statuses URL: https://docs.altitudepay.solutions/transaction-api/supported-transaction-statuses.html # Know which transaction statuses are final and which can still change The `TransactionStatus` enum has two transitional statuses, `CREATED` and `PENDING`, and five terminal ones. Once a transaction reaches a terminal status it is finalized and immutable. Status transitions: - `CREATED` → `PENDING`: Submitted - `PENDING` → `APPROVED`: Authorized - `PENDING` → `DECLINED`: Rejected - `PENDING` → `SESSION_EXPIRED`: Timeout - `CREATED` → `INVALID`: Validation error CREATED and PENDING can also move to CANCELED. 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 #### From created to final A transaction can still change while it is `CREATED` or `PENDING`. The other five statuses are final. [Check a status](transaction-status-api.md) ```mermaid stateDiagram-v2 [*] --> CREATED CREATED --> PENDING : submitted CREATED --> CANCELED : user cancel CREATED --> INVALID : validation failure PENDING --> APPROVED : authorized PENDING --> DECLINED : rejected PENDING --> SESSION_EXPIRED : timeout PENDING --> CANCELED : cancel APPROVED --> [*] DECLINED --> [*] SESSION_EXPIRED --> [*] CANCELED --> [*] INVALID --> [*] ``` ## 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: Operate › Handling Declines URL: https://docs.altitudepay.solutions/references/handling-declines/ # Base your decline handling on stable, normalized processing codes Every response and webhook carries `processing_code`, which the gateway normalizes from the PSP’s raw response. Branch on it, and keep `payment_provider_response_code` for logs and support. How it works: 1. **Payment provider** — Raw response code · varies per provider 2. **Altitude Pay** — Normalizes it into a processing code 3. **Your backend** — Branches on processing_code · logs the raw code When a transaction does not result in an approval, the response and the Transaction Processed Webhook carry four fields that explain the outcome. Understanding the difference between them is essential for building reliable decline-handling logic. ## Response Fields Every transaction response and webhook payload includes the following outcome fields: | Field | Source | Stability | Intended Use | | --------------------------------- | ---------------------- | ----------------------------------- | ---------------------------------------------------------------------------- | | `processing_code` | Gateway (normalized) | Stable, enumerated | **Primary field for branching integration logic.** | | `response_message` | Gateway (normalized) | Stable, human-readable | Display-safe summary matching `processing_code`. | | `payment_provider_response_code` | Upstream PSP (raw) | Varies per provider and over time | Logging, support investigations, reconciliation. | | `payment_provider_response_message` | Upstream PSP (raw) | Varies per provider and over time | Logging and diagnostics only — do not branch on this value. | !!! tip "Recommendation" **Always map your decline-handling logic to `processing_code`.** The gateway normalizes upstream PSP responses into a stable, enumerated set of processing codes. PSP codes differ between providers, change over time, and are not guaranteed to be stable — use them for logs and support tickets, not for logic. ## Processing Code Reference Processing codes are grouped into families by the leading digit. Handling guidance for each family is summarized below the table. ### `0000` — Approved | Code | Description | | ------ | --------------------------------- | | `0000` | Transaction approved successfully | ### `1xxx` — Transaction lifecycle and basic declines | Code | Description | | ------ | ----------------------------------------------------------------- | | `1000` | Transaction declined (generic decline from the issuer/PSP) | | `1001` | Transaction failed | | `1002` | Transaction pending (not yet in a terminal state) | | `1003` | Transaction canceled | | `1004` | Transaction declined — insufficient funds | | `1005` | Session expired | ### `2xxx` — Invalid request Validation and configuration errors. These will not succeed on retry with the same input. | Code | Description | | ------ | -------------------------------------------------------------------- | | `2000` | Invalid request (generic) | | `2001` | Payment method expired | | `2002` | Payment method invalid | | `2003` | Payment method not found or invalid | | `2004` | Payment method type not supported | | `2005` | Payment method blocked | | `2006` | Payment method inactive | | `2007` | Installments not supported for this payment method | | `2008` | Payment link expired | | `2009` | Payment link not found | | `2010` | Transaction type not supported | | `2011` | Transaction feature not supported | | `2012` | Installments not supported | | `2013` | Terminal not found | | `2014` | Terminal inactive | | `2015` | Terminal not accepting payments | | `2016` | Terminal does not support installments | | `2017` | Merchant inactive | | `2018` | Feature not enabled | | `2019` | Merchant does not support tokenization | | `2020` | Card CVV invalid | | `2021` | Payment provider account inactive | | `2022` | Shop (location) inactive | | `2023` | Account inactive | | `2024` | Payment provider inactive | ### `3xxx` — Authentication (3DS) | Code | Description | | ------ | ---------------------------------------------------- | | `3000` | Authentication not performed or incomplete | | `3001` | Authentication rejected by the issuer | | `3002` | Authentication unavailable | ### `4xxx` — Risk Declined by the gateway's risk engine before reaching the PSP. Retrying the same transaction will produce the same result. | Code | Description | | ------ | -------------------------------------------------------------- | | `4100` | Blacklist rule matched (customer, card, or merchant) | | `4101` | Fraud check failed | | `4200` | Velocity check failed (generic) | | `4201` | Velocity check failed — customer limit exceeded | | `4202` | Velocity check failed — merchant limit exceeded | | `4203` | Velocity check failed — PSP limit exceeded | ## Handling Guidance Use the following patterns to map processing codes to user-facing behavior: | Family | Retryable? | Recommended action | | ------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------- | | `0000` | — | Treat the transaction as successful. | | `1000`, `1001`, `1004` | Sometimes | Prompt the customer to try a different payment method or contact their issuer. Do not retry the same card automatically. | | `1002` | Wait | Do not finalize the order. Wait for the terminal webhook (`transaction:approved` / `:declined`) or poll the Transaction Status API. | | `1003` | No | Transaction was canceled; offer the customer to start a new checkout. | | `1005` | No | The checkout session has expired — start a new session. | | `2001`, `2002`, `2003`, `2020` | No (same input) | Prompt the customer to correct card details or use a different card. | | `2004`, `2005`, `2006`, `2007`, `2010`, `2011`, `2012`, `2019` | No | Offer an alternative payment method. The selected method/feature is not supported for this transaction. | | `2008`, `2009` | No | The payment link is no longer valid. Generate a new payment link. | | `2013` – `2018`, `2021` – `2024` | No (configuration) | Non-retryable configuration issues. Log the response and contact Altitude Pay support. | | `3000`, `3001`, `3002` | No (as-is) | 3DS authentication did not succeed. Prompt the customer to retry authentication or use a different card. | | `4xxx` | No | The transaction was blocked by the risk engine. Do not retry automatically. Inform the customer and, where appropriate, escalate to support. | !!! note "Logging PSP details" Always persist `payment_provider_response_code` and `payment_provider_response_message` alongside `processing_code` in your transaction records. These raw values are invaluable when reconciling with PSPs or when contacting Altitude Pay support about a specific decline. --- ## Source: Changelog URL: https://docs.altitudepay.solutions/changelog.html # Track every change to the Altitude Pay documentation All notable changes to these docs are listed by date and grouped into Added, Changed, Fixed and Removed. All notable changes to the Payment Gateway documentation will be documented on this page. --- ## 2026-09-30 ### Fixed - [Webhooks](references/webhooks/index.md) now says the SHA-512 digest is lowercase hex of the raw request body, and a new "Which key signs a webhook" section lists which events are signed with your merchant key and which with the Altitude Pay key, and how the JWT's `sub` claim tells them apart. The page previously said every transaction-related event used the merchant key. - On phones, the menu now opens over the whole screen, so its last items are no longer cut off at the bottom, and the current tab is listed like the others. - [Overview](index.md) and [Obtain Access Token](getting-started/authentication/obtain-access-token.md) now show the real token lifetime: 300 seconds on UAT and 60 seconds in production. They previously showed 3600. Read `expires_in` from each token response rather than hard-coding it. ### Changed - The empty Transaction API overview page now redirects to [Transaction Status API](transaction-api/transaction-status-api.md). - The webhook verification flow on [Webhooks](references/webhooks/index.md) is now a short sequence diagram in the same style as the other diagrams. - The transaction lifecycle on [Supported transaction statuses](transaction-api/supported-transaction-statuses.md) is redrawn: it reads left to right from `CREATED`, the five final statuses are grouped on the right, each status is in its colour and a legend explains them. The "Transaction Transition Rules" diagram, which showed the same transitions, is folded into it. ## 2026-09-29 ### Fixed - [Webhooks](references/webhooks/index.md), [Transaction Processed Webhook](references/webhooks/transaction-processed-webhook.md), [Handling Declines](references/handling-declines/index.md), [API Integration](integration-types/api-integration.md) and [Transaction Management](references/transaction-management/index.md) now use your payment gateway's name throughout, and Webhooks lists your gateway's support address. - [Embedded Fields](integration-types/embedded-fields.md) now names `PaymentComponent()` as the one function that starts the payment component. - The link to the Paysafe [Direct API Integration](references/paysafe-redirect/direct-api-integration.md) on the Paysafe customization page now stays on this site. ## 2026-09-28 ### Changed - New design for every documentation site. It has a one-row header with the tabs and search (open it with ⌘K or Ctrl K), and code samples sit in dark panels with a language bar and a copy button. Tables, callouts, status values, sequence diagrams and API reference blocks are restyled to match; diagrams now colour each participant by who runs it and carry a legend. Each site keeps its own brand colours, typeface and logo. - Dark mode on every site. It follows the system setting, and the button in the header switches it. - Pages no longer have a left navigation or an "On this page" column, so text, code samples and diagrams use the full width. Instead, a bar at the top of every page lists the sections of the current tab, with links to the pages of the current section. On phones the menu still lists every page. Diagrams now span the text column. - Headings, tables and notes no longer carry icons, and headings no longer show the ¶ link beside them. Links to sections still work as before. - Navigation regrouped into six tabs that follow an integration from start to finish: Get started, Integrate, Payment methods, Payouts, Operate and Changelog. Payment methods groups its pages into Alternative, Cards, Wallets and Redirects, the same categories as its method browser. Every page keeps its address. - New landing sections at the top of [Overview](index.md), [Hosted Payment Page](integration-types/hosted-payment-page.md), [Embedded Fields](integration-types/embedded-fields.md), [API Integration](integration-types/api-integration.md), [Alternative payments](references/payments/index.md), [Non-card Payouts](references/payout/index.md) and [Transaction Management](references/transaction-management/index.md). They cover who does what in the flow, an interactive preview of what the customer sees, the steps with code in cURL and Node.js, and where to go next. The existing content of each page follows unchanged. - Every other page now opens with its own hero, including every payment method, payout and operations page, the Get started pages and this changelog. It states what the page helps you do, gives a short summary and links to the page's key sections. Most heroes also show who does what, the test values or the environment URLs. - [Alternative payments](references/payments/index.md) now opens with a Payment methods browser. - It replaces "Choose a method", "Create a payment" and the "More payment methods" links. - Pick Alternative, Cards, Wallets or Redirects to see each of its methods in turn: how it works, its fields and a request example. The Jump to links scroll to a method on the page, or open the category's other pages. - Cards also has a Test tab with the test cards and simulated 3DS flows. - A new Processing flows section shows the Payments and OCT flows. - Sequence diagrams are redrawn on every page: - numbered steps; - "IF" and "OR" branches; - step banners; - a legend that tells requests from responses. Their text can now be selected and found with the browser's search. - Every landing page's code panel shows the endpoint in its header. The second hero button jumps to a section on the same page. Each landing page ends with links to the previous and next tab and to its API reference. - Moving between pages no longer reloads the whole site, and pages, buttons, tabs, switches and previews animate as they change. Clicking a card, button or link that opens another page first scrolls smoothly back to the top, and Back returns to where you were. The tabs and Jump to links at the top of a page, and in the Payment methods browser, open the next page without scrolling. You stay at its hero, which describes the page you picked, or at its content if you had scrolled past the hero. On wide screens, hero bands with a diagram share one height, so the tabs below them stay in place as you switch pages. Movement is left out when the operating system asks for reduced motion. - `llms.txt` and `llms-full.txt` follow the new navigation and now also list the [Authentication Simulator](references/3ds/simplified-simulator.md) and [Account Funding Transactions](references/aft/index.md) pages. `llms-full.txt` includes each landing page's summary and fills in the brand's hosts and URLs in code samples. ### Fixed - The links to "Saving Cards for Future Payments" and "Creating a Customer" on [API Integration](integration-types/api-integration.md) now open those sections. - The Transaction Status API link on [Neogated (BLIK)](references/neogated/index.md) now opens the [Transaction Status API](transaction-api/transaction-status-api.md) page. - A missing page now shows a "Page not found" message with links back to the overview and the changelog. - The flow diagram in the [3D Secure card payment recipe](recipes/3ds-card-payment.md) now numbers every step and shows the full request-authentication path. --- ## 2026-09-16 ### Added - New optional `originator_url` request field on [Authorize](references/cards/authorize.md), [Payments](references/payments/direct-api-integration.md), [Hosted Payment Page](integration-types/hosted-payment-page.md) and the [Embedded Fields](integration-types/embedded-fields.md) session request: the URL of the website or page on which the customer started the purchase. It must be an `http`/`https` URL (a `#` fragment is allowed); if omitted or empty, the website configured on the terminal is used. The value stays on the transaction when it is retried or routed to another payment provider, and is forwarded to providers that require the originating website. --- ## 2026-09-15 ### Added - New [Deleting a Saved Card](integration-types/api-integration.md#deleting-a-saved-card) section on the [API Integration](integration-types/api-integration.md) page, documenting `DELETE /api/payment-methods/{payment_method_id}` — the previously undocumented endpoint for removing a stored card. It covers the required `payment_method:detach` permission, the `200` response carrying the payment method with `status: "DELETED"`, the fact that the call is idempotent and needs no `Idempotency-Key`, and the `403` and `422` responses. - An explanation of what deletion actually does: the card can no longer be charged, it is removed from the customer, the record is retained and still retrievable as `DELETED` for reconciliation and disputes, and the deletion cannot be undone. Permanent erasure of the stored card data (for example a GDPR erasure request) is noted as a separate operation handled through support. - Guidance on implementing a "remove card" action in a merchant application: the call carries merchant API credentials and must be made from the merchant backend, never from a client application — the publishable client key cannot delete payment methods. - New [Charging a Deleted Card](integration-types/api-integration.md#charging-a-deleted-card) error case, showing the `INVALID_REQUEST_PAYMENT_METHOD_INACTIVE` rejection returned when a transaction references a deleted payment method, and **Workflow 3: Customer Removes a Saved Card**. --- ## 2026-08-28 ### Removed - The **Paydor** and **Kynta** documentation sites have been retired. `docs.paydor.com` and `docs.kynta.com` are no longer built or published, and all Paydor- and Kynta-specific environment values, endpoints and examples have been removed from the documentation. --- ## 2026-08-27 ### Added - New **Altitude Pay** documentation site at [https://docs.altitudepay.solutions](https://docs.altitudepay.solutions), carrying the Altitude Pay brand — logo, favicon, the Altitude violet accent and the Figtree typeface, matching the Altitude Pay dashboard and login page. - Altitude Pay environment values on the [Environments](getting-started/environments.md) page: the UAT and Production OIDC Token URLs (realms `qore-uat` and `qore-prod`, shared with the Qore Payments platform) and API URLs. - Altitude Pay endpoints across the rest of the documentation — the token and session endpoints and the `embedded-fields.js` script tag on [Embedded Fields](integration-types/embedded-fields.md), the challenge notification URLs on [3D Secure](references/3ds/index.md), the base URLs on [Transaction Management](references/transaction-management/index.md), the Google Pay request example and gateway identifier on [Google Pay](references/google-pay/index.md), and the `llms.txt` URLs on [AI-Assisted Integration](getting-started/ai-assisted-integration.md). ### Fixed - The example token request on the [Hosted Payment Page](integration-types/hosted-payment-page.md) page no longer shows one specific brand's UAT authentication host in its comment; it now points to the [Environments](getting-started/environments.md) page for the OIDC Token URL of your own environment. --- ## 2026-08-20 ### Added - New **Adhere to Google policies** step in [Activation Prerequisites](references/google-pay/index.md#activation-prerequisites) on the [Google Pay](references/google-pay/index.md) page, linking both the Google Pay and Wallet API Acceptable Use Policy and the Google Pay API Terms of Service. Previously only the Acceptable Use Policy was referenced. - New explanation of the Google Pay `gateway` integration type in [Google Pay API Configuration](references/google-pay/index.md#google-pay-api-configuration), stating that the Payment Gateway performs all token decryption on the merchant's behalf, and describing how to set `gateway` and `gatewayMerchantId` in the `tokenizationSpecification` object, with a link to Google's gateway ID reference. - New [Apple Pay](references/apple-pay/index.md) page, giving Apple Pay its own entry under **References**. It covers Apple Pay tokenization (DPAN, cryptogram, ECI) and why Apple Pay is treated as a fully authenticated `CRYPTOGRAM_3DS` transaction, Apple's branding and acceptable-use obligations, activation prerequisites, the supported card brands, and the two available integration methods — [Wallet Redirect](references/wallet-redirect/index.md) where the PSP renders the Apple Pay sheet, and [Network Token](references/network-token/index.md) for merchants who decrypt the `PKPaymentToken` themselves. ### Changed - The [Hosted Payment Page](references/google-pay/index.md#hosted-payment-page) section of the Google Pay page now opens by explaining that the hosted checkout integration simplifies offering Google Pay to customers. - The [Wallet Redirect](references/wallet-redirect/index.md), [Network Token](references/network-token/index.md), [Google Pay](references/google-pay/index.md) and [API Integration](integration-types/api-integration.md) pages now cross-link to the new Apple Pay page. ### Fixed - Corrected the [Hosted Payment Page](integration-types/hosted-payment-page.md) request contract: the required `return_url` field did not exist and has been replaced with the three actual required redirect fields, `success_url`, `cancel_url` and `error_url`; `description` is now listed as required (1–100 characters); `reference` is documented as capped at 40 characters; and the previously undocumented `language` field and `options` object (`number_of_instalments`, `skip_three_ds`, `cvv_less`) have been added. The "Backend Setup" section now notes that its example targets the separate `/api/sessions` endpoint used by Embedded Fields, not `/api/hosted-payment-page`. - Fixed incorrect Keycloak realm names on the [Embedded Fields](integration-types/embedded-fields.md) page for Paydor, Kynta and Qore Payments, which previously pointed to Finrelay's `uat`/`production` realms instead of each brand's own realm (`paydor-uat`/`paydor-prod`, `kynta-uat`/`kynta-prod`, `qore-uat`/`qore-prod`). - Fixed the same realm mismatch in the Production rows for Paydor and Kynta on the [Environments](getting-started/environments.md) page. - `build-all.ps1` now builds the Qore Payments site and generates its `llms.txt`; it previously only built Finrelay, Paydor, Kynta and AdriaPay. --- ## 2026-09-06 ### Added - New [Bank Redirect (Interac & Open Banking)](references/bank-redirect/index.md) page documenting the `interac-redirect` and `openbanking-redirect` payment method types. It covers the Hosted Payment Page, Embedded Fields and Direct API flows, the embedded-fields stub script keys, and flow diagrams for both the HPP and API paths. It states that both run on the **payment** transaction type — the terminal needs `TRX_TYPE_PAYMENT` and they are initiated through [`POST /api/transactions/payment`](references/payments/direct-api-integration.md), not the card `authorize` endpoint. - The Bank Redirect page states that the outcome is asynchronous: `authorize` always returns `PENDING`, the transaction-processed webhook is the source of truth, and a payment the customer abandons is not cancelled automatically — it stays `PENDING` until the bank reports an outcome, so the Transaction Status API should be used to check where it stands. - Documented the limitations of both methods: purchase only, no stored payment method or recurring use, and no 3-D Secure step — the customer's bank performs its own authentication. ## 2026-08-19 ### Added - New [Billing Address](references/google-pay/index.md#billing-address) section on the [Google Pay](references/google-pay/index.md) page, explaining how to request a billing address via `billingAddressRequired` and `billingAddressParameters` in the Google Pay API request, with a request example and a link to Google's `BillingAddressParameters` reference. - New **Google Pay branding** note on the [Google Pay](references/google-pay/index.md) page: merchants offering Google Pay must use the official Google Pay logo and button assets in compliance with the Google Pay Android and web brand guidelines, without modifications to asset colors, proportions, or appearance. ### Changed - The [Google Pay](references/google-pay/index.md) introduction now uses Google's standard description — *Google Pay™ lets users pay with their cards stored in their Google Account* — and the page title carries the ™ mark. --- ## 2026-08-13 ### Added - New [Supported Languages](integration-types/hosted-payment-page.md#supported-languages) section on the [Hosted Payment Page](integration-types/hosted-payment-page.md) page. It lists the four available languages, explains how to set the default language per merchant in the Merchant Portal (**Accounts → Merchants** → **Hosted Payment Page customizations** → **Language**), covers the language selector customers can use on the payment page, and documents the fallback to English. - Added **Romanian** (`ro-RO`) and **Swedish** (`sv-SE`) to the [Supported Locales](integration-types/embedded-fields.md#supported-locales) table for Embedded Fields. - New [Initialization Options](integration-types/embedded-fields.md#initialization-options) table on the [Embedded Fields](integration-types/embedded-fields.md) page, documenting `clientKey`, `sessionId`, `environment` and `locale`. Previously these were only shown inline in code samples. Notes that when `locale` is omitted the customer's browser language is used if it is supported, otherwise English. ### Changed - The [Customization](integration-types/hosted-payment-page.md#customization) section on the Hosted Payment Page page now names the available customizations, including the display language, and links to the new Supported Languages section. --- ## 2026-06-11 ### Added - New [Simplified Authentication Simulator](references/3ds/simplified-simulator.md) reference page under **3D Secure**, documenting the test-card-driven 3DS simulator (frictionless, interactive redirect approve/decline, form-submit authenticated/declined, and frictionless-authenticated-with-insufficient-funds) and the metadata escape hatch for unmapped cards. ### Changed - Made the Simplified Authentication Simulator the **default, recommended** option for testing 3DS. The [3D Secure](references/3ds/index.md#simulator) page and the [3DS Card Payment recipe](recipes/3ds-card-payment.md#testing-with-the-3ds-simulator) now lead with the test cards and reframe the metadata-driven `simulated_flow` simulator as the advanced option. --- ## 2026-06-02 ### Added - Added **iDEAL** (`paysafe-redirect-idl`) to the [Paysafe Redirect](references/paysafe-redirect/index.md) Supported Payment Methods list and the Direct API Integration examples, and added `paysafe-redirect-idl` to the Paysafe Redirect authorize OpenAPI schema. ### Fixed - Added the missing **PaySafe Wallet** (`paysafe-redirect-wlt`) code to the Paysafe Redirect authorize OpenAPI schema so the `payment_method.type` enum matches the documented Supported Payment Methods list. - Corrected an invalid payment method code in the Paysafe Redirect authorize OpenAPI response example (`paysafe-redirect-pcs` → `paysafe-redirect-psc`, PaySafeCard). --- ## 2026-05-26 ### Fixed - Corrected the Qore Payments production OIDC realm in [Environments](getting-started/environments.md) from `production` to `qore-prod`. --- ## 2026-05-20 ### Added - Added a [Supported Transaction Types](references/transaction-management/index.md) section to the Transaction Management page, listing all initial transaction types (`PURCHASE`, `AUTHORIZE`, `OCT`, `PAYOUT`, `PAYMENT`) and management transaction types (`CAPTURE`, `REFUND`, `VOID`) with descriptions. ### Changed - Clarified across [Payments](references/payments/index.md), [Payments — Direct API Integration](references/payments/direct-api-integration.md), [Payouts](references/payout/index.md), [Payouts — Direct API Integration](references/payout/direct-api-integration.md), [OCT](references/oct/index.md), [Hosted Payment Page](integration-types/hosted-payment-page.md), and [Embedded Fields](integration-types/embedded-fields.md) that the `amount` field must be an integer in minor units. Decimal or fractional values (for example `100.00` or the string `"100.00"`) are rejected at request validation with a clear error. --- ## 2026-05-18 ### Changed - Updated all request/response examples across [API Integration](integration-types/api-integration.md), [Hosted Payment Page](integration-types/hosted-payment-page.md), [Embedded Fields](integration-types/embedded-fields.md), [3D Secure](references/3ds/index.md), [Google Pay](references/google-pay/index.md), and [Webhooks](references/webhooks/transaction-processed-webhook.md) to use `PURCHASE` instead of `AUTHORIZE` as the example `transaction_type`. --- ## 2026-05-15 ### Added - New [Wallet Redirect (Google Pay & Apple Pay)](references/wallet-redirect/index.md) reference page covering the `google-pay-redirect` and `apple-pay-redirect` payment method types: how the PSP-hosted wallet flow works, HPP / Embedded Fields / Direct API behavior, the `authorize` request and response shape, and how it differs from the native [Google Pay](references/google-pay/index.md) integration. --- ## 2026-05-12 ### Changed - Updated the [Direct API Integration](references/payments/direct-api-integration.md) request schema: `description`, `country`, `customer_ip`, `return_url`, `error_url`, and `cancel_url` are now required. Added the new `error_url` and `cancel_url` fields for redirecting the customer after failed or canceled payments (previously a single `return_url` covered all outcomes). --- ## 2026-04-27 ### Added - New [API Integration Guidelines](getting-started/api-integration-guidelines.md) page in **Getting Started** covering recommended request hygiene (mandatory `User-Agent` header, HTTPS, correct `Content-Type`, clean payloads) and how to troubleshoot a 403 with an empty response body. ### Changed - Documented the required recipient `customer` fields on the [OCT](references/oct/index.md) request: `first_name`, `last_name`, `email`, `phone`, `address`, `city`, `country`, and `postal_code` are all required. Added a Customer Fields table with field-level notes (E.164 phone format, ISO 3166-1 alpha-2 country code) and a JSON example, and updated the OCT OpenAPI schema to mark these fields as required. --- ## 2026-04-24 ### Added - Documented the optional `customer_ip` field on the card **Authorize** request. Pass the end customer's IPv4/IPv6 address so fraud and 3DS risk checks see their IP rather than your backend's; if omitted, the gateway falls back to the IP of the incoming HTTP request ### Changed - Aligned the `customer_ip` description on the **Payments**, **Payouts**, and **OCT** request schemas with the Authorize page — noting IPv4/IPv6 support, the fallback to the HTTP request IP, and when to set it explicitly --- ## 2026-04-23 ### Added - Added field format requirements table to the [Encryption Example](references/cards/encryption-example.md) page, documenting accepted plain-text formats for card number, CVV, expiration month, and expiration year before encryption. - Added Card Payment Data Fields reference table to the [API Integration](integration-types/api-integration.md) page. ### Changed - Clarified that `encrypted_expiration_year` accepts both 2-digit (`26`) and 4-digit (`2026`) year formats in the Encryption Example page, API Integration page, and OpenAPI spec. - Clarified `phone` / `customer_phone_number` field descriptions across Embedded Fields, Hosted Payment Page, and Neogated Direct API Integration pages: phone numbers must be in **E.164 format** (e.g., `+12125551234`) and are validated using Google libphonenumber's `isPossibleNumber` check, which verifies the number is a plausible length for its country code. --- ## 2026-04-21 ### Changed - **Environments** page now shows the complete OIDC token URL (e.g. `https://auth.example.com/realms/production/protocol/openid-connect/token`) instead of separate OIDC URL and Realm fields, and groups UAT and Production values into distinct sections for easier reference --- ## 2026-04-20 ### Changed - Moved **Handling Declines** into its own subsection under References (at `references/handling-declines/index.html`) ### Added - Added **Handling Declines** reference page documenting the full list of `processing_code` values returned by the gateway (families `0xxx`, `1xxx`, `2xxx`, `3xxx`, `4xxx`), the distinction between gateway-normalized fields (`processing_code`, `response_message`) and raw PSP fields (`payment_provider_response_code`, `payment_provider_response_message`), and recommended retry/handling behavior per code family --- ## 2026-04-17 ### Added - Account Funding Transaction (AFT) documentation — how to include `options.funding` parameters for VISA and Mastercard funding transactions on PURCHASE and AUTHORIZE - Documented custom 3DS challenge notification URL (`options.three_ds.notification_url`) for Direct API integrations, including validation rules, the ACS callback flow, and the required `authentication-completed` follow-up call --- ## 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