Google Pay is a digital wallet service that allows shoppers to make payments using their Android devices. Violet supports Google Pay as a payment method for your marketplace. You can use Stripe Elements to integrate Google Pay into your checkout flow with Violet. Alternatively, you can integrate natively with Google Pay using the Android SDK.

For a detailed, end-to-end example of how Violet Checkout works with Google Pay, you can download our sample application: Violet Sample App.

The following guide walks through how to integrate Google Pay with Violet using Stripe Elements.

Creating an Order

The first step is to create a cart with items in it for your shopper as described here, however, with the following new flag added to the request body:

{
  "wallet_based_checkout": true
}

This flag is required to ensure the rest of the checkout flow is aware that an external wallet will be used to complete the payment.

Carts with wallet_based_checkout set to true cannot be completed without completing the front end client work listed below, specifically the Stripe Element integration and the client side Payment Confirmation.

This will initialize the cart as a wallet-based cart allowing you to utilize a payment intent rather than raw credit card details. When wallet_based_checkout is true the response will include the following properties:

{
  "stripe_key": "pk_test_UHg8oLvg4rrDCbvtqfwTE8qd",
  "payment_transactions": [
          {
              "id": 84808,
              "order_id": 123981,
              "payment_provider": "STRIPE",
              "payment_provider_transaction_id": "pi_3QpCRx2eXDTnoxH51SOBjwot",
              "amount": 900,
              "currency": "USD",
              "capture_status": "REQUIRES_PAYMENT_METHOD",
              "capture_method": "AUTOMATIC",
              "transfer_status": "PENDING",
              "transfer_method": "AUTOMATIC",
              "metadata": {
                  "payment_intent_client_secret": "pi_3QpCRx2eXDTnoxH51SOBjwot_secret_0VYreFpdISo01L0K26HPSXUWt",
                  "payment_intent_id": "pi_3QpCRx2eXDTnoxH51SOBjwot"
              },
              "errors": [],
              "date_created": "2025-02-05T17:31:45+0000",
              "date_last_modified": "2025-02-05T17:31:45+0000",
              "status": "REQUIRES_PAYMENT_METHOD"
          }
  ]
}

You will need the stripe_key and payment_intent_client_secret to render the Google Pay button on your checkout page.

Rendering the Google Payment Sheet

Once a cart has been created, the rest of the checkout flow can take place on the Google Payment Sheet. This includes:

  • Applying customer information
  • Selecting shipping addresses
  • Selecting shipping methods
  • Selecting payment information
  • Confirming the order.

Google also allows you to only request parts of the information from the list above if you already have details about the customer. Hence, there are multiple possible ways checkout can be completed through Google Pay. The two most common ways are:

  • Google Pay Payment — In this scenario, customer information and shipping and billing addresses are requested prior to displaying the Google Pay term sheet so that only Payment takes place through Google Pay.
  • Google Pay Checkout — In this scenario, all information is requested and attached to the cart through Google Pay.

Google Pay Payment

To support the “Payment-only using Google Pay” flow, where only the charge is completed through Google Pay, please integrate with Stripe Elements, and described here. Since Stripe Payment Elements has multiple payment methods built into it, Google Pay being one of them, this solution will work out of the box to complete payments.

Google Pay Checkout

Retrieving the Payment Intent Client Secret

To render the Google Payment Sheet, Violet recommends using Stripe elements. This ensures that the payment can successfully be handled by Violet without requiring any extra effort on your side.

In order to render a Stripe Payment Element, Stripe requires both a Stripe Key as well as a Payment Intent Client Secret. You will have both values from the original create cart response from above.

The stripe_key is your Stripe Publishable Key and does not change. It is used to initialize the Stripe Elements on your client side. You can store this as an environment variable in your system so that you don’t need to rely on fetching it from the Cart.

Integrating with the Stripe Payment Request

The Payment Request Button Element gives you a single integration for Apple Pay, Google Pay, and Link — Stripe’s one-click checkout product. Now that you have a Stripe Key and Payment Intent Client Secret, you’re ready to render the Stripe Payment Request button and collect all shopper information through Google Pay. If your user is on an android device, Stripe will automatically detect this and render the Google Pay button.

Set up Stripe.js

Install the Stripe.js React libraries and the Stripe JS loader from the npm public registry:

npm install --save @stripe/react-stripe-js @stripe/stripe-js

Add Stripe.js and Elements to your payment page

