Google Pay iFrame

 
Note

Google Pay is currently a closed Beta feature and does not support CAD at this time.

Pre-Requisites
  • Access to our permissioned Google Pay feature
  • Legal Entity ID (other than your app's) -- required to create an Account
  • Account ID (other than your app's) -- required to create a Payment in the last step of this article

We support the use of Google Pay on your platform. Using Google Pay allows your merchants to simplify the checkout flow, making checkouts faster and improving the conversion performance of your platform. Google Pay payment method tokens are encrypted by Google and decrypted by us, protecting payment information even in the event of a compromised application. Follow this guide to learn how to integrate Google Pay for your merchants.

The sequence diagram below describes the happy path flow of our Google Pay iFrame.

Google PayWePay iFramePartner ServerWePay ServerPayer authorizes paymentEncrypted payload on successReturn response resultsPOST /paymentsGoogle PayWePay iFramePartner ServerWePay Server

Embed The Google Pay iFrame

Step 1: Generate a Google Pay token via our iFrame

Note that this sample is intended to:

  1. Showcase the configurable options available on our Google Pay iFrame
  2. Make the fields in a Google Pay response easily available for testing

Feel free to copy and paste this code into your own project once your app is enabled for Google Pay, or just take the following components:

  • <div id="google_pay_iframe"></div> in your payer-facing HTML
  • <script src="https://cdn.wepay.com/wepay.min.js"></script> in your payer-facing HTML head
  • The entire JS
csshtmljs
Copy
Copied
#shipping-address {
  display: none;
}

#complete-screen {
  display: none;
}
Copy
Copied
<body>
  <div class="container">
    <ul class="collapsible popout">
      <li>
        <div class="collapsible-header"><i class="material-icons">shopping_cart</i>Order</div>

        <div class="collapsible-body" id="order-body">
          <div class="container">
            <div class="row">
              <img class="responsive-img col m6 materialboxed"
                src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/t-1671/mop.png" alt="Primo Mop" />
              <div class="col m5">
                <div class="card-panel blue-grey">
                  <div class="card-content white-text">
                    <span class="card-title">Primo Mop</span>
                    <p>Quantity: 20<br>Price: $15.00 USD</p>
                  </div>
                </div>
              </div>
            </div>
            <div class="row">
              <div class="card-panel blue-grey">
                <div class="card-content white-text center">
                  <p>Subtotal: 300.00 USD<br>Shipping: $7.50 USD<br>Fees: $2.50 USD</p>
                  <span class="card-title">Total: $310.00 USD</span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </li>
      <li class="active">
        <div class="collapsible-header"><i class="material-icons">place</i>Address</div>
        <div class="collapsible-body row" id="address-body">
          <div class="row">
            <div class="col s12">
              <div class="card blue-grey">
                <div class="card-content white-text">
                  <span class="card-title">Billing Address</span>
                  <p>Enter the address associated with your card.</p>
                </div>
              </div>
            </div>
          </div>
          <div class="container">
            <form class="col s12">
              <div class="row">
                <div class="input-field col s6">
                  <input placeholder="first name" id="first_name" type="text" class="validate">
                  <label for="first_name">first name</label>
                </div>
                <div class="input-field col s6">
                  <input placeholder="last name" id="last_name" type="text" class="validate">
                  <label for="last_name">last name</label>
                </div>
              </div>
              <div class="row">
                <div class="input-field col s12">
                  <input placeholder="email" id="email" type="text" class="validate">
                  <label for="email">email</label>
                </div>
              </div>
              <div class="row">
                <div class="input-field col s6">
                  <input placeholder="street address" id="street_address" type="text" class="validate">
                  <label for="street_address">street address</label>
                </div>
                <div class="input-field col s6">
                  <input placeholder="suite / apt." id="suite" type="text" class="validate">
                  <label for="suite">suite / apt.</label>
                </div>
              </div>
              <div class="row">
                <div class="input-field col s4">
                  <input placeholder="city" id="city" type="text" class="validate">
                  <label for="city">city</label>
                </div>
                <div class="input-field col s4">
                  <input placeholder="state" id="state" type="text" class="validate">
                  <label for="state">state</label>
                </div>
                <div class="input-field col s4">
                  <input placeholder="zip" id="zip" type="text" class="validate">
                  <label for="zip">zip</label>
                </div>
              </div>
            </form>
            <form action="#">
              <p>
                <label>
                  <input type="checkbox" id="same" onclick="showMe('shipping-input')" />
                  <span>Different from shipping address?</span>
                </label>
              </p>
            </form>
          </div>
          <form class="col s12" id="shipping-address">
            <div class="row">
              <div class="col s12">
                <div class="card blue-grey">
                  <div class="card-content white-text">
                    <span class="card-title">Shipping Address</span>
                    <p>Enter the address where your order will be shipped.</p>
                  </div>
                </div>
              </div>
            </div>
            <div class="container">
              <div class="row">
                <div class="input-field col s6">
                  <input placeholder="first name" id="first_name" type="text" class="validate">
                  <label for="first_name">first name</label>
                </div>
                <div class="input-field col s6">
                  <input placeholder="last name" id="last_name" type="text" class="validate">
                  <label for="last_name">last name</label>
                </div>
              </div>
              <div class="row">
                <div class="input-field col s12">
                  <input placeholder="email" id="email" type="text" class="validate">
                  <label for="email">email</label>
                </div>
              </div>
              <div class="row">
                <div class="input-field col s6">
                  <input placeholder="street address" id="street_address" type="text" class="validate">
                  <label for="street_address">street address</label>
                </div>
                <div class="input-field col s6">
                  <input placeholder="suite / apt." id="suite" type="text" class="validate">
                  <label for="suite">suite / apt.</label>
                </div>
              </div>
              <div class="row">
                <div class="input-field col s4">
                  <input placeholder="city" id="city" type="text" class="validate">
                  <label for="city">city</label>
                </div>
                <div class="input-field col s4">
                  <input placeholder="state" id="state" type="text" class="validate">
                  <label for="state">state</label>
                </div>
                <div class="input-field col s4">
                  <input placeholder="zip" id="zip" type="text" class="validate">
                  <label for="zip">zip</label>
                </div>
              </div>
          </form>
        </div>
        <button class="btn waves-effect waves-light" type="submit" name="action"
          onclick="closeAddress(); openSubmit();">Next
          <i class="material-icons right">send</i>
        </button>
  </div>
  </li>
  <li id="checkout">
    <div class="collapsible-header"><i class="material-icons">credit_card</i>Submit</div>
    <div class="collapsible-body" id="submit">
      <span>
        <div class="container">
          <div class="row">
            <img class="responsive-img" id="accepted-cards"
              src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/t-1671/card-brands.png" border="0"
              alt="Accepted Cards" />
          </div>
          <div class="row">
            <div id="google_pay_iframe"></div>
          </div>
          <div class="row">
            <div class="card blue-grey lighten-2">
              <div class="card-content white-text">
                <p>By clicking submit, you agree to Mop Emporium's <a href="#">refund, cancellation, and return</a>
                  policy.<br>Orders will be shipped in 2-3 business days, at which time tracking information will be
                  provided in a confirmation email.</p>
              </div>
            </div>
          </div>
          <button class="btn waves-effect waves-light" type="submit" name="action" id="submit-credit-card-button">Submit
            <i class="material-icons right">send</i>
          </button>
          <div id="token"></div>
        </div>
      </span>
    </div>
  </li>
  </ul>
  <div class="row">
    <div id="tokenBank"></div>
  </div>
  <div class="row">
    <div id="complete-screen" class="row">
      <div class="col s12 m12">
        <div class="card blue-grey darken-1">
          <div class="card-content white-text">
            <span class="card-title">Your order is complete!</span>
            <p>We're working on getting your order ready to ship. Once it does, you can log in and track it
              here.<br><br><br><br><br></p>
          </div>
          <div class="card-action">
            <a href="#">Track your order</a>
            <a href="#">Help</a>
          </div>
        </div>
      </div>
    </div>
  </div>
  </div>
  <script src="https://cdn.wepay.com/wepay.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
