Skip to main content

Google Pay S2S Integration Guide

This guide walks you through integrating Google Pay payments using the SysPay Wallet Pay S2S API. Your backend orchestrates the Google Pay flow through two API calls, while your frontend uses the Google Pay JS API to display the payment sheet and collect the payment token.

Unlike Apple Pay, Google Pay does not require a per-transaction merchant session validation step. However, because the S2S flow serves the Google Pay sheet from your own domain, you must register your domain with Google and use your own Google Pay merchant ID for production — see Google Pay Console onboarding.

Important

Only the CRYPTOGRAM_3DS authentication method is supported. The PAN_ONLY method is not supported and must not be included in allowedAuthMethods.

Prerequisites

  • Google Pay enabled on your SysPay API feature
  • ROLE_API_PAYMENT permission granted (plus ROLE_PREAUTH for pre-authorizations)
  • Your WSSE API credentials (login + passphrase) — see Authentication
  • The Google Pay JS library loaded on your frontend (https://pay.google.com/gp/p/js/pay.js)
  • For production: your own Google Pay & Wallet Console merchant ID, with the domain(s) that serve the Google Pay sheet registered against it — see Google Pay Console onboarding

Google Pay Console Onboarding

In the S2S flow, the Google Pay payment sheet is served from your own domain. Google requires that the merchant ID presented to the browser (merchantInfo.merchantId) be a Google Pay & Wallet Console merchant ID whose registered domain matches the domain serving the sheet.

To go live in production, you must:

  1. Register your business in the Google Pay & Wallet Console and obtain your own Google Pay merchant ID (format BCR2DN…).
  2. Register the domain(s) from which you serve the Google Pay sheet in the console, and complete Google's business/integration review.
  3. Provide your Google Pay merchant ID to SysPay (via your SysPay account manager or onboarding configuration).

Once configured, SysPay returns your merchant ID in the google_pay_config.merchant_id field of the init response; pass it directly as merchantInfo.merchantId.

Production requires your own registered merchant ID

If the merchant ID presented to the browser is not registered against the domain serving the sheet, Google rejects the payment in production with error OR_BIBED_11.

An S2S merchant serving Google Pay from its own domain must register and configure its own merchant ID.

note

The TEST environment (SysPay sandbox) works without domain registration — Google only enforces domain matching in PRODUCTION.

Payment Flow Overview

The Google Pay S2S flow consists of two steps:

  1. Init — Create a payment operation and receive Google Pay configuration
  2. Payment — Submit the Google Pay payment token (received from loadPaymentData)
Browser (any)                      Your Server                      SysPay API
│ │ │
│ 1. User clicks Google Pay │ │
│─────────────────────────────> │ │
│ │ 2. POST wallet init │
│ │──────────────────────────────> │
│ │ <── google_pay_config ─────── │
│ 3. loadPaymentData() │ │
│ <──── config ──────────────── │ │
│ │ │
│ 4. User selects card & │ │
│ authorizes payment │ │
│ │ │
│ 5. paymentData returned │ │
│─── token ────────────────────> │ │
│ │ 6. POST wallet payment │
│ │──────────────────────────────> │
│ │ <── payment result ────────── │
│ 7. Show result │ │
│ <──── SUCCESS / FAILURE ───── │ │

Step 1 — Initialize Payment

Call the Initialize wallet payment endpoint with payment_method.type set to GOOGLE_PAY.

{
"payment_method": {
"type": "GOOGLE_PAY"
},
"amount": "1999",
"currency": "EUR",
"reference": "order-20260223-001",
"description": "Order #001",
"preauth": false,
"ems_url": "https://merchant.example.com/notify",
"customer": {
"firstname": "John",
"lastname": "Doe",
"email": "john@example.com",
"ip": "203.0.113.42",
"language": "en",
"billing_address": {
"address1": "123 Main St",
"city": "Paris",
"postal_code": "75001",
"country": "FR"
}
}
}

The response contains:

  • id — Save this. You need it for Step 2.
  • google_pay_config — Pass these fields to your frontend to build the PaymentDataRequest.
{
"google_pay_init": {
"id": 370530,
"status": "OPEN",
"payment_method": "GOOGLE_PAY",
"google_pay_config": {
"gateway": "syspay",
"gateway_merchant_id": "SYSPA978",
"merchant_id": "BCR2DN4T7654321",
"merchant_name": "My Store",
"environment": "PRODUCTION",
"country_code": "FR"
}
}
}

How to use google_pay_config

Config fieldUse in Google Pay JS API
gatewaytokenizationSpecification.parameters.gateway
gateway_merchant_idtokenizationSpecification.parameters.gatewayMerchantId
merchant_idmerchantInfo.merchantId
merchant_namemerchantInfo.merchantName
environmentPaymentsClient constructor (TEST or PRODUCTION)
country_codetransactionInfo.countryCode

In production, merchant_id is your own Google Pay & Wallet Console merchant ID once configured with SysPay.

See Initialize wallet payment for the full request and response reference.

Step 2 — Submit Payment

After the user selects a card and authorizes the payment, loadPaymentData() returns the payment data. Base64-encode the token and send it to the Submit wallet payment endpoint.

{
"payment_method": {
"type": "GOOGLE_PAY",
"token": "eyJzaWduYXR1cmUiOiJNRVlDSVFEQ2ZiLi4uIiwicHJvdG9jb2xWZXJzaW9uIjoiRUN2MiIs..."
}
}

The token is built by base64-encoding the tokenization data string:

const token = btoa(paymentData.paymentMethodData.tokenizationData.token);

Payment Statuses

StatusMeaning
SUCCESSPayment captured successfully
AUTHORIZEDPre-authorization successful (capture later)
FAILEDPayment was declined — see failure_category and failure_message
ERRORProcessing error
TIMED_OUTPayment timed out

See Submit wallet payment for the full request and response reference.

Frontend Integration

Loading the Google Pay JS Library

Add this script tag to your checkout page:

<script src="https://pay.google.com/gp/p/js/pay.js" async></script>

Checking Google Pay Availability

Before showing the Google Pay button, check that the user can pay:

const paymentsClient = new google.payments.api.PaymentsClient({
environment: 'PRODUCTION' // or 'TEST'
});

const isReadyToPayRequest = {
apiVersion: 2,
apiVersionMinor: 0,
allowedPaymentMethods: [{
type: 'CARD',
parameters: {
allowedAuthMethods: ['CRYPTOGRAM_3DS'],
allowedCardNetworks: ['VISA', 'MASTERCARD', 'AMEX']
}
}]
};

const response = await paymentsClient.isReadyToPay(isReadyToPayRequest);
if (response.result) {
// Show Google Pay button
}

Google Pay Button

Use Google's official button for a consistent user experience:

const button = paymentsClient.createButton({
onClick: () => startGooglePay(orderData),
buttonColor: 'black',
buttonType: 'pay',
buttonSizeMode: 'fill'
});
document.getElementById('google-pay-container').appendChild(button);

Full JavaScript Example

async function startGooglePay(orderData) {
// Step 1: Initialize payment via your backend
const initResponse = await fetch('/your-backend/wallet/init', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(orderData)
});
const { wallet_pay_init } = await initResponse.json();
const operationId = wallet_pay_init.id;
const config = wallet_pay_init.google_pay_config;

// Create Google Pay client
const paymentsClient = new google.payments.api.PaymentsClient({
environment: config.environment
});

// Build the payment data request
const paymentDataRequest = {
apiVersion: 2,
apiVersionMinor: 0,
allowedPaymentMethods: [{
type: 'CARD',
parameters: {
allowedAuthMethods: ['CRYPTOGRAM_3DS'],
allowedCardNetworks: ['VISA', 'MASTERCARD', 'AMEX']
},
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
gateway: config.gateway,
gatewayMerchantId: config.gateway_merchant_id
}
}
}],
merchantInfo: {
merchantId: config.merchant_id,
merchantName: config.merchant_name
},
transactionInfo: {
totalPriceStatus: 'FINAL',
totalPrice: (orderData.amount / 100).toFixed(2),
currencyCode: orderData.currency,
countryCode: config.country_code
}
};