To use the Stripe Payment element, wrap your checkout page in a Stripe Elements provider. This is where you will also use the Stripe Key provided in the Order response.

import React from 'react';
import ReactDOM from 'react-dom';
import {Elements} from '@stripe/react-stripe-js';
import {loadStripe} from '@stripe/stripe-js';

import CheckoutForm from './CheckoutForm';

// Make sure to call `loadStripe` outside of a component's render to avoid
// recreating the `Stripe` object on every render.
const stripePromise = loadStripe('pk_test_UHg8oLvg4rrDCbvtqfwTE8qd');

function App() {
  return (
    <Elements stripe={stripePromise}>
      <CheckoutForm />
    </Elements>
  );
};

ReactDOM.render(<App />, document.getElementById('root'));

Add the Payment Request to your Checkout Form

In your checkout form, render the Stripe PaymentElement component:

import React, {useState, useEffect} from 'react';
import {PaymentRequestButtonElement, useStripe} from '@stripe/react-stripe-js';

const CheckoutForm = () => {
  const stripe = useStripe();
  const [paymentRequest, setPaymentRequest] = useState(null);

  useEffect(() => {
    if (stripe) {
      const pr = stripe.paymentRequest({
        country: 'US', //From the Order object
        currency: 'usd', //From the the Order object
        total: {
          label: 'Sub Total', //From the Order object
          amount: 1099,
        },
        requestPayerName: true,
        requestPayerEmail: true,
				requestShipping: true
      });

			// Check the availability of the Payment Request API.
      pr.canMakePayment().then(result => {
        if (result) {
          setPaymentRequest(pr);
        }
      });

    }
  }, [stripe]);

	if (paymentRequest) {
    return <PaymentRequestButtonElement options={{paymentRequest}} />
  }

  // Use a traditional checkout form.
  return 'Insert your form or button component here.';
}

export default CheckoutForm;

Use the requestPayerName parameter to collect the payer’s billing address for Google Pay. You can use the billing address to perform address verification and block fraudulent payments. All other payment methods automatically collect the billing address when one is available.

Next, learn how to apply customer information and request shipping methods.

Applying Customer Information and Requesting Shipping Methods

Listen to the shippingaddresschange event to detect when a shipping address is selected by the shopper. Use this address to fetch valid shipping options from Violet.

Applying a Shipping Address to an order is required to retrieve Shipping Methods and present them to the shopper.

However, Google does not return a complete shipping address to your code, omitting information like the actual street address for privacy reasons. Violet accounts for this by allowing wallet_based_checkout carts to pass an empty string for address_1 at this point in the checkout flow.

Once the payment is confirmed, Google will provide the address info. That info must be sent in the /submit request body as shown below so the real address can be finalized during order submit.

// Handle `shippingaddresschange` event
paymentRequest.on('shippingaddresschange', function(event) {
  var updateWith = event.updateWith;
  var shippingAddress = event.shippingAddress;

  // handle shippingaddresschange event
  // Call Violet API to apply customer info to Cart
  const orderResponse = await handleCustomerInfo();

  // Call Violet API to get shipping methods for selected address
  const shippingMethods = await handleFetchShipping();

  // Apply shipping methods to updateDetails object

  // call event.updateWith within 30 seconds
  updateWith(updateDetails);
});

// Handler 'event' object
{
	firstName: "",
	lastName: "",
	email: "",
  shippingAddress: {
    country: "US",
    addressLine: [
      "185 Berry St.",
      "Suite 500",
    ],
    region: "CA",
    city: "San Francisco",
    ...,
  },
  updateWith: function(updateDetails) {
    // Call this function to merge your updates into the current
    // PaymentRequest object. Note that you must call this
    // within 30 seconds.
  },
}

const handleFetchShipping = async (result) => {
  //Call to Violet to get available shipping methods
  const res = await fetch('{{baseUrl}}/checkout/cart/:cart_id/shipping', {
      method: 'GET',
      headers: { 'Content-Type': 'application/json' }
  });

  const shipping = await res.json();

}

const handleCustomerAddress = async (result) => {
  //Call to Violet to apply the customer's address
  const res = await fetch('{{baseUrl}}/checkout/cart/:cart_id/customer', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
			body: {
                "first_name": "Ishan",
                "last_name": "Wallet Checkout Tests",
                "email": "ishan.guru+test_order@violet.io",
                "shipping_address": {
                    "address_1":"", //Apple will not provide this to you yet
                    "city":"Brooklyn",
                    "country":"US",
                    "postal_code":"11206",
                    "state":"NY",
                    "type":"SHIPPING"
                },
                "billing_address": {
                    "address_1":"", //Apple will not provide this to you yet
                    "city":"Seattle",
                    "country":"US",
                    "postal_code":"98109",
                    "state":"WA",
                    "type":"BILLING"
                }
			}
    });

  const orderResponse = await res.json();
}