</body>
Copy
Copied
//The test app ID for GooglePay has been added here
var appId = "690619";

var myAppId = appId;
var apiVersion = "3.0";
var error = WePay.configure("stage", myAppId, apiVersion);
if (error) {
  console.log(error);
}
const google_pay_configs = {
    button_configs: {
        // "default" | "black" | "white"
        buttonColor: 'black',
        // "book" | "buy" | "checkout" | "donate" | "order" | "pay" | "plain" | "subscribe" | "long" | "short"
        buttonType: 'checkout',
        // "en" | "ar" | "bg" | "ca" | "cs" | "da" | "de" | "el" | "es" | "et" | "fi" |
        // "fr" | "hr" | "id" | "it" | "ja" | "ko" | "ms" | "nl" | "no" | "pl" |
        // "pt" | "ru" | "sk" | "sl" | "sr" | "sv" | "th" | "tr" | "uk" | "zh"
        buttonLocale: 'en',
        // "static" | "fill"
        buttonSizeMode: 'fill',
        // any string
        className: undefined,
        // jsx style css
        style: {
            width: "100%",
            height: "100%",
        },
        paymentRequest: {
            merchantInfo: {
                merchantName: 'Mop City'
            },
            // boolean
            emailRequired: true,
            /**
             * if required, shipping address must be specified
             * shippingAddressParameters:
             * {
             *  allowedCountryCodes: string[]
             *  phoneNumberRequired?: boolean
             * }
             */
            shippingAddressRequired: true,
            shippingAddressParameters: {
                allowedCountryCodes: ['US', 'CA', 'AU'],
                phoneNumberRequired: true,
            },
            allowedPaymentMethods: {
                // 'MASTERCARD' | 'VISA' | 'AMEX' | 'DISCOVER' | 'JCB'
                allowedCardNetworks: ['VISA', 'MASTERCARD', 'AMEX', 'DISCOVER', 'JCB'],
                // boolean
                billingAddressRequired: true,
            },
            transactionInfo: {
                // "NOT_CURRENTLY_KNOWN" | "ESTIMATED" | "FINAL"
                totalPriceStatus: 'FINAL',
                // any string
                totalPriceLabel: 'Total',
                // '4.0'
                totalPrice: '310.0',
                // USD / CAD etc.
                currencyCode: 'USD',
                // US / CA etc.
                countryCode: 'US',
                // "DEFAULT" | "COMPLETE_IMMEDIATE_PURCHASE"
                checkoutOption: 'DEFAULT',
                /**
                 * DisplayItemType: "LINE_ITEM" | "SUBTOTAL" | "TAX" | "DISCOUNT" | "SHIPPING_OPTION"
                 * DisplayItemStatus: "FINAL" | "PENDING"
                 *
                 * displayItems: [{ label: string, type: DisplayItemType, price: string, status?; DisplayItemStatus }]
                 */
                displayItems: [
                    { label: '20x Primo Mop', type: 'LINE_ITEM', price: '15.00' },
                    { label: "Subtotal", type: 'SUBTOTAL', price: '300.00' },
                    { label: "Shipping", type: 'LINE_ITEM', price: '0' },
                    { label: "Fees", type: 'TAX', price: '2.50' },
                ]
            },
            offerInfo: {
                // [{ redemptionCode: string, description: string }]
                offers: [{ redemptionCode: 'BIGSPENDER', description: "10% Off Orders of $100 or more" }]
            },
            /**
             * if required, shipping options must be specified
             * shippingOptionParameters:
             * {
             *  shippingOptions: [{ id: string, label: string, description?: string}]
             *  defaultSelectedOptionId: string
             * }
             */
            shippingOptionRequired: true,
            shippingOptionParameters: {
                shippingOptions: [
                    { id: 'ground', label: 'Ground', description: '7 - 250 Business Days - FREE' },
                    { id: 'priority', label: 'Priority', description: '7 - 25 Business Days - $15.99' },
                    { id: 'nextday', label: 'Next Day', description: '1 Business Day - $399.99' },
                ],
                defaultSelectedOptionId: 'ground',
            },
        },
    },
    on_success: function (data) {
        document.getElementById('token').innerHTML = JSON.stringify(data);
    },
    on_error: function (error) {
        document.getElementById('token').innerHTML = JSON.stringify(error);
    },
      /**
     * input:
     * intermediatePaymentData {
     *  // INITIALIZE is sent when the payment window is opened, the rest are
     *  // when the cardholder selects one of the relevant options
     *  callbackTrigger: "INITIALIZE" | "SHIPPING_ADDRESS" | "SHIPPING_OPTION" | "OFFER"
     *  offerData?: {
     *      redemptionCodes: string[],
     *  },
     *  shippingAddress?: {
     *      administrativeArea: string // "CA"
     *      countryCode: string // "US"
     *      locality: string // "Mountain View"
     *      postalCode: string // "94043"
     *  },
     *  shippingOptionData?: {
     *      id: string // the id of the selected shipping option
     *  }
     *
     *  returns
     *  newPaymentData {
     *    newTransactionInfo?: {
     *      currencyCode: string
     *      countryCode?: string
     *      totalPrice: string
     *      totalPriceLabel?: string
     *      totalPriceStatus: "NOT_CURRENTLY_KNOWN" | "ESTIMATED" | "FINAL"
     *      transactionNote?: string
     *      checkoutOption?: "DEFAULT" | "COMPLETE_IMMEDIATE_PURCHASE"
     *      displayItems?: {
     *          label: string
     *          type: "LINE_ITEM" | "SUBTOTAL" | "TAX" | "DISCOUNT" | "SHIPPING_OPTION"
     *          price: string
     *          status?: "FINAL" | "PENDING"
     *      }
     *    },
     *    newShippingOptionParameters?: {
     *      shippingOptions: [{ id: string, label: string, description?: string}]
     *      defaultSelectedOptionId?: string
     *    },
     *    newOfferInfo?: {
     *      offers: [{ redemptionCode: string, description: string }]
     *    },
     *    error?: {
     *      reason: "SHIPPING_ADDRESS_INVALID" | "SHIPPING_ADDRESS_UNSERVICEABLE" | "SHIPPING_OPTION_INVALID" | "OFFER_INVALID" | "PAYMENT_DATA_INVALID" | "OTHER_ERROR"
     *      intent: // same as callbackIntent - "OFFER" | "SHIPPING_ADDRESS" | "SHIPPING_OPTION" | "PAYMENT_AUTHORIZATION" | "PAYMENT_METHOD"
     *      message: string
     *    }
     *  }
     * }
     */
    on_update_payment_data: function (intermediatePaymentData) {
        return getNewPaymentMethodData(intermediatePaymentData);
    }
};

