Payment means · Google Pay
Google Pay™ lets your customers pay quickly and securely using the cards saved to their Google Account. As their Payment Service Provider, HiPay decrypts the Google token and processes the payment on your behalf.
We support Google Pay on both Web and Android.
Every merchant must comply with the official Google Pay API Terms of Service and Acceptable Use Policy before processing live transactions. This applies to every integration method, Web and Android alike.
Choose your integration path
The two paths are independent and share nothing: pick the one that matches the surface your customers buy from. A merchant selling on both runs both.
Websites and web applications
Google Pay is displayed directly on your checkout page. This documentation, together with Google's integration checklist, is enough to start accepting payments on your site.
You can either build your own integration against the Google Pay API, or use the HiPay JS SDK, which handles eligibility, the payment sheet and tokenization for you. See the dedicated section further down this page.
Native Android applications
Google Pay is integrated inside your mobile app through Google's Android API. HiPay stays the Payment Service Provider: your app obtains the Google token, your server sends it to HiPay.
Follow Google's official guide, then run through the integration checklist and the brand guidelines before submitting your app.
HiPay JS SDK · Google Pay · Integration guide
Show the Google Pay button on your checkout page and let HiPay handle eligibility, the payment sheet, and the exchange of the Google token for a HiPay token. In about forty lines of front-end code.
How it works
You put an empty <div> on your page. The SDK injects a HiPay iframe into it,
and that iframe holds the real Google Pay button. Everything that touches the card stays inside
the iframe: your page never sees card data.
Your page calls create()
The SDK validates your options, creates the iframe, and checks with HiPay that the card networks you asked for are actually enabled on your account.
The iframe asks Google whether the buyer can pay
If so, the official button appears and you receive ready. If not, you receive
unavailable, the slot collapses, and you hide your block.
The buyer clicks and the Google Pay sheet opens
They pick a card and confirm. Google returns an encrypted token, which the iframe immediately exchanges for a HiPay token with the HiPay secure vault — while the sheet is still open.
You receive the HiPay token
The paymentAuthorized event hands you the token and its context data. Your
server uses it to create the transaction through the Order API.
A HiPay token is not a payment. Until your server calls the Order API with that token, nothing is charged. The token is single-use and short-lived: send it to your server as soon as you receive it.
Requirements
| Item | Detail |
|---|---|
| HiPay account | Google Pay must be enabled on your merchant account. The card networks allowed on the account are checked every time the button is displayed. |
| API credentials | The SDK needs both a username and a password. The
merchant identifier sent to Google is derived from them: an auth_token
alone is not enough for a wallet element. |
| HTTPS | Google Pay refuses to load outside HTTPS, including in the test environment. |
The credentials you put on this page are readable by anyone. Use the API account dedicated to the front end (Hosted Fields), never the credentials of the server account that calls the Order API.
Load the SDK
A single <script> tag, before your own code.
<!-- Test --> <script src="https://stage-libs.hipay.com/js/sdkjs.js"></script> <!-- Production --> <script src="https://libs.hipay.com/js/sdkjs.js"></script>
The Google Pay script (pay.google.com/gp/p/js/pay.js) is loaded by HiPay
inside the iframe. Do not add it to your own page.
Prepare the container
This is the step that costs the most integration time. Three rules, and the button shows up on the first try.
index.html<div id="hipay-googlepay"></div>style.css
#hipay-googlepay { width: 100%; max-width: 320px; height: 48px; /* an explicit height, not "auto" */ }
Rule 1 — selector is an id, not a CSS selector
Pass "hipay-googlepay", not "#hipay-googlepay" and not
".my-block". The SDK looks the element up by its id. Any other form
gives you SELECTOR_NOT_FOUND.
Rule 2 — the container must be empty
No child element, no text, not even a non-breaking space or a templating comment that renders
text. The SDK refuses to populate a container that is already occupied and throws
SELECTOR_CONTAINER_NOT_EMPTY. Your own loader therefore belongs next to
the container, not inside it.
Rule 3 — the container must have a resolvable height
The iframe is laid out as width: 100%; height: 100%. If its parent is
height: auto, the percentage cannot resolve and the browser falls back to an
iframe’s intrinsic size: 300 × 150 px. Give it a height in pixels, as a
percentage of a parent that is itself sized, or through a grid / flex track.
The iframe enforces a floor of 240 × 40 px, Google’s documented minimum for the button. A narrower container is overflowed and the button label is clipped, silently.
Create the HiPay instance
checkout.js
const hipay = new HiPay({ username: '01234567.stage-secure-gateway.hipay-tpp.com', password: 'Test_xxxxxxxxxxxxxxxxxxxxxxxx', environment: 'stage', // 'stage' | 'production' lang: 'en', debug: false });
| Option | Required | Purpose |
|---|---|---|
| username | yes | API username. Also used to derive the merchant identifier sent to Google. |
| password | yes | API password. |
| lang | no | SDK language. Defaults to fr. No effect on Google Pay, which displays no HiPay text to the buyer. |
| environment | no | stage, production or custom. Defaults to production. Only production puts Google Pay in PRODUCTION mode; anything else stays in TEST. |
| debug | no | true turns on [HiPay:*] console traces, both on your page and inside the iframe. |
| custom_urls | no | Routes calls to your own endpoints. Relevant keys here: gateway, secure_vault. |
As long as environment is anything other than production, Google Pay
runs in the test environment: the cards shown are test cards and no real
payment is possible. This is the single most common cause of “it worked in staging” tickets.
Create the wallet element
checkout.js
let wallet; try { wallet = hipay.create('wallet', { selector: 'hipay-googlepay', provider: 'googlepay', displayName: 'My Store', request: { amount: '42.90', currencyCode: 'EUR', countryCode: 'FR', supportedNetworks: ['visa', 'mastercard'] } }); } catch (error) { // error.code: stable code, worth logging and quoting to support. // error.message: the detail, with the path of the offending field for an option. console.error(error.code, error.message, error.details); }
It does not return a promise: it throws when something is wrong, invalid
options included, since schema validation throws too. The try / catch is
not optional: at that moment the instance does not exist yet, so there is no
on('error') to listen on. Throwing is the only channel available for a creation
failure.
And the most common failure is not a typo, it is context: SELECTOR_NOT_FOUND
when the container has not been rendered yet. Without a catch, the exception
breaks everything after it in your script, including your other payment methods.
What the thrown error carries
The thrown object carries code, message, details and
payload. code is the value to test if you want to tell cases apart;
message gives, for an invalid option, the exact path of the offending field.
{
"name": "HIPAY_INTERNAL_ERROR",
"code": "CREATE_OPTIONS_INVALID_TYPE",
"message": "options.request.amount: expected string, received number",
"details": {}
}
The checks on the constructor and on create()‘s arguments run before the v2 part
and throw an Error without a code: the code is then
the value of message (HIPAY_CREATE_MISSING_TYPE…). See table A3.
The schema is strict
Any unknown key in options or in request makes create()
fail, with Unrecognized key in originalError.message. This is
deliberate: a typo on currencyCode is better than a payment started in the wrong
currency.
The amount is a string
amount: '42.90', not 42.9. The number of decimals must match the
currency: two for EUR, none for JPY. A value that is too precise is
rejected, a value that is too short is padded ('42' becomes '42.00' in
euros). Minimum: 0.01.
The full list of options is in the reference.
Listen to events
Seven events. Two tell you what occupies your slot, three concern the outcome of the payment, two report failures.
checkout.js// — Display verdict: exactly one of the two arrives. — // 1. The button is displayed and clickable. wallet.on('ready', () => { hideYourLoader(); }); // 2. Nothing to show for this buyer. The iframe removes itself; // what happens to the rest of your page is up to you. wallet.on('unavailable', ({ code }) => { console.info('Google Pay unavailable:', code); }); // — Payment outcome, after the buyer clicks. — // 3. The payment is authorized. // Send this whole payload to your backend: it is the one, and the only one, // that calls the HiPay Order API to create and capture the transaction. wallet.on('paymentAuthorized', (paymentData) => { sendToBackend(paymentData); }); // 4. The buyer closed the sheet. This is not an error. wallet.on('paymentCanceled', () => { showMessage('Payment canceled.'); }); // 5. The payment did not go through. The button stays clickable. wallet.on('paymentFailed', (failure) => { console.warn(failure.code, failure.details); showMessage('The payment failed. Please try another payment method.'); }); // — Diagnostics. — // 6. Non-blocking degradation: the flow carries on. wallet.on('warning', (warning) => { monitoring.capture(warning.code, warning); }); // 7. Technical failure. For your monitoring, not for the buyer. wallet.on('error', (error) => { monitoring.capture(error.code, error); });
If you want to react differently depending on the situation — hide the block, offer another
payment method, show a particular message — use the code field of the payload.
The other fields (message, details) are there for diagnostics and
monitoring.
Four events carry a code: unavailable,
paymentFailed, error and warning — in other words
everything that can go sideways, an ineligible buyer included. The other three do not:
ready and paymentCanceled have no payload at all, and
paymentAuthorized carries payment data, not a code. There is only one way to
succeed.
What paymentData contains
The payload is more than the token: it also carries the card brand, the payment product to declare, and three blocks of context the Order API expects. Send it whole, without picking pieces out of it.
{
"token": "faf3f4a8ff5e6d0541e0c8a9b9a4a3f1", // HiPay card token, single-use
"brand": "VISA", // brand returned by the vault
"payment_product": "visa", // payment product to declare
"provider": "googlepay",
"browser_info": { … }, // browser block for 3-D Secure v2
"device_fingerprint": "04000QZ7h9…", // optional
"data_id": "8f2c1e40-…" // optional
}
The detail of browser_info and the role of each field are in the
payload reference; the mapping to the Order API is in
step 6.
Capture on your server
Capture happens from your server, never from the browser. Your page forwards
the paymentAuthorized payload as is to your backend; your backend calls the HiPay
Order API with that token and the order details. Here is what each field is for.
| Payload field | Use on the Order API side |
|---|---|
| token | The HiPay card token, single-use. |
| payment_product | The payment product to declare (visa, mastercard, cb…). |
| brand | The card brand as returned by the vault. Informational. |
| provider | googlepay. Useful for your own logging. |
| browser_info | Browser block expected by 3-D Secure v2 authentication. |
| device_fingerprint | Device fingerprint, for fraud prevention. |
| data_id | HiPay analytics session identifier. |
Amount, currency, order reference, description, customer details: your server supplies them, from your own cart — never from values passed through the browser. See the Order API documentation for the full list of fields.
Do not log the token and do not store it. Do not use the amount returned by the
browser to build the transaction: a customer can change it.
Full example
A minimal page showing how the previous steps fit together, in two files as in a real integration. It is an illustration, not an integration starting point: adapt it to your page, your error handling and your constraints before going live.
checkout.html<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Checkout</title> <link rel="stylesheet" href="checkout.css"> </head> <body> <h1>Your order: €42.90</h1> <!-- Empty container with an explicit height (see checkout.css) --> <div id="hipay-googlepay"></div> <p id="message" role="status"></p> <!-- The SDK first, your code second --> <script src="https://stage-libs.hipay.com/js/sdkjs.js"></script> <script src="checkout.js"></script> </body> </html>checkout.css
#hipay-googlepay { width: 100%; max-width: 320px; height: 48px; }checkout.js
const message = document.getElementById('message'); const hipay = new HiPay({ username: 'YOUR_USERNAME', password: 'YOUR_PASSWORD', environment: 'stage', // 'production' in production debug: true // set back to false in production }); let wallet; try { wallet = hipay.create('wallet', { selector: 'hipay-googlepay', provider: 'googlepay', displayName: 'My Store', request: { amount: '42.90', currencyCode: 'EUR', countryCode: 'FR', supportedNetworks: ['visa', 'mastercard'] } }); } catch (error) { console.error('[GooglePay] create failed', error.code, error.message); } if (wallet) { wallet.on('ready', () => { console.info('[GooglePay] button ready'); }); wallet.on('unavailable', ({ code }) => { console.info('[GooglePay] unavailable', code); }); wallet.on('paymentAuthorized', (paymentData) => { message.textContent = 'Processing payment…'; // Send the whole payload to your backend, which will call // the HiPay Order API to create and capture the transaction. sendToBackend(paymentData); }); wallet.on('paymentCanceled', () => { message.textContent = 'Payment canceled.'; }); wallet.on('paymentFailed', (failure) => { console.warn('[GooglePay] failed', failure.code, failure.details); message.textContent = 'The payment failed. Please try another card.'; }); wallet.on('warning', (warning) => { console.warn('[GooglePay] warning', warning.code); }); wallet.on('error', (error) => { console.error('[GooglePay] error', error.code, error); }); }
environment: 'stage' and debug: true are staging values.
sendToBackend is yours to write: it is what forwards the payload to your server.
The handlers here only log — what you show the buyer, and what happens to your page, is up to
you.
Verdict guarantee
This is the most useful contract the SDK offers, and the one that saves you from writing your
own timer. Three guarantees, within a bounded time after a successful create().
ready or unavailable, never both
Exactly one of the two arrives, once, per element. That is the display verdict: it states what occupies your slot.
An error before the verdict means ready will not come
But error is not itself a verdict: it does not say what occupies the slot.
Depending on the failure, the slot ends up either empty, or filled with an error message
rendered inside the iframe.
warning never prevents ready
The flow carries on in degraded mode. Log it, hide nothing.
Stop it on the first of ready, unavailable or
error, without reading a single code. Only the first two tell you what to
display.
An unavailable may still follow an error (the slot collapsed), and
an error may follow ready (a failure during payment). Neither
changes the decision your loader has already made.
Lifecycle
What happens between create() and the token, and what you can receive at each
stage.
Validation and iframe creation synchronous
Options are validated, the container is checked, the iframe is injected. Any failure here
is an exception thrown by create(), not an event: nothing is
injected into your page.
Iframe loading
The iframe loads and signals that it is operational — a matter of moments on a normal connection. The 15 seconds are not a waiting time but a give-up threshold: if nothing arrives before then, loading has failed. The element is torn down and no verdict follows.
Card network resolution
The SDK asks HiPay which networks are active on your account and intersects the result
with your supportedNetworks. Empty intersection: the element becomes
unavailable. If the call fails, your networks are kept and you receive a
warning — the flow carries on, Google will revalidate anyway.
Google eligibility verdict
The iframe asks Google whether this buyer, in this browser, can pay. Positive answer: the
button appears and ready fires. Negative answer: unavailable.
Google usually answers in a fraction of a second; the 10 seconds are, again, a give-up
threshold, beyond which the absence of an answer is treated as a failure, followed by an
unavailable.
Click, payment sheet, tokenization
The buyer confirms in the Google sheet. The Google token is exchanged for a HiPay token before the sheet closes: if the exchange fails, the sheet stays open and the buyer can try again. One payment at a time: clicks made while a payment is in flight are ignored.
Options for create('wallet', …)
Root
| Option | Type | Required | Description |
|---|---|---|---|
| selector | string | yes | The container’s id, without #. The container must exist and be empty. |
| provider | string | yes | Only value accepted today: 'googlepay'. |
| request | object | yes | Transaction details. See the next table. |
| displayName | string | no | Your store name, passed to Google Pay for the payment sheet. Without a value, the SDK sends Demo Merchant in the test environment, and nothing at all in production — the buyer then sees the name attached to HiPay’s Google account. Set this field so they see yours. |
| existingPaymentMethodRequired | boolean | no | Defaults to false. When true, the button is shown only if Google confirms the buyer already has a saved card. See the warning below. |
request
| Field | Type | Required | Description |
|---|---|---|---|
| amount | string | yes | Decimal as a string, minimum '0.01'. The number of decimals must be compatible with the currency; the value is normalized ('42' → '42.00' in EUR). |
| currencyCode | string | yes | ISO 4217, three uppercase letters. Example: 'EUR'. |
| countryCode | string | yes | ISO 3166-1 alpha-2, two uppercase letters. The merchant’s country. Example: 'FR'. |
| supportedNetworks | string[] | no | Defaults to ['visa','mastercard','maestro','cb']. Accepted values: visa, mastercard, maestro, cb — case-insensitive, duplicates removed. Google Pay keeps only visa and mastercard; if your list contains neither, create() fails. |
Google is not always able to answer this question: in a cross-origin iframe, when third-party cookies are blocked — Safari’s default — it simply omits the information. The SDK treats that absence as a yes and shows the button; only an explicit refusal hides it. In the test environment Google always answers “card present”, so this option cannot be tested in staging.
Instance API
create() returns an object with three methods. Nothing else is exposed.
| Method | Effect |
|---|---|
| on(event, callback) | Subscribes a listener. Several listeners per event are allowed. |
| removeListener(event, callback) | Removes a listener. Pass exactly the same function reference. |
| destroy() | Removes the iframe from the DOM, cuts communication and returns null. Call it when you leave the checkout page. No effect if the element has already been torn down: calling it twice, or after unavailable, does not throw. |
Events
| Event | Payload | Iframe removed | When |
|---|---|---|---|
| ready | none | no | The element is displayed and usable: the button exists and is clickable. Terminal display verdict. |
| unavailable | { code } | yes | Nothing to show for this buyer or this configuration. Not an error. Terminal display verdict. |
| paymentAuthorized | payment data | no | HiPay tokenization succeeded. This is the only event that gives you a usable token. |
| paymentCanceled | none | no | The buyer closed the payment sheet. This is not an error, do not present it as one. |
| paymentFailed | { code, details?, originalError? } | no | The attempt did not go through: refused by Google, or HiPay tokenization failed. The button stays clickable. |
| warning | error object | no | Non-blocking degradation: the flow carries on and ready remains possible. Log it, show nothing. |
| error | error object | varies | Technical failure, at bootstrap or during payment. Meant for your monitoring, not for the buyer. |
On unavailable, the SDK removes the iframe from the DOM, cuts communication with
it and clears its timers. The object returned by create() stays
valid: your listeners are not removed, on() and
removeListener() keep working, and destroy() is still safe to call —
it simply does nothing. There is nothing for you to clean up.
Payload formats
paymentAuthorized
{
"token": "faf3f4a8ff5e6d0541e0c8a9b9a4a3f1",
"brand": "VISA",
"payment_product": "visa",
"provider": "googlepay",
"browser_info": {
"java_enabled": false,
"javascript_enabled": true,
"language": "en-GB",
"color_depth": 24,
"screen_height": 1080,
"screen_width": 1920,
"timezone": "-60",
"http_user_agent": "Mozilla/5.0 (Macintosh; …)",
"ipaddr": "",
"http_accept": ""
},
"device_fingerprint": "04000QZ7h9…",
"data_id": "8f2c1e40-…"
}
ipaddrandhttp_acceptare intentionally empty: the HiPay gateway fills them server-side.device_fingerprintanddata_idmay be absent if they could not be resolved. Treat them as optional.timezoneis the offset in minutes, as a string, as expected by the 3-DS block.
How payment_product is determined
The HiPay vault returns a brand and, for co-badged cards, a domestic network. The domestic network wins when it is a known one.
| Vault response | payment_product |
|---|---|
| domestic_network CB | cb |
| domestic_network BCMC | bcmc |
| brand VISA | visa |
| brand MASTERCARD | mastercard |
unavailable
{ "code": "GOOGLEPAY_NOT_AVAILABLE" }
paymentFailed
code is always present; details and originalError only
appear when there is something to carry.
{
"code": "WALLET_TOKENIZATION_FAILED",
"details": { "provider": "googlepay" },
"originalError": {
"name": "HttpError",
"message": "HTTP request failed with status 500",
"code": "HTTP_SERVER_ERROR",
"status": 500
}
}
originalError follows the same cause-chain structure as for error and
warning: that is where you find the detail of what Google or the HiPay vault
answered.
error and warning
Both share the same shape.
{
"name": "HIPAY_INTERNAL_ERROR",
"code": "WALLET_ELIGIBILITY_CHECK_FAILED",
"message": "The wallet eligibility check did not answer.",
"details": { "provider": "googlepay" },
"originalError": {
"name": "Error",
"message": "WALLET_ELIGIBILITY_CHECK_TIMED_OUT"
}
}
codeidentifies the step that failed. It is the value to test if you want to tell cases apart, and the one to quote in a support ticket.nameis the error type; today alwaysHIPAY_INTERNAL_ERROR.originalErroris a recursive, stack-free cause chain —{ name, message, code?, status?, details?, originalError? }. The step’s code wins at the first level, the cause descends.detailsis best-effort context, never sensitive data.
Example of a cause chain
A failed gateway call, reported as a warning:
{
"name": "HIPAY_INTERNAL_ERROR",
"code": "NETWORK_RESOLUTION_FAILED",
"message": "The available payment products could not be resolved.",
"details": { "requestedNetworks": ["visa", "mastercard"] },
"originalError": {
"name": "HttpError",
"message": "HTTP request failed with status 503",
"code": "HTTP_SERVER_ERROR",
"status": 503
}
}
Error catalog
Every failure carries a code, the only stable discriminant. Tables are grouped by
how the code reaches you.
A1 — Exceptions thrown by create()
Synchronous. Nothing is injected into your page. The code is on error.code.
| error.code | Cause and fix |
|---|---|
| SELECTOR_NOT_FOUND | No element carries that id. Check that you are not passing #, and that create() runs after the container is rendered. details: { selector }. |
| SELECTOR_CONTAINER_NOT_EMPTY | The container already holds an element or text. Empty it, or put your content next to it. details: { selector }. |
| WALLET_REQUESTED_NETWORKS_UNSUPPORTED | Your supportedNetworks shares no network with Google Pay’s (visa, mastercard). details: { requestedNetworks }. |
| WALLET_USERNAME_MISSING | Wallet element with a client that has no username: the merchant identifier sent to Google is derived from it. |
| WALLET_PROVIDER_UNKNOWN | Internal guard on the provider dispatch. An unknown provider is rejected earlier by validation. |
| FRAME_PARAMS_INVALID | Internal guard on the iframe URL builder. Not reachable from your options. |
| UNEXPECTED_ERROR | Catch-all. Any occurrence is a bug worth reporting, along with originalError. |
A2 — Invalid options
Also thrown by create(). The code follows the
CREATE_OPTIONS_INVALID_<ISSUE> grammar, and message carries the
path of the offending field (options.request.amount: …).
| error.code | Typical trigger |
|---|---|
| CREATE_OPTIONS_INVALID_TYPE | amount passed as a number instead of a string. |
| CREATE_OPTIONS_INVALID_FORMAT | Malformed currencyCode, countryCode, locale or amount. |
| CREATE_OPTIONS_INVALID_VALUE | Value outside the allowed set, an unknown network for instance. |
| CREATE_OPTIONS_INVALID_UNION | Unknown provider: no schema variant matches. |
| CREATE_OPTIONS_INVALID_TOO_SMALL | Empty selector, supportedNetworks: [], amount below 0.01. |
| CREATE_OPTIONS_INVALID_TOO_BIG | An address field that is too long. |
| CREATE_OPTIONS_INVALID_CUSTOM | Business rule: amount with too many decimals for the currency. |
| CREATE_OPTIONS_INVALID_UNRECOGNIZED_KEYS | Unknown key in options or request. Check the spelling. |
| CREATE_OPTIONS_INVALID_KEY | Invalid key in an options dictionary. |
| CREATE_OPTIONS_INVALID_ELEMENT | Invalid item in a list. |
| CLIENT_CREDENTIALS_MISSING | Neither auth_token, nor the username + password pair. |
| CLIENT_ENVIRONMENT_INVALID | environment outside stage / production / custom. |
| CLIENT_INVALID_* | Another problem on the client block, same grammar. |
| VALIDATION_ISSUE_UNKNOWN | Unmapped issue kind. Worth reporting. |
A3 — Exceptions from the legacy SDK
Thrown before the v2 part is reached. These errors have no code:
the code is the value of error.message.
| error.message | Cause and fix |
|---|---|
| HIPAY_MISSING_INIT_OPTIONS | new HiPay() called without an options object. |
| HIPAY_MISSING_CREDENTIALS | Missing username or password. Both are required for a wallet. |
| HIPAY_INVALID_ENVIRONMENT | environment outside the allowed values. |
| HIPAY_CREATE_MISSING_TYPE | create()‘s first argument is empty or missing. Expected: 'wallet'. |
| HIPAY_CREATE_MISSING_OPTIONS | create()‘s second argument is missing or not an object. |
B — Codes of the error event
Emitted asynchronously after create(). Meant for your monitoring.
| code | Cause | What follows |
|---|---|---|
| FRAME_HANDSHAKE_TIMEOUT | No answer from the iframe within 15 s: loading blocked by the browser, an extension, or an unreachable HiPay domain. | Element torn down. No verdict follows. |
| THIRD_PARTY_SDK_UNAVAILABLE | Google Pay script missing from the iframe page (status: 'missing') or loaded without exposing its API (status: 'failed'). | The iframe displays its own message. Neither ready nor unavailable. |
| WALLET_PROVIDER_INIT_FAILED | Opening the Google Pay session failed. | unavailable · ELIGIBILITY_NO_VERDICT |
| WALLET_ELIGIBILITY_CHECK_FAILED | The eligibility check threw, or did not answer within 10 s. A refusal from Google is not an error. | unavailable · ELIGIBILITY_NO_VERDICT |
| WALLET_PAYMENT_REQUEST_FAILED | After ready: the payment sheet request failed for a technical reason, or the provider returned a blank token. A buyer cancellation emits nothing. | The button stays clickable, verdict unchanged. |
| FRAME_ROOT_MISSING | Build defect in the iframe page. | Nothing is rendered, no verdict. |
| CONTEXT_MISSING | Internal programming error inside the iframe. | Not reachable from your integration. |
| UNEXPECTED_ERROR | Catch-all. Any occurrence is a bug worth reporting. | Depends on the origin. |
C — Codes of the warning event
Non-blocking: the flow carries on and ready remains possible.
| code | Cause and consequence |
|---|---|
| NETWORK_RESOLUTION_FAILED | The gateway call listing the active payment products failed: 4xx/5xx, timeout, off-schema response. The element falls back to the networks you requested. details: { requestedNetworks }, HTTP cause in originalError. |
D — Codes of the unavailable event
None of these is a failure: the button simply has no reason to be there. Hide your block and offer another payment method.
| code | Meaning |
|---|---|
| GOOGLEPAY_NOT_AVAILABLE | Google states that this buyer cannot pay with Google Pay in this browser. No finer cause is given. |
| GOOGLEPAY_NO_ACTIVE_CARD | With existingPaymentMethodRequired: true, Google explicitly answered that the buyer has no saved card. |
| NO_ELIGIBLE_NETWORK | The intersection between the networks active on your HiPay account and your supportedNetworks is empty. A configuration matter, not a buyer matter. |
| ELIGIBILITY_NO_VERDICT | The eligibility question got no answer. The technical diagnosis arrived in parallel on error. |
E — Codes of the paymentFailed event
| code | Meaning |
|---|---|
| WALLET_PAYMENT_REFUSED | Google refused the request. What Google answered — its own code and label — travels in
originalError. |
| WALLET_TOKENIZATION_FAILED | Exchanging the Google token for a HiPay token failed: vault refusal, timeout, network,
unexpected response. The cause travels in originalError. Nothing was
charged. |
F — Codes that only ever appear as a cause
These are never the top-level code: you only meet them inside
originalError. A test expecting one of them at the first level will never pass.
| code | Appears under |
|---|---|
| GOOGLE_PAY_SDK_NOT_READY | WALLET_PROVIDER_INIT_FAILED |
| GOOGLE_PAY_SESSION_NOT_INITIALIZED | WALLET_ELIGIBILITY_CHECK_FAILED, WALLET_PAYMENT_REQUEST_FAILED |
| GOOGLE_PAY_PAYMENT_TOKEN_EMPTY | WALLET_PAYMENT_REQUEST_FAILED |
| HTTP_TIMEOUT | NETWORK_RESOLUTION_FAILED — no response within the timeout (10 s by default) |
| HTTP_NETWORK_ERROR | NETWORK_RESOLUTION_FAILED — offline, blocked, CORS |
| HTTP_CLIENT_ERROR | NETWORK_RESOLUTION_FAILED — 4xx status, status carried |
| HTTP_SERVER_ERROR | NETWORK_RESOLUTION_FAILED — 5xx status, status carried |
| HTTP_UNEXPECTED_STATUS | NETWORK_RESOLUTION_FAILED — any other non-2xx status |
| HTTP_RESPONSE_INVALID_* | NETWORK_RESOLUTION_FAILED — off-schema gateway response |
The buyer pressed Pay and nothing was charged: that is a payment outcome, not a failure of the
element. It therefore arrives on paymentFailed, your payment channel, and never
on error. One rule to remember:
everything that follows a click on the button arrives on the payment events.
Troubleshooting
First reflex: pass debug: true to new HiPay() and filter the console on
HiPay. The HiPay:networks, HiPay:wallet and
HiPay:frame channels tell you most of the story.
| Symptom | Most likely cause | Fix |
|---|---|---|
| Nothing appears, no event, no exception | The container did not exist yet when the call ran | Call create() after the container is rendered. In a framework, from a mount effect. |
| The button is 300 × 150 px | The container is height: auto |
Give it an explicit height. The iframe is height: 100% and cannot resolve otherwise. |
| The button label is clipped | Container narrower than 240 px | Widen the container, or leave it at width: 100% inside a wide enough block. |
SELECTOR_NOT_FOUND although the div exists |
selector: '#my-id' or a class selector |
Pass the bare id, without #. |
SELECTOR_CONTAINER_NOT_EMPTY on a div that looks empty |
A space, a rendered line break, or a loading component inside it | Empty the container completely. Put any temporary content outside it. |
Neither ready nor unavailable arrives |
An error fired before the verdict |
This is the only expected case. Read its code: FRAME_HANDSHAKE_TIMEOUT or THIRD_PARTY_SDK_UNAVAILABLE most of the time. |
| The button appears, but the sheet closes immediately | A parameter accepted by the eligibility check is refused by the sheet | The check is more lenient than the sheet: ready does not validate the transaction. Read originalError on paymentFailed. |
| Everything works in staging, nothing works in production | Google Pay is not enabled on your production HiPay account | The two environments have separate configurations. Contact HiPay support with the code you received. |
| The sheet shows test cards in production | environment is not exactly 'production' |
Only that value switches Google Pay to PRODUCTION mode. |
paymentFailed · WALLET_PAYMENT_REFUSED as soon as the sheet opens |
A transaction parameter is refused by Google | Check countryCode, currencyCode and the format of amount. What Google answered is in originalError. |
Google Pay · Direct integration · No HiPay SDK
Integrate the official Google Pay API yourself, in PAYMENT_GATEWAY mode with
hipay as the gateway. You keep full control of the user experience; in exchange you
own the Google Pay client, the button, the payment sheet and the token exchange.
How it works
If you would rather not use the HiPay JavaScript SDK and want to keep full control over the user
experience and over the integration of the official Google Pay API on your front end, you must
configure Google Pay using the PAYMENT_GATEWAY mode.
Follow the Google documentation to integrate a Google Pay solution on your website.
Google encrypts the buyer’s card data with HiPay’s public key. Your front end receives a secure Google token, which you convert into a HiPay token before handing it to your server, which then calls the HiPay Order API.
Prerequisites: getting your Merchant ID
Before starting the technical integration, set up your account on Google Pay & Wallet.
| Step | What to do |
|---|---|
| Open the console | Go to pay.google.com/business/console. |
| Sign in | Create or sign in to the Google Account associated with your business. |
| Accept the terms | Accept the Google Pay Terms of Service and the Acceptable Use Policy. |
| Configure your profile | Complete your merchant profile and obtain your unique Merchant ID. |
| Save the ID | You will need it to configure your payment requests. |
Without this prior configuration, your integration will not work. Make sure your Merchant ID is properly configured before going any further.
The five steps
Initialize the client
Load the official Google script and instantiate the PaymentsClient in
TEST or PRODUCTION mode.
Configure your parameters
Set up the official Google Pay API in PAYMENT_GATEWAY mode using
hipay as the gateway.
Render the button
Verify user readiness through the API and inject the official button, respecting Google’s guidelines.
Convert the token
Retrieve the encrypted Google Pay token and convert it into a HiPay token through our secure vault API.
Process the payment
Send the HiPay token to the
HiPay POST /v1/order endpoint
from your server.
Include and initialize Google Pay
Load the official Google Pay JavaScript library on your checkout page. Load it asynchronously and
instantiate the PaymentsClient as soon as the script has finished loading.
<script async src="https://pay.google.com/gp/p/js/pay.js" onload="onGooglePayLoaded()"></script>
Once loaded, the script automatically triggers the onGooglePayLoaded() callback.
Inside that function, instantiate the PaymentsClient by declaring your target
environment.
function onGooglePayLoaded() { // Instantiate the Google Pay client const paymentsClient = new google.payments.api.PaymentsClient({ // ENVIRONMENT CONFIGURATION: // 'TEST' for development, staging and QA environments. // 'PRODUCTION' for your live store (requires HTTPS and an approved domain). environment: 'TEST' }); // You can now define your configuration objects and check whether // the user is ready to pay. See step 2. }
Configure payment and merchant parameters
Once the client is ready, define the payment configuration objects: hipay as the
payment gateway, the card networks you support, and your merchant identifiers for both HiPay and
Google.
// 1. Tokenization specification — the HiPay gateway configuration const tokenizationSpecification = { type: 'PAYMENT_GATEWAY', parameters: { 'gateway': 'hipay', // Your HiPay public API username, Base64 encoded 'gatewayMerchantId': btoa('YOUR_PUBLIC_API_USERNAME') } }; // 2. Card payment method — supported networks and authentication const cardPaymentMethod = { type: 'CARD', parameters: { // HiPay supports both authentication methods allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'], // Card networks supported on HiPay allowedCardNetworks: ['MASTERCARD', 'VISA', 'MAESTRO'], billingAddressRequired: true, billingAddressParameters: { // FULL includes street, city, postal code and country format: 'FULL', isPhoneNumberRequired: false } }, tokenizationSpecification: tokenizationSpecification }; // 3. Full Google Pay request object, carrying your merchant identity const paymentDataRequest = { apiVersion: 2, apiVersionMinor: 0, allowedPaymentMethods: [cardPaymentMethod], merchantInfo: { // TEST: not validated by Google, a dummy value is accepted. // PRODUCTION: your official 20-digit Google Merchant ID. merchantId: 'YOUR_GOOGLE_MERCHANT_ID', // The brand name shown to the buyer in the Google Pay sheet merchantName: 'Your Store Name' }, transactionInfo: { totalPriceStatus: 'FINAL', totalPrice: '45.00', currencyCode: 'EUR', // ISO 4217 // ISO 3166-1 alpha-2. Required if the buyer is in an SCA-regulated region. countryCode: 'FR' } };
merchantInfo.merchantId and gatewayMerchantId are
not the same thing.
merchantInfo.merchantIdis your Google Merchant ID, generated by Google once your website is registered in the Google Pay Console. InTESTit can be omitted or left as a dummy value.gatewayMerchantIdis the Base64-encoded public API username of your HiPay account. It is what identifies Google Pay tokens as belonging to you.
Authentication methods
HiPay supports both Google Pay authentication methods. They differ in how the card is tokenized and in how risk is handled downstream.
| PAN_ONLY | CRYPTOGRAM_3DS | |
|---|---|---|
| Tokenization | Google provides the standard Primary Account Number (PAN) token. | Google provides an encrypted cryptogram with device binding, instead of a PAN. |
| HiPay’s role | HiPay processes the transaction and may trigger 3-D Secure based on its fraud detection engine and payment risk assessment. | HiPay processes the encrypted token directly, without triggering 3-D Secure. Security is inherent to the device-based tokenization. |
| Use case | Standard card processing, where 3-D Secure is applied conditionally according to regulatory and risk-based requirements. | High-security transactions, where strong tokenization removes the need for an additional authentication flow. |
| Payment flow | token → risk assessment → possible 3-DS challenge → authorization | encrypted device-bound token → direct processing → authorization, no 3-DS page |
Declaring your authentication methods
In your Google Pay request, declare the methods you accept.
checkout.jsconst cardPaymentMethod = { type: 'CARD', parameters: { allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'], allowedCardNetworks: ['MASTERCARD', 'VISA', 'MAESTRO'] } };
- When you include both methods, Google Pay picks the tokenization method according to the device’s capabilities and the issuer’s support.
- When you want device-based tokenization only, declare
['CRYPTOGRAM_3DS']alone to guarantee stronger security throughout the payment chain. - The
authentication_indicatorparameter below controls 3-D Secure behaviour forPAN_ONLYcredentials only.
Forcing 3-D Secure authentication
Control how 3-D Secure is applied to PAN_ONLY transactions with the
authentication_indicator parameter of your HiPay Order request (see
step 5): 1 lets HiPay’s fraud detection engine decide when
3-D Secure is needed, 2 enforces it on every transaction.
{
"authentication_indicator": 1,
"...": "other order parameters"
}
Geographic considerations
The methods you can support depend on the transaction’s geographic context. See Google’s SCA guide for the detail.
- PAN_ONLY is generally available worldwide, but HiPay may trigger 3-D Secure based on fraud detection and on the cardholder’s location.
- CRYPTOGRAM_3DS is recommended in regions subject to Strong Customer Authentication, such as the EEA and the UK.
Include the right countryCode in your transaction information so that Google Pay
can determine the appropriate authentication method for the local regulation.
Collecting the billing address
The billingAddressParameters configuration lets you request the buyer’s billing
address during the Google Pay transaction.
const cardPaymentMethod = { type: 'CARD', parameters: { allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'], allowedCardNetworks: ['MASTERCARD', 'VISA', 'MAESTRO'], billingAddressRequired: true, billingAddressParameters: { format: 'FULL', isPhoneNumberRequired: false } }, tokenizationSpecification: tokenizationSpecification };
This information is useful for:
- Fraud prevention — validate the billing address against the card issuer’s records.
- Order fulfilment — use the address for delivery or verification.
- Compliance — hold the customer information your regulatory requirements call for.
Configuration options
| Option | Value | Effect |
|---|---|---|
| billingAddressRequired | true | Billing address information is required. With false, it is optional. |
| format | FULL | Complete address: street, city, postal code, country. |
| format | FULL-ISO3166 | Complete address including name, street address, locality, region, country code, postal code and ISO 3166 administrative area. |
| format | MIN | Minimal address: postal code and country only. |
| isPhoneNumberRequired | true | Also collects the buyer’s phone number along with the billing address. |
The billing address returned in the Google Pay response should be passed on to HiPay in your Order request, for additional validation and fraud prevention.
Check readiness and render the button
Before displaying the payment button, verify that the user is able to pay with Google Pay through
the isReadyToPay() method. If the answer is positive, render the official button
dynamically.
For Google to approve your production access, your button must strictly comply with the official Google Pay Brand Guidelines. Never create a custom HTML/CSS button from scratch: always use the button creation API provided by the script.
<div id="google-pay-container"></div>checkout.js
// Check whether the user can pay with the configuration you defined paymentsClient.isReadyToPay(paymentDataRequest) .then(function (response) { if (response.result) { // Create the official Google Pay button const button = paymentsClient.createButton({ buttonColor: 'default', // 'default', 'black' or 'white' buttonType: 'buy', // 'buy', 'plain', 'donate', 'checkout'… buttonSizeMode: 'responsive', onClick: onGooglePayButtonClicked }); // Append the button to your own container document.getElementById('google-pay-container').appendChild(button); } else { // Fallback: hide the Google Pay option or show your standard card form console.log('Google Pay is not available for this user or device.'); } }) .catch(function (err) { console.error('isReadyToPay error: ', err); });
Retrieve the payment token
When the buyer clicks the Google Pay button, open the Google payment sheet with
loadPaymentData(). Once the buyer validates the transaction, the Google API returns a
PaymentData object. Extract the raw JSON string of the encrypted token, then convert
it into a HiPay token — see the
HiPay token response format.
// Triggered when the buyer clicks your official Google Pay button function onGooglePayButtonClicked() { // Open the secure Google Pay sheet paymentsClient.loadPaymentData(paymentDataRequest) .then(async function (paymentData) { // 1. Extract the encrypted payment token from Google's response const googlePayToken = paymentData.paymentMethodData.tokenizationData.token; // 2. Exchange the Google token for a HiPay token through the secure vault const response = await fetch('https://secure2-vault.hipay-tpp.com/rest/v2/google-pay/token.json', { method: 'POST', headers: { 'Authorization': 'Basic ' + btoa('<YOUR_PUBLIC_API_USERNAME>:<YOUR_PUBLIC_API_PASSWORD>'), 'Content-Type': 'application/json' }, body: JSON.stringify({ google_pay_token: googlePayToken }) }); const hipayTokenData = await response.json(); // 3. Forward the HiPay token to your backend, which processes the payment processBackendPayment(hipayTokenData.token); }) .catch(function (err) { // Errors, or the buyer closing the Google sheet console.error('Google Pay payment sheet error: ', err); }); }
Server-side processing
From your server, call our
order creation endpoint POST /v1/order,
injecting the token obtained in the previous step.
{
"orderid": "CMD-2026-98765",
"amount": 45.00,
"currency": "EUR",
"payment_product": "<BRAND_OR_DOMESTIC_NETWORK_OF_HIPAY_TOKEN>",
"cardtoken": "<TOKEN_FIELD_OF_HIPAY_TOKEN>",
"accept_url": "https://www.your-site.com/success",
"decline_url": "https://www.your-site.com/decline"
}