Applying the Shipping Method

Once shipping methods have been shown to the Shopper, you can subscribe to the shippingoptionchange event to select and apply new shipping methods to the cart.

// Handle `shippingaddresschange` event
paymentRequest.on('shippingoptionchange', function(event) {
  var updateWith = event.updateWith;
  var shippingOption = event.shippingOption;
  // Send shipping method selected to Violet API
  handleShippingChange();

  // call event.updateWith within 30 seconds
  updateWith(updateDetails);
});

// Handler event object
{
  shippingOption: {
    id: "someUniqueID",
    label: "Ground",
    detail: "UPS standard ground shipping",
    amount: 999,
  },
  updateWith: function(updateDetails) {
    // Call this function to merge your updates into the current
    // PaymentRequest object. Note that you must must call this
    // within 30 seconds.
  },
}

// Call to update Violet Shipping
const handleShippingChange = async (result) => {

	//Call to Violet to apply shipping method
  const res = await fetch('{{baseUrl}}/checkout/cart/:cart_id/shipping?price_cart=true', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify([
		    {{shippingData}}
			])
    });

  const orderResponse = await res.json();

}

Confirming the Payment

Once you’ve applied shipping methods to all the bags in the order, you can confirm the payment using a paymentMethodId and paymentIntentClientSecret.

Listen to the paymentmethod event to receive a PaymentMethod object and then confirm the card payment.

paymentRequest.on('paymentmethod', async (ev) => {
  // Confirm the PaymentIntent without handling potential next actions (yet).
  const {paymentIntent, error: confirmError} = await stripe.confirmCardPayment(
    clientSecret,
    {payment_method: ev.paymentMethod.id},
    {handleActions: false}
  );

  if (confirmError) {
    // Report to the browser that the payment failed, prompting it to
    // re-show the payment interface, or show an error message and close
    // the payment interface.
    ev.complete('fail');
  } else {
    // Report to the browser that the confirmation was successful, prompting
    // it to close the browser payment method collection interface.
    ev.complete('success');
    // Check if the PaymentIntent requires any actions and if so let Stripe.js
    // handle the flow. If using an API version older than "2019-02-11"
    // instead check for: `paymentIntent.status === "requires_source_action"`.
    if (paymentIntent.status === "requires_action") {
      // Let Stripe.js handle the rest of the payment flow.
      const {error} = await stripe.confirmCardPayment(clientSecret);
      if (error) {
        // The payment failed -- ask your customer for a new payment method.
      } else {
        handleVioletSubmit(ev)
      }
    } else {
			handleVioletSubmit(ev)
    }
  }
});

Submitting the Order to Violet

If a successful result is returned from the call to Stripe, the payment has been authorized. You can now submit the Order to Violet.

Applying Final Customer Information

After post-payment confirmation, Google stops redacting customer information such as street address. It will now be available so you will need to re-apply this information to the cart. This can be done during the submit call itself.

You can apply the customer information in the handleVioletSubmit method called above.

const handleVioletSubmit = async (result) => {
  const res = await fetch('{{baseUrl}}/checkout/cart/:cart_id/submit', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: {
			  "order_customer" :{
			        "first_name": "P.",
			        "last_name": "Sherman",
			        "email": "p.sherman@findingnemo.com",
			        "shipping_address": {
			            "address_1":"42 Wallaby Way",
			            "city":"Sydney",
			            "country":"AU",
			            "postal_code":"4000",
			            "state":"NSW",
			            "type":"SHIPPING"
			        },
			        "same_address": true
			    }
			}
    });

  const orderResponse = await res.json();

  handleOrderConfirmation(orderResponse);
}

The order has now been submitted. You can display a confirmation screen to your Shopper upon success and close out from the Google Pay Payment Sheet.

Limitations

Multi-merchant Checkout

Multi-merchant checkout with Google Pay requires additional work due to the nuances around shipping methods for these orders. The Google Pay Term Sheet was not built to allow for selecting different shipping methods within the same order. This means that to implement Violet’s support for multi-merchant checkout with different shipping methods for different merchants within the same order, we recommend using the Google Pay Payment flow instead of the Google Pay Checkout flow. This will let you collect user information and allow them to select the shipping methods for each bag before paying with Google Pay.