const OFFERS = {
    SPRING25: {
        label: "25% Off Order",
        value: .25,
    },
    BIGSPENDER: {
        label: "10% Off Orders of $100 or more",
        value: .1,
    }
};

const SHIPPING_COSTS = {
    CA: {
        ground: 12.99,
        priority: 29.99,
        nextday: 1199.99,
    },
    US: {
        ground: 0,
        priority: 15.99,
        nextday: 399.99,
    }
};

const TAX_RATES = {
    CA: .12,
    US: .0925,
};

const CURRENCY_CODES = {
    CA: 'CAD',
    US: 'USD',
};

const getNewPaymentMethodData = (intermediatePaymentData) => {
    const country = intermediatePaymentData?.shippingAddress?.countryCode;
    const shippingOption = intermediatePaymentData?.shippingOptionData?.id;
    const offers = intermediatePaymentData?.offerData?.redemptionCodes;

    const error = getErrors(country, offers);

    if (error) {
        return error;
    }

    return {
        ...getNewTransactionInfo(offers, shippingOption, country),
        ...getNewShippingOptionParameters(country, shippingOption),
        ...getNewOfferInfo(offers),
    };
};

const getErrors = (country, offers = []) => {
    // verifying shipping address without actually verifying
    if (country && !(country in CURRENCY_CODES)) {
        return {
            error: {
                reason: "SHIPPING_ADDRESS_INVALID",
                intent: "SHIPPING_ADDRESS",
                message: "We're sorry, we cannot ship to that address. Please select another one."
            }
        };
    }

    const withoutInvalidOffers = offers.filter((offer) => offer in OFFERS);
    const hasInvalidOffer = withoutInvalidOffers.length !== offers.length;
    if (hasInvalidOffer) {
        return {
            newOfferInfo: {
                offers: getOfferDetails(withoutInvalidOffers)
            },
            error: {
                reason: "OFFER_INVALID",
                intent: "OFFER",
                message: "Invalid offer entered. Please try again.",
            }
        };
    }

    const selectedOffers = new Set();
    const withoutDuplicates = offers.filter((offer) => {
        if (selectedOffers.has(offer)) {
            return false;
        }
        selectedOffers.add(offer);
        return true;
    });
    const hasDuplicates = withoutDuplicates.length !== offers.length;

    if (hasDuplicates) {
        return {
            newOfferInfo: {
                offers: getOfferDetails(withoutDuplicates)
            },
            error: {
                reason: "OFFER_INVALID",
                intent: "OFFER",
                message: "Invalid offer entered. Maximum one use per offer.",
            }
        };
    }
};

