.. meta:: :description: Hosted Fields integration with Cyber: embed the Hosted Fields JS SDK card fields into the Connecting Party payment page, exchange the card for a hostedFieldsToken and process the transaction with it. .. _hosted_payment_fields: Hosted Fields ############# .. role:: ex .. role:: code .. _hosted_payment_fields_introduction: Introduction ==================================================== In a regular payment form the card number, expiry date and CVV live in the DOM of the Connecting Party page: every script on the page can read them (including third-party ones — analytics, chat widgets, tags), they travel to the Connecting Party server and end up in its logs. Any vulnerability on the page, or a single compromised third-party library, means a card data leak. Hosted Fields separates the fields from the page by origin. Each card field is a dedicated :ex:`iframe` loaded from the Cyber domain: its content is not accessible to the Connecting Party page code nor to the Connecting Party server. Card data goes straight from the :ex:`iframe` to Cyber and is exchanged there for a **hostedFieldsToken** — a short-lived single-use string the card cannot be reconstructed from. This page describes how to embed such fields into a payment page, obtain a :ex:`hostedFieldsToken` and process a payment with it, without passing card data through Connecting Party systems. The scheme has four practical implications. Card data never enters Connecting Party systems Not the DOM, the page javascript, the requests to the Connecting Party server, or its logs. This significantly reduces PCI DSS scope. .. note:: The applicable Self-Assessment Questionnaire (SAQ) is to be agreed with a QSA — it depends on more than the card data collection method alone. The Payer stays on the Connecting Party page Unlike a redirect to a payment page or a full-page Cyber form, checkout is not interrupted: there are no redirects, the browser address does not change, and the :code:`Pay` button belongs to the Connecting Party page. Layout and field placement remain under Connecting Party control The SDK creates the fields in exactly the :ex:`div` elements specified in the configuration, and knows nothing about how they are arranged. The containers are styled with the Connecting Party page's own css, with no restrictions; the text inside the field is styled through the SDK, see :ref:`Appearance`. The server side changes in one parameter Only the :ex:`hostedFieldsToken` arrives from the browser to the Connecting Party server. The Connecting Party server must still initiate the transaction itself — the token only replaces the card parameters in that call, it does not process a payment on its own. The payment is processed with the same server-to-server call as without Hosted Fields — for example a :ref:`Sale`, but several other calls accept the token as well: the card parameters — :ex:`credit_card_number`, :ex:`expire_month`, :ex:`expire_year`, :ex:`cvv2` — are replaced with a single :ex:`hosted_fields_token` parameter. The remaining order parameters (:ex:`amount`, :ex:`currency`, :ex:`client_orderid`, :ex:`order_desc`, Payer address and email) are sent unchanged. See :ref:`Step 3` for the full list of calls that accept the token. .. _hosted_payment_fields_prerequisites: What to Obtain from Cyber ==================================================== :ex:`ENDPOINTID` and :ex:`MERCHANTLOGIN` in the examples are placeholders. Real values are provided by Cyber on onboarding, independently for the sandbox and the production environment. .. list-table:: :widths: 25, 75 :header-rows: 1 :class: longtable * - Parameter - Description * - Endpoint ID - Identifier of the Endpoint the payment is processed on. Used in the URL of server calls and passed on SDK initialization as :ex:`endpointId`. * - Merchant login - Merchant login provided by Cyber. Sent as :ex:`oauth_consumer_key` when signing requests. The Endpoint must belong to this login. * - RSA key pair - Needed for signing with OAuth 1.0a RSA-SHA256, the method used in the examples on this page. Two keys generated together, one private and one public: the **private** key signs the requests and must be kept secret from everyone — it never leaves the Connecting Party server; the **public** key is sent to the support manager and is used to verify that a request was signed with the respective private key. This is one of the supported signature methods — others, for example OAuth HMAC-SHA1, use a control key instead of a key pair; see :ref:`Request Authentication Methods`. * - SDK script URL - Where the browser loads the SDK and the card fields from: :ex:`https://gate.e-securegate.com/sdk/hosted-fields/latest/index.js` for production, :ex:`https://sandbox.e-securegate.com/sdk/hosted-fields/latest/index.js` for the sandbox. The keys are generated with a `Key-Pair-Factory tool `_ or :ref:`OpenSSL`, see :ref:`Generating Key Pair`. The signing procedure is described in :ref:`OAuth RSA-SHA256`. .. warning:: | The RSA **private** key signs server calls and stays on the Connecting Party server. It must never appear in page markup, in page javascript, in a :ex:`data` attribute, in a cookie, or in any response the browser can read. | The only credential that crosses into the browser is the :ex:`ephemeralTicket` from :ref:`Step 1`, and it is not a cryptographic key of any kind: it is a single-use pass that admits exactly one tokenization on one Endpoint, which is why putting it on the page is safe. .. _hosted_payment_fields_flow: Tokenization Flow ==================================================== Three parties take part: the Payer, the Connecting Party — its checkout page and its server — and Cyber — the card fields it embeds as :ex:`iframe` elements inside the Connecting Party page, and the server behind them. The checkout page and the card fields both live in the Payer's browser but belong to different origins, which is what keeps the card data out of reach of the page. The diagram below illustrates the flow on the example of a :ref:`Sale` call — see (14) for the other calls the same token works with. .. uml:: :align: center skinparam roundcorner 20 skinparam sequenceArrowThickness 2 skinparam ParticipantPadding 30 actor Payer as Customer box "Payer's Browser" participant "Connecting Party\nCheckout Page" as Page participant "Card Fields\n(3 iframes)" as Fields end box participant "Connecting Party\nServer" as Merchant participant "Cyber" as g autonumber Customer -> Page: Checkout activate Merchant == Page preparation == Merchant -> g: Request an ephemeralTicket note right of Merchant Only an ephemeralTicket is issued here — this step does not initiate a transaction by itself end note activate g g --> Merchant: ephemeralTicket deactivate g Merchant -> Page: Payment page with the ephemeralTicket note right The RSA private key never leaves the server: only the already-issued single-use ephemeralTicket goes to the browser end note deactivate Merchant == Card data entry == Page -> Fields: HostedFields.init() Fields --> Page: onReady() Customer -> Fields: Enter card data note right of Fields Card data stays inside the iframes end note == Tokenization == Customer -> Page: Click "Pay" Page -> Fields: sdk.tokenize(ephemeralTicket) activate Fields Fields -> g: Card data + ephemeralTicket activate g g --> Fields: hostedFieldsToken deactivate g Fields --> Page: onToken(hostedFieldsToken) deactivate Fields == Payment == Page -> Merchant: POST /merchant/pay activate Merchant Merchant -> g: /api/v4/sale/ with hosted_fields_token note right of Merchant Tokenization only exchanged the card for a token — this Sale call is what actually initiates the transaction end note activate g g --> Merchant: Order ID g -> g: Process Sale group Get Final Status group Receive Connecting Party Callback g -> Merchant: Callback with final status Merchant --> g: HTTP 200 deactivate g end group Order Status Request Merchant -> g: Get status by Order ID activate g g --> Merchant: Final status deactivate g end end Merchant --> Page: Payment result deactivate Merchant Page -> Customer: Show result | (2,3) The :ex:`ephemeralTicket` is issued for a specific Endpoint, lives for 15 minutes and is consumed by a single tokenization. The request is signed with OAuth 1.0a RSA-SHA256, see :ref:`Step 1. Obtain an ephemeralTicket`. | (4) The :ex:`ephemeralTicket` is safe to expose in the browser; the RSA private key is not, see the :ref:`warning above`. | (5,6) The SDK creates one :ex:`iframe` per specified :ex:`div` and calls :ex:`onReady` once all of them have loaded — see :ref:`Step 2. Fields and Tokenization`. | (7) Entered values never leave the Cyber windows: neither the DOM, nor the page javascript, nor the Connecting Party server can see them, see :ref:`Restrictions`. | (9,12) :ex:`sdk.tokenize()` asks the :ex:`pan` field to collect the values of all fields and exchange them for a :ex:`hostedFieldsToken`; the result is delivered back to the page through :ex:`onToken`, see :ref:`Step 2`. | (10) The :ex:`pan` field collects the values of all three fields: the neighbouring :ex:`iframe` elements are loaded from the same origin and read each other directly, bypassing the Connecting Party page. | (11) A :ex:`hostedFieldsToken` is returned — a string of up to 2048 characters, valid for 5 minutes, that the card cannot be reconstructed from. The :ex:`ephemeralTicket` is spent at this point: a new payment attempt requires a new one. | (13) This is the Connecting Party's own server endpoint, not Cyber's — its name and format are entirely up to the Connecting Party, see the :ex:`sendHostedFieldsTokenToServer` call in :ref:`Step 2`. | (14) This call is what actually initiates the transaction — tokenization by itself only exchanged the card for a token. A regular Sale request, but with :ex:`hosted_fields_token` instead of the card parameters, see :ref:`Step 3. Payment with the hostedFieldsToken`. | (15) The response is asynchronous: it carries the Order ID, but not the payment status. See :ref:`Async-Response`. | (17,18) To implement callback with final status handling see :ref:`Connecting Party Callback`. | (19,20) To implement order status request see :ref:`Order Status Request` by Order ID. Status should be requested multiple times with 3-5 seconds interval until final status will be received in response. The same flow as an interactive diagram — step by step, with the request and the response of each step: .. raw:: html
.. _hosted_payment_fields_integration: Integration ==================================================== Reference merchant integrations are published on `GitHub `_ — the same payment, the same screens, the same flow, differing in the server stack and in whether the page is plain javascript or React: PHP, Node.js (Express), Python (Flask), .NET (ASP.NET Core), Go, Ruby (Sinatra), Java (Spring Boot), Kotlin (Ktor) and Rust (axum), plus React pages on Next.js and on Go. They cover all three steps below, the OAuth 1.0a signature included, and are a working starting point for your own. .. _hosted_payment_fields_step1: Step 1. Obtain an ephemeralTicket ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :ex:`ephemeralTicket` is issued for a specific Endpoint, lives for 15 minutes and is consumed by a single tokenization. The Connecting Party server requests it with the :ref:`/api/v4/tokenize/create-ephemeral-ticket` call: the URLs, the request, the response and the errors are described there. The request is signed the same way as other server calls — in the examples here with :ref:`OAuth 1.0a RSA-SHA256`; other signature methods are supported as well, see :ref:`Request Authentication Methods`. The :ex:`ephemeralTicket` is then embedded into the page being served — into a javascript variable or a :ex:`data` attribute. It is safe to expose in the browser: it allows exactly one tokenization on the given Endpoint, after which it is withdrawn. .. warning:: Issue a new :ex:`ephemeralTicket` for every payment attempt. It is not a session credential and must not be cached, reused between Payers, or handed out to a page that is not about to take a card. The key that signs this very request is a different thing and stays on the server, see the :ref:`warning above`. .. warning:: The clock of the Connecting Party server must be accurate: :ex:`oauth_timestamp` outside the permitted window (10 minutes by default) is rejected, and a repeated :ex:`oauth_nonce` is rejected as well. .. _hosted_payment_fields_step2: Step 2. Fields and Tokenization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Markup: an empty :ex:`div` per field, the payment button, and an element for the error message. The :ex:`hf-field` class should be present in the markup from the start — the SDK sets it as well, but until the :ex:`iframe` loads the container would otherwise have zero height and the layout would jump. The payment button starts disabled and is enabled from :ex:`onReady`. .. code-block:: html

The script URL carries no parameters. The fields build the tokenization address themselves — from their own address, the :ex:`endpointId`, and the context path of the installation, which travels inside the :ex:`ephemeralTicket`. None of it comes from the :ex:`init` options, so a value taken from page javascript cannot redirect the card data, see :ref:`Restrictions`. Cyber rejects a ticket issued on another installation (error :ex:`4003`). .. note:: The script tag above is synchronous and placed at the end of :ex:``, so :ex:`HostedFields` is available to the next :ex:` .. code-block:: html .. warning:: The SDK must be loaded with a classic :ex:`