try {
// Display the Google Pay payment sheet
const paymentData = await paymentsClient.loadPaymentData(paymentDataRequest);

// Base64-encode the token
const token = btoa(paymentData.paymentMethodData.tokenizationData.token);

// Step 2: Submit payment via your backend
const paymentResponse = await fetch(
`/your-backend/wallet/${operationId}/payment`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
payment_method: { type: 'GOOGLE_PAY', token }
})
}
);
const { payment } = await paymentResponse.json();

if (payment.status === 'SUCCESS' || payment.status === 'AUTHORIZED') {
// Show success to the user
} else {
// Show failure to the user
}
} catch (err) {
if (err.statusCode === 'CANCELED') {
// User closed the payment sheet
} else {
// Handle other errors
}
}
}

Backend Integration (PHP)

class GooglePayService
{
private $apiLogin;
private $apiPassphrase;
private $baseUrl;

public function __construct(string $apiLogin, string $apiPassphrase, string $baseUrl)
{
$this->apiLogin = $apiLogin;
$this->apiPassphrase = $apiPassphrase;
$this->baseUrl = $baseUrl;
}

/**
* Step 1: Initialize Google Pay payment
*/
public function init(array $orderData): array
{
$orderData['payment_method'] = ['type' => 'GOOGLE_PAY'];

return $this->request('POST', '/merchant/payment/wallet', $orderData);
}

/**
* Step 2: Submit payment token
*/
public function submitPayment(int $operationId, string $base64Token): array
{
return $this->request('POST', "/merchant/payment/{$operationId}/wallet", [
'payment_method' => [
'type' => 'GOOGLE_PAY',
'token' => $base64Token,
],
]);
}

private function request(string $method, string $path, array $body): array
{
$ch = curl_init($this->baseUrl . $path);

curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-WSSE: ' . $this->buildWsseHeader(),
],
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($response, true);

if ($httpCode >= 400) {
throw new \RuntimeException(
sprintf('API error %d: %s', $httpCode, $response)
);
}

return $data;
}