const getNewTransactionInfo = (offers, shippingOption, country) => {
    const priceDetails = getPriceDetails(offers, shippingOption, country);

    return {
        newTransactionInfo: {
            currencyCode: 'USD',
            countryCode: 'US',
            totalPrice: priceDetails.total,
            displayItems: getDisplayItems(priceDetails),
            totalPriceStatus: 'FINAL',
            totalPriceLabel: 'Total',
        }
    };
};

const getPriceDetails = (offers, shippingOption, country = 'US') => {
    const base = 300;
    const shipping = SHIPPING_COSTS[country]?.[shippingOption];
    const offerAmount = offers && offers.reduce((total, offer) => offer in OFFERS ? total + OFFERS[offer].value : total, 0);

    const discount = offerAmount && base * offerAmount;

    const subTotal = base - (discount || 0);
    const tax = subTotal * TAX_RATES[country];

    const total = subTotal + tax + (shipping || 0);

    return {
        base: base.toFixed(2),
        shipping: shipping && shipping.toFixed(2),
        discount: discount && discount.toFixed(2),
        tax: tax.toFixed(2),
        total: total.toFixed(2),
    };
};

const getDisplayItems = ({ base, shipping, discount, tax }) => {
    return [
        { label: '20x Primo Mop', type: 'LINE_ITEM', price: '15.00' },
        { label: "Subtotal", type: 'SUBTOTAL', price: base },
        discount && { label: 'Discount', type: 'LINE_ITEM', price: `-${discount}` },
        shipping && { label: "Shipping", type: 'LINE_ITEM', price: shipping },
        { label: "Tax", type: 'TAX', price: tax },
    ].filter(Boolean);
};