The best approach is a hybrid method where single merchant checkout allows for the full Google Pay experience and then multi bag checkout only allows for Google Payment. See the Violet Sample App for a working example of this that you can reference.

Alternative

If your app still requires the Google Pay Checkout experience, Violet suggests the following approach:

Note: This approach results in a sub-optimal user experience and increases your monthly payments to Violet. We highly recommend using the Google Pay Payment flow instead.

If you look closely at the Order object for multi-merchant Orders, you will notice that each Bag contains details, including tax, shipping, and totals; specific to a single merchant.

{
    "id": 747,
    "token": "0fb48193744a4fe08e18f586830e8e04",
    "user_id": 10238,
    "app_id": 10193,
    "developer_id": 10122,
    "customer": { ... },
    "bags": [
        {
            "id": 1091,
            "order_id": 747,
            "merchant_id": 10131,
            "app_id": 10193,
            "external_id": "445",
            "status": "ACCEPTED",
            "fulfillment_status": "PROCESSING",
            "financial_status": "PAID",
            "skus": [ ... ],
            "shipping_method": {
                "type": "FLAT_RATE_PRICE",
                "carrier": "OTHER",
                "label": "Flat Rate",
                "price": 500,
                "id": 1368,
                "shipping_method_id": "d8fb74f7cd682de8ae35a7fdd1e26f56",
                "bag_id": 1091,
                "merchant_id": 10131
            },
            "taxes": [
                {
                    "order_id": 747,
                    "merchant_id": 10131,
                    "state": "NY",
                    "amount": 2305,
                    "description": "Tax"
                }
            ],
            "sub_total": 23045,
            "shipping_total": 500,
            "tax_total": 2305,
            "total": 25850,
            "taxes_included": false,
            "transactions": [ ... ],
            "external_checkout": true,
            "commission_rate": 0.0,
            "date_created": "2023-03-29T18:12:55+0000",
            "remorse_period_ends": "2023-04-28T18:12:55+0000",
            "currency": "USD",
            "external_currency": "USD",
            "channel": "APP",
            "platform": "BIGCOMMERCE",
            "fulfillments": [],
            "wallet_based_checkout": true,
            "bag_id": 1091,
            "bag_status": "ACCEPTED",
            "merchant_name": "Violet BC"
        },
				{
            "id": 1092,
            "order_id": 747,
            "merchant_id": 10132,
            "app_id": 10193,
            "external_id": "445",
            "status": "ACCEPTED",
            "fulfillment_status": "PROCESSING",
            "financial_status": "PAID",
            "skus": [ ... ],
            "shipping_method": {
                "type": "HEAVY_DUTY_SHIPPING",
                "carrier": "OTHER",
                "label": "Heavy Duty",
                "price": 1000,
                "id": 1368,
                "shipping_method_id": "d8fb74f7cd682de8ae35a7fdd1e26f56",
                "bag_id": 1092,
                "merchant_id": 10132
            },
            "taxes": [
                {
                    "order_id": 747,
                    "merchant_id": 10132,
                    "state": "NY",
                    "amount": 2605,
                    "description": "Tax"
                }
            ],
            "sub_total": 23045,
            "shipping_total": 1000,
            "tax_total": 2605,
            "total": 26650,
            "taxes_included": false,
            "transactions": [ ... ],
            "external_checkout": true,
            "commission_rate": 0.0,
            "date_created": "2023-03-29T18:12:55+0000",
            "remorse_period_ends": "2023-04-28T18:12:55+0000",
            "currency": "USD",
            "external_currency": "USD",
            "channel": "APP",
            "platform": "SHOPIFY",
            "fulfillments": [],
            "wallet_based_checkout": true,
            "bag_id": 1091,
            "bag_status": "ACCEPTED",
            "merchant_name": "Violet Shopify"
        }
    ],
    "shipping_address": { ... },
    "billing_address": { ... },
    ...
}

In order to support the Google Pay term sheet, you will need to split the order from 1 Order with N bags, to N orders each with 1 bag. Each merchant will then have its own order. Prior to the rendering the Google Pay screens, you can create these new Orders against Violet with SKU and any other information you already have pre-filled, and then guide your shopper through multiple Google Pay payment flows. This will complete each order individually, resulting in multiple charges to the shopper on their payment methods, increased payment processing fees for you, and increased payments to Violet depending on your pricing model.