Skip to content

Latest commit

 

History

History
386 lines (318 loc) · 22.7 KB

TecGooglePayREADME.md

File metadata and controls

386 lines (318 loc) · 22.7 KB

TEConnect Google Pay

This document demonstrates the available options, using Google Pay via TEConnect. TEConnect currently offers a Manual Entry form, Apple Pay and Google Pay. Your app may use one - or all of these platforms to collect a payment token. The section below explains how opt-in works with TEConnect, for each available platform. The remainder of the document focuses on Google Pay with TEConnect.

Payment Request Opt-In

createTEConnect accepts a public key as the first argument. This public key is for TEConnect Manual Entry. The second argument is the TEConnect options object.
Providing an appleMerchantId or a googleMerchantId to the tecPaymentRequest object is how to opt-in for each Payment Request Platform. So providing an appleMerchantId is an opt-in for Apple Pay - and a googleMerchantId for Google Pay. Example below.

const TE_CONNECT: TEConnect = createTEConnect("__publicKeyGoesHere__", {
    tecPaymentRequest: {
        appleMerchantId: "__tecAppleMerchantId__"
        googleMerchantId: "__googleMerchantId__",
    }
});

Opting in to one or more platform affects the way the Payment Request Object should be supplied to TEConnect - as well as the CanMakePaymentsResult.

Be aware that if you only opt-in for one platform (i.e. only an googleMerchantId is provided to createTEConnect) - you may provide the payment request object for that specific platform. In this case - the Google Pay can be provided as-is.

In the case of multiple payment request platforms (i.e. both an appleMerchantId and googleMerchantId are provided) - the payment request object must be structured to reflect each platform. Example below.

const paymentRequestObject = {
    applePay: applePayRequestObject,
    googlePay: googlePayRequestObject
}

Important Google Pay Notes

The Google Pay workflow differs from Apple Pay in a few ways.