private function buildWsseHeader(): string
{
$nonce = random_bytes(16);
$timestamp = time();
$digest = base64_encode(sha1($nonce . $timestamp . $this->apiPassphrase, true));

return sprintf(
'AuthToken MerchantAPILogin="%s", PasswordDigest="%s", Nonce="%s", Created="%d"',
$this->apiLogin,
$digest,
base64_encode($nonce),
$timestamp
);
}
}

Usage

$googlePay = new GooglePayService('139112001', 'your-passphrase', 'https://app.syspay.com/api/v2');

// Step 1
$init = $googlePay->init([
'amount' => '1999',
'currency' => 'EUR',
'reference' => 'order-001',
'customer' => [
'ip' => $_SERVER['REMOTE_ADDR'],
'email' => 'john@example.com',
],
]);
$operationId = $init['wallet_pay_init']['id'];

// Step 2 (called from your payment endpoint)
$result = $googlePay->submitPayment($operationId, $base64TokenFromFrontend);

Testing Google Pay

Use the sandbox environment (https://app-sandbox.syspay.com/api/v2) for testing.

Google Pay provides a TEST environment that returns test tokens without charging real cards. The SysPay sandbox environment is configured to work with Google Pay's test mode.

Test Setup

  1. Set environment to TEST when creating the PaymentsClient (the sandbox init endpoint returns "environment": "TEST" automatically).
  2. In TEST mode, Google Pay returns dummy tokens that work with test processors — no real card is required.
  3. Point your backend to the SysPay sandbox base URL.
  4. Run through the full payment flow — init and payment submission.

What to Verify

  • The Google Pay button appears only when isReadyToPay() returns true.
  • The payment sheet displays the correct merchant name and amount.
  • After selecting a card, the payment processes and returns SUCCESS or AUTHORIZED.
  • Error scenarios are handled: close the payment sheet, and verify your frontend responds appropriately.
  • If you configured an ems_url, verify that webhook notifications are received.

Checklist

  • Google Pay is enabled on your SysPay API feature
  • Google Pay JS library is loaded on your checkout page
  • isReadyToPay() is called before showing the Google Pay button
  • allowedAuthMethods contains only CRYPTOGRAM_3DS (not PAN_ONLY)
  • WSSE authentication is implemented with unique nonces
  • amount is sent in cents as a string
  • customer.ip is the end-user's real IP (not your server's IP)
  • Payment token is base64-encoded before sending to the payment endpoint
  • Payment status is checked and the result is displayed to the user
  • Error responses are handled gracefully on both frontend and backend
  • ems_url is set for webhook notifications (recommended)
  • Production: you have your own Google Pay & Wallet Console merchant ID (BCR2DN…)
  • Production: the domain(s) serving the Google Pay sheet are registered against that merchant ID in the Google Pay & Wallet Console
  • Production: your merchant ID is configured with SysPay and returned in google_pay_config.merchant_id
  • Tested end-to-end in sandbox before going live