const getNewShippingOptionParameters = (country, shippingOption) => {
    if (!shippingOption) {
        return {};
    }

    return {
        newShippingOptionParameters: {
            shippingOptions: getShippingOptions(country),
            defaultSelectedOptionId: shippingOption,
        }
    };
};

const getShippingOptions = (country = 'US') => {
    const { ground, priority, nextday } = SHIPPING_COSTS[country];

    return [
        {
            id: 'ground',
            label: 'Ground',
            description: `7 - 250 Business Days - ${freeOrMoney(ground)}`,
        },
        {
            id: 'priority',
            label: 'Priority',
            description: `7 - 25 Business Days - ${freeOrMoney(priority)}`,
        },
        {
            id: 'nextday',
            label: 'Next Day',
            description: `1 Business Day - ${freeOrMoney(nextday)}`,
        },
    ];
};

const freeOrMoney = (amount) => amount === 0 ? 'FREE' : `$${amount}`;

const getNewOfferInfo = (offers) => {
    if (!offers) {
        return {};
    }

    return {
        newOfferInfo: {
            offers: getOfferDetails(offers),
        }
    };
};

const getOfferDetails = (offers) => {
    return offers.map((offer) => ({
        redemptionCode: offer,
        description: OFFERS[offer].label,
    }));
};