The confirm-token Event is only mandatory if you do not provide the optional callbackIntents in your payment request object. If you do choose to leverage an onPaymentAuthorized callback - the token will be presented in that callback function, prior to the confirm-token Event. By registering an onPaymentAuthorized callback - the Google Pay form will wait for a positive or negative response from your application before closing (waiting for your application's processing response). This callback is optional - and the default behavior is the form closes upon token generation (confirm-token Event). The minimally viable code example uses the confirm-token Event, while the more complex implementation uses the onPaymentAuthorized callback to capture the token.

Apple Pay Id (appleMerchantId) is the only required ID to complete the Apple Pay workflow. Google Pay, on the other hand, requires three points of identification: merchantId, merchantName, and gatewayId. The merchantId and merchantName will be negotiated directly between you and Google. Google's documentation on MerchantInfo and their Wallet Console is very detailed - just remember that the merchantId is fed to the createTEConnect function, while the other points of information are provided in the payment request object. The gatewayId is provided by Magensa™ after a successful account creation.

Apple Pay uses an event driven workflow (Apple Pay Listeners), while Google uses optional callbacks. This means that for Apple Pay - you will be leveraging TEConnect's listenFor method, wheras for Google - any optional callback or options will be provided directly in the payment request object. This can help differentiate which platform the end-user is using - as MPPG will have different operations to process the tokens created (Manual Entry, Apple Pay, or Google Pay).


Google Pay Payment Request Object

This section lists all available properties that are available for use with TEConnect. Many of the properties correspond directly to Google's documentation on the payment request object (found here). Some of the properties listed in Google's documentation (such as tokenizationSpecification) are built by TEConnect, and as such is not read as an input.

An Example for a simple implementation can be found here. An example for the most complex object allowed can be found below.

For more information on each property - please see Google's Documentation.

Property Name Input Type Required Default Value (if optional) Notes
apiVersion number 2 Optional field to set the major version of Google Pay API
apiVersionMinor number 0 Optional field to set the minor version of Google Pay API
allowedAuthMethods string[] ["PAN_ONLY", "CRYPTOGRAM_3DS"] More details on allowedAuthMethods here
allowedCardNetworks string[] ✔️ N/A More details on allowedCardNetworks here
merchantName string ✔️ N/A merchantId is provided to the createTeConnect call. merchantName is provided in the payment request object. More info here
gatewayId string ✔️ N/A Given by Magensa™ after a successful account creation
transactionInfo TransactionInfo ✔️ More details on all available properties here
callbackIntents string[] N/A Declares intents for paymentDataCallbacks
paymentDataCallbacks object N/A Callback functions for dynamic price updates, and user-facing messages via Google Pay form
offerInfo OfferInfo N/A OfferInfo is displayed when the payment sheet loads. Offer implementation example here
emailRequired boolean N/A set true to request an email address
shippingAddressRequired boolean N/A set true to request a full shipping address
shippingOptionRequired boolean N/A Set to true when the SHIPPING_OPTION callback intent is used. This field is required if you implement support for Authorize Payments or Dynamic Price Updates. More info on ShippingOptionParameters
shippingAddressParameters object N/A If shippingAddressRequired is set to true, specify shipping address restrictions
shippingOptionParameters ShippingOptionParameters[] N/A Set default options. More details on ShippingOptionParameters here
allowedPaymentMethods PaymentMethod[] N/A TEConnect will build the default "CARD" Payment Method. This property allows for optionally adding more. Be aware that every PaymentMethod supplied will receive Magensa's tokenizationSpecification. Overrides for tokenizationSpecification is not supported at this time. It is unlikely this property will be utilizied, but is available for future iterations.
environment string "TEST" "TEST" or "PRODUCTION". More details on PaymentOptions.environment here
prefetchPaymentData boolean N/A If set to true, prefetchPaymentData will be called upon button mount. More info on prefetchPaymentData here

Google Pay Button Options

Google Pay offers many button options to tailor the Google Pay Button to the applications theme. Here are Google's Brand Guidelines that explain how the button may be styled appropriately. Here we have Google's list of ButtonOptions. These button options are available with a few restrictions (two properties managed by TEConnect).

TEConnect is responsible for creating and mounting the button. To that end - TEConnect will handle the allowedPaymentMethods property, which is based upon the payment request object supplied to TEConnect, in addition to any additional payment methods supplied in the optional property allowedPaymentMethods. It's unlikely any additional allowedPaymentMethods need be supplied (Google Pay currently only supports the "CARD" payment method which is built based upon the payment request object supplied to TEConect), however adding additional payment methods is supported if desired.

TEConnect is also responsible for the button click handler (onClick). If any action is needed before the payment form is rendered - make use of the preClick property in the TEConnect options to provide your own function. The preClick function has its limitations, and is only intended for application triggers prior to the Google Pay button click:

  • preClick functions will be called synchronously. The function is called before returning the async onClick function.
  • Any return value from the preClick function will not be returned to the caller.

Below is a table of available properties, and after that are examples. Feed the options object to the TecPaymentRequestButtons component.

Property Name Input Type Notes
preClick function will be called syncronously. Return value will not be returned to caller.
buttonColor string See Google's ButtonOptions
buttonType string See Google's ButtonOptions
buttonLocale string See Google's ButtonOptions
buttonSizeMode string See Google's ButtonOptions
buttonRootNode HTMLDocument or ShadowRoot See Google's ButtonOptions
const exampleGoogleButtonOptions = {
    preClick: () => console.log('preClick executed'),
    buttonColor: 'black',
    buttonType: 'order',
    buttonLocale: 'en', //If not supplied - defaults to browser or OS language settings
    buttonSizeMode: 'static'
}

const buttonOptions = {
    googlePayOptions: exampleGoogleButtonOptions
}

import { createTEConnect } from '@magensa/te-connect';
import { TeConnectJs } from '@magensa/te-connect-js';


function demoInit() {
    const teConnectInstance = createTEConnect("__publicKeyGoesHere__", { 
        tecPaymentRequest: { googleMerchantId: "__tecGoogleMerchantId__" } 
    });

    var teConnect = new TeConnectJs(teInstance);

    var tecPrInterface = teConnect.createTecPaymentRequest(googlePaymentRequestObject);

    tecPrInterface.canMakePayments().then( canMakePaymentsResult => {
        if (canMakePaymentsResult)
            tecPrInterface.mountTecPrButton("example-div", buttonOptions);
    });

    tecPrInterface.listenFor('confirm-token', tokenResp => {
        const { tokenDetails, completePayment, error } = tokenResp;

        if (error) {
            //Unsuccessful - check message.
        }
        else {
            //Successful - pass `tokenDetails.token` to MPPG to process payment
        }
    });
}

demoInit();

Google Pay Example Implementation

The below implementation utilizes most of the Google Pay features, for a more complex example. Your application's specific implementation will differ according to your needs.

Google also provides several implementation examples as well - just be aware that TEConnect handles most of the interactions between the Google Pay API and your application - meaning that most all of your logic will be provided in the payment request object (as opposed to the various sections provided in Google's examples).

index.html

<body>
    <h1>Token Exchange Connect Payment Request</h1>
    <div id="example-div"></div>
    <script src='main.bundle.js'></script>
</body>
//======= Build Payment Request Object =======

function getGoogleTransactionInfo() {
	return {
        displayItems: [{
			label: "Subtotal",
			type: "SUBTOTAL",
			price: "11.00",
        },
		{
            label: "Tax",
            type: "TAX",
            price: "1.00",
        }],
        countryCode: 'US',
        currencyCode: "USD",
        totalPriceStatus: "FINAL",
        totalPrice: "12.00",
        totalPriceLabel: "Total"
	};
}

function processPayment(paymentData) {
	return new Promise(function(resolve, reject) {
        console.log(paymentData);
        // When using an 'onPaymentAuthorized' callback - pass the property below to MPPG for payment processing.
        console.log(paymentData.paymentMethodData.tokenizationData.token);
        //If processing is successful - resolve - otherwise reject
        resolve({});
    });
}

function onPaymentAuthorized(paymentData) {
    // When using an 'onPaymentAuthorized' callback - you don't need to 'listenFor' 'confirm-token'.
	return new Promise(function(resolve, reject) {
        processPayment(paymentData)
        .then(function() {
            resolve({transactionState: 'SUCCESS'});
        })
        .catch(function() {
            resolve({
                transactionState: 'ERROR',
                error: {
                    intent: 'PAYMENT_AUTHORIZATION',
                    message: 'Insufficient funds',
                    reason: 'PAYMENT_DATA_INVALID'
                }
            });
        });
    });
}

function getGoogleUnserviceableAddressError() {
	return {
        reason: "SHIPPING_ADDRESS_UNSERVICEABLE",
        message: "Cannot ship to the selected address",
        intent: "SHIPPING_ADDRESS"
	};
}

function getGoogleDefaultShippingOptions() {
	return {
        defaultSelectedOptionId: "shipping-001",
        shippingOptions: [{
            "id": "shipping-001",
            "label": "Free: Standard shipping",
            "description": "Free Shipping delivered in 5 business days."
        },
        {
            "id": "shipping-002",
            "label": "$1.99: Standard shipping",
            "description": "Standard shipping delivered in 3 business days."
        },
        {
            "id": "shipping-003",
            "label": "$10: Express shipping",
            "description": "Express shipping delivered in 1 business day."
        }]
    };
}

function getShippingCosts() {
	return {
        "shipping-001": "0.00",
        "shipping-002": "1.99",
        "shipping-003": "10.00"
    }
}

function calculateNewTransactionInfo(shippingOptionId) {
	let newTransactionInfo = getGoogleTransactionInfo();

    let shippingCost = getShippingCosts()[shippingOptionId];
    newTransactionInfo.displayItems.push({
        type: "LINE_ITEM",
        label: "Shipping cost",
        price: shippingCost,
        status: "FINAL"
    });

    let totalPrice = 0.00;
    newTransactionInfo.displayItems.forEach(displayItem => totalPrice += parseFloat(displayItem.price));
    newTransactionInfo.totalPrice = totalPrice.toString();

    return newTransactionInfo;
}

function onPaymentDataChanged(intermediatePaymentData) {
	return new Promise(function(resolve, reject) {
		let shippingAddress = intermediatePaymentData.shippingAddress;
		let shippingOptionData = intermediatePaymentData.shippingOptionData;
		let paymentDataRequestUpdate = {};

		if (intermediatePaymentData.callbackTrigger === "INITIALIZE" || intermediatePaymentData.callbackTrigger === "SHIPPING_ADDRESS") {
			if(shippingAddress.administrativeArea === "NJ")  {
				paymentDataRequestUpdate.error = getGoogleUnserviceableAddressError();
			}
			else {
				paymentDataRequestUpdate.newShippingOptionParameters = getGoogleDefaultShippingOptions();
				let selectedShippingOptionId = paymentDataRequestUpdate.newShippingOptionParameters.defaultSelectedOptionId;
				paymentDataRequestUpdate.newTransactionInfo = calculateNewTransactionInfo(selectedShippingOptionId);
			}
		}
		else if (intermediatePaymentData.callbackTrigger === "SHIPPING_OPTION") {
		    paymentDataRequestUpdate.newTransactionInfo = calculateNewTransactionInfo(shippingOptionData.id);
		}

		resolve(paymentDataRequestUpdate);
	});
}

const googlePaymentRequestObject = {
	apiVersion: 2,
	apiVersionMinor: 0,
	allowedCardNetworks: ["AMEX", "DISCOVER", "JCB", "MASTERCARD", "MIR", "VISA"],
	merchantName: "TEConnect Test",
	gatewayId: "__magensa_gateway_id__",
	transactionInfo: {
        displayItems: [{
			label: "Subtotal",
			type: "SUBTOTAL",
			price: "11.00",
        },
      	{
			label: "Tax",
			type: "TAX",
			price: "1.00",
		}],	
		countryCode: 'US',
		currencyCode: "USD",
		totalPriceStatus: "FINAL",
		totalPrice: "12.00",
		totalPriceLabel: "Total"
  	},
	callbackIntents: ["SHIPPING_ADDRESS",  "SHIPPING_OPTION", "PAYMENT_AUTHORIZATION"],
	emailRequired: true,
	shippingAddressRequired: true,
	shippingOptionRequired: true,
	shippingAddressParameters: {
		allowedCountryCodes: ['US'],
		phoneNumberRequired: true
	},
	shippingOptionParameters: getGoogleDefaultShippingOptions(),
	paymentDataCallbacks: {
		onPaymentAuthorized: onPaymentAuthorized,
		onPaymentDataChanged: onPaymentDataChanged
	},
	environment: "TEST"
};

const customGoogleButtonOptions = {
    googlePayOptions: {
        preClick: () => console.log('preClick executed just before payment sheet loads'),
        buttonColor: 'white',
        buttonType: 'order',
        buttonLocale: 'ja',
        buttonSizeMode: 'fill'
    }
}

//======= JS Implementation =======

import { createTEConnect } from '@magensa/te-connect';
import { TeConnectJs } from '@magensa/te-connect-js';


function demoInit() {
    const teConnectInstance = createTEConnect("__publicKeyGoesHere__", { 
        tecPaymentRequest: { googleMerchantId: "__tecGoogleMerchantId__" } 
    });

    var teConnect = new TeConnectJs(teInstance);

    var tecPrInterface = teConnect.createTecPaymentRequest(googlePaymentRequestObject);

    tecPrInterface.canMakePayments().then( canMakePaymentsResult => {
        if (canMakePaymentsResult)
            tecPrInterface.mountTecPrButton("example-div", customGoogleButtonOptions);
    });

    /*
    Note that because we have registered an `onPaymentAuthorized` callback in the payment request object, the listener below becomes optional for Google Pay:

    tecPrInterface.listenFor('confirm-token', tokenResp => {
        const { tokenDetails, completePayment, error } = tokenResp;

        if (error) {
            //Unsuccessful - check message.
        }
        else {
            //Successful - pass `tokenDetails.token` to MPPG to process payment
        }
    });
    */
}

demoInit();

Google Pay Error Handling

Google offers a few methods to display errors to users via the Google Pay form. Both those options exist within the optional callbacks supplied to the paymentDataCallbacks property of the paymentRequestObject.