const google_pay_container_id = "google_pay_iframe";
WePay.createGooglePayIframe(google_pay_container_id, google_pay_configs);

// ok

document.addEventListener('DOMContentLoaded', function() {
  // initialize materialize collapsible
  var collapsibleElement = document.querySelector('.collapsible');
  var collapsibleInstance = M.Collapsible.init(collapsibleElement);

  // initialize materialize material box
  var materialBox = document.querySelectorAll('.materialboxed');
  var instances = M.Materialbox.init(materialBox);

  // load with address open
  collapsibleInstance.open(2);
});
// show shipping address if different
function showMe() {
  var box = document.getElementById('same');
  var vis = (box.checked) ? "block" : "none";
  document.getElementById('shipping-address').style.display = vis;
}
// close address section on "next" click
function closeAddress() {
  var elems = document.querySelector('.collapsible');
  var instances = M.Collapsible.init(elems);
  instances.close(1);
}
// open submit section on "next" click
function openSubmit() {
  var elems = document.querySelector('.collapsible');
  var instances = M.Collapsible.init(elems);
  instances.open(2);
}

Configure The Google Pay Button

You can configure the following properties of the Google Pay button, as seen starting on line 10 of the CodePen:

  • Button color
  • Button language
  • Button text (e.g. checkout, buy, pay, etc.)
  • Button size behavior

You can also add custom styling.

Configure The Google Pay Request

You can configure the following properties of the Google Pay request, as seen starting on line 29 of the CodePen:

  • Accepted card brands -- Note that this must match the card brands that we accept
  • Merchant name
  • Whether the payment sheet will require the payer's email address
  • Whether the payment sheet will require the payer's shipping address
  • Further define requirements of the shipping address, if required
  • Details about the transaction (e.g. amount, currency, display items, etc)
  • Discount details
  • Whether the payer will be required to select a shipping method
  • Details about shipping method selections to show the payer

Again, your configuration must include setting the accepted card brands to the card brands that we accept:

Copy
Copied
const allowedCardNetworks = ["AMEX", "DISCOVER", "INTERAC", "JCB", "MASTERCARD", "VISA"];

Send the returned Google Pay token to your server in preparation for using it with the WePay APIs.


Use The Google Pay Token

Step 2: Convert the Google Pay token into a WePay Payment Method

After you've generated a Google Pay token with your own app ID, you will need to send a POST /payment_methods request to us, using the Google Pay token returned by our iFrame.

See the sample request below, where the Google Pay token, payment method type, and cardholder information need to be provided. All the required card holder information can be retreived from the iFrame response. See our Process Payments article for further information on processing payments.
Note
Be sure to use the Pre-Release version 3.0.rc.2.1 or 3.2 in the Version header parameter for your requests to this endpoint.

Example request to POST /payment_methods:

Copy
Copied
  curl -X POST \
  --url 'https://stage-api.wepay.com/payment_methods' \
  -H 'Accept: application/json'\
  -H 'App-Id: {YOUR-APP-ID}'\
  -H 'App-Token: {YOUR-APP-TOKEN}'\
  -H 'Api-Version: '3.2'\
  -H 'Content-Type: application/json'\
  -H 'Unique-Key: {UNIQUE-KEY}
  --data-raw {
        "type": "google_pay",
        "google_pay": {
            "payment_method_data": "{googlePayToken_value}",
            "card_holder": {
                "holder_name": "John Snow",
                "email": "example@wepay.com",
                "address": {
                    "country": "US",
                    "postal_code": "94025"
                }
            }
        }
    }

Below is a sample response payload where a WePay payment method ID will be returned (be sure to keep this handy - you'll need to use this to create and capture a payment later), as well as fill in other information.

Example response from POST /payment_methods:
Copy
Copied
{
  "id": "00000000-6363-0000-0000-00521ec2df84",
  "resource": "payment_methods",
  "path": "/payment_methods/00000000-6363-0000-0000-00521ec2df84",
  "owner": {
      "id": "27415",
      "resource": "applications",
      "path": null
  },
  "create_time": 1629930873,
  "type": "google_pay",
  "google_pay": {
      "card_holder": {
          "holder_name": "Tony Stark",
          "email": "example@wepay.com",
          "address": {
              "line1": null,
              "line2": null,
              "city": null,
              "region": null,
              "postal_code": "94025",
              "country": "US"
          },
          "phone": {
              "country_code": null,
              "phone_number": null,
              "type": null
          }
      },
      "expiration_month": 4,
      "expiration_year": 2030,
      "display_name": "MasterCard xxxxxx4769"
  },
  "custom_data": null,
  "api_version": "3.0"
}

Create A Wepay Payment

Step 3: Use the WePay Payment Method ID to create a Payment

Use the WePay payment method ID returned above to make a POST /payment_methods request. You'll use the id value from the POST /payment_methods response as the value for payment_method.payment_method_id in the POST /payments request.

See our Capture Authorized Payments guide to learn about how to execute manual, deferred, and partial captures of payments.

Example request to POST /payments:

Copy
Copied
curl -X POST \
  --url 'https://stage-api.wepay.com/payments' \
  -H 'Accept: application/json'\
  -H 'App-Id: {YOUR-APP-ID}'\
  -H 'App-Token: {YOUR-APP-TOKEN}'\
  -H 'Api-Version: 3.0'\
  -H 'Content-Type: application/json'\
  -H 'Unique-Key: {UNIQUE-KEY}
  --data-raw '{
	"amount": AMOUNT_AS_INTEGER,
	"currency": "USD",
	"account_id": "{merchant's-account-id}",
	"payment_method": {
            "payment_method_id": "{INSERT_PAYMENT_METHOD-ID}",
            "type": "payment_method_id"
	}
}
Read through the Risk Certification guide to identify standard required payment-level rBits and Risk Headers. A payment may be flagged for manual risk review in which it is subject to 1-2 business days hold. The inclusion of payment-level rBits reduces the frequency and duration of manually reviewed payments and allows our Risk team to accurately action a payment.

Example response from POST /payments:
Copy
Copied
{
	"amount" : 1000,
	"amount_refunded" : 0,
	"amount_disputed" : 0,
	"auto_capture" : true,
	"capture_at" : null,
	"create_time" : 1510080179,
	"currency" : "USD",
	"custom_data" : null,
	"failure_reason" : null,
	"fee_amount" : 0,
	"id" : "00000000-0000-0000-0000-0000767bf5cd",
	"order" : null,
	"owner" : {
		"id" : "{merchant's-account-id}",
		"path" : "/accounts/{merchant's-account-id}",
		"resource" : "accounts"
	},
	"path" : "/payments/00000000-0000-0000-0000-0000767bf5cd",
	"payment_method" : {
		"id" : "00000000-6363-0000-0000-000008b93a6e",
		"path" : "/payment_methods/00000000-6363-0000-0000-000008b93a6e",
		"resource" : "payment_methods"
	},
	"pending_reasons" : [
		{
			"details" : [],
			"reason_code" : "PROCESSING",
			"reason_message" : "Payment is being processed."
		}
	],
	"resource" : "payments",
	"status" : "pending",
	"authorization_code" : "0224241",
	"txnr_app_fee" : null,
	"txnr_merchant" : null,
	"initiated_by" : "none",
	"api_version": "3.0"
}

Create a Test Payment

Follow these steps to create test payments with Google Pay:

  1. Initialize the Google Pay iFrame provided in this guide.
  2. For testing only, subscribe to the Google Test Card Suite in order to utilize test cards.
  3. Generate a Google Pay token from the iFrame.
  4. Call POST/ payment_methods with API version 3.0.rc.2.1 or 3.2 and convert the Google Pay token into a payment method. (Note: a Google Pay token needs to be converted into a payment method and cannot be directly ingested in a POST/ payments call)
  5. Utilize the generated payment_methods_id in the POST/ payments call to make a successful authorization call (set auto_capture to true to capture the auth immediately or call POST/ payments/{id}/capture to capture the payment later).
Note
The Google Pay iFrame and the POST/ payment_methods endpoint with API version 3.0.rc.2.1 or 3.2 can also be used to generate payment_method_token and payment_methods_id for CC/DB/ACH respectively.