diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eea10c9..aff0eced 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # CHANGELOG +## 3.113.0 + +- Local Payment Methods + - Fix hasTokenizationParams to account for token URL param +- Hosted Fields + - add support for `-webkit-text-fill-color` CSS rule + ## 3.112.1 - Venmo diff --git a/package-lock.json b/package-lock.json index 457901fc..7143c6f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "braintree-web", - "version": "3.112.1", + "version": "3.113.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "braintree-web", - "version": "3.112.1", + "version": "3.113.0", "license": "MIT", "dependencies": { "@braintree/asset-loader": "2.0.1", diff --git a/package.json b/package.json index fbffbe61..fc99887e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "braintree-web", - "version": "3.112.1", + "version": "3.113.0", "license": "MIT", "main": "src/index.js", "private": true, diff --git a/src/hosted-fields/shared/constants.js b/src/hosted-fields/shared/constants.js index 31c430af..d7a3069d 100644 --- a/src/hosted-fields/shared/constants.js +++ b/src/hosted-fields/shared/constants.js @@ -55,6 +55,7 @@ var constants = { "-webkit-box-shadow", "-webkit-font-smoothing", "-webkit-tap-highlight-color", + "-webkit-text-fill-color", "-webkit-transition", "appearance", "box-shadow", diff --git a/src/lib/analytics.js b/src/lib/analytics.js index 7483a999..72d6fc7c 100644 --- a/src/lib/analytics.js +++ b/src/lib/analytics.js @@ -2,8 +2,23 @@ var constants = require("./constants"); var metadata = require("./add-metadata"); +var assign = require("./assign").assign; function sendPaypalEvent(clientInstanceOrPromise, eventName, callback) { + return sendPaypalEventPlusFields( + clientInstanceOrPromise, + eventName, + {}, + callback + ); +} + +function sendPaypalEventPlusFields( + clientInstanceOrPromise, + eventName, + extraFields, + callback +) { var timestamp = Date.now(); return Promise.resolve(clientInstanceOrPromise) @@ -35,6 +50,10 @@ function sendPaypalEvent(clientInstanceOrPromise, eventName, callback) { ]; data.tracking = [trackingMeta]; + if (extraFields && typeof extraFields === "object") { + data.tracking = [appendExtraFieldsTo(trackingMeta, extraFields)]; + } + return request( { url: url, @@ -52,6 +71,22 @@ function sendPaypalEvent(clientInstanceOrPromise, eventName, callback) { }); } +function appendExtraFieldsTo(trackingMeta, extraFields) { + var result = {}; + var allowedExtraFields = assign({}, extraFields); + + Object.keys(allowedExtraFields).forEach(function (field) { + if (constants.ALLOWED_EXTRA_EVENT_FIELDS.indexOf(field) === -1) { + delete allowedExtraFields[field]; + } + }); + + result = assign(trackingMeta, allowedExtraFields); + + return result; +} + module.exports = { sendEvent: sendPaypalEvent, + sendEventPlus: sendPaypalEventPlusFields, }; diff --git a/src/lib/constants.js b/src/lib/constants.js index b8b416df..95e0fed0 100644 --- a/src/lib/constants.js +++ b/src/lib/constants.js @@ -48,4 +48,5 @@ module.exports = { SOURCE: "client", PLATFORM: PLATFORM, BRAINTREE_LIBRARY_VERSION: "braintree/" + PLATFORM + "/" + VERSION, + ALLOWED_EXTRA_EVENT_FIELDS: ["paypal_context_id"], }; diff --git a/src/local-payment/external/local-payment.js b/src/local-payment/external/local-payment.js index 7242e3ea..f12483d8 100644 --- a/src/local-payment/external/local-payment.js +++ b/src/local-payment/external/local-payment.js @@ -872,6 +872,10 @@ LocalPayment.prototype._formatTokenizePayload = function (response) { * // ?btLpToken=token&btLpPaymentId=payment-id&btLpPayerId=payer-id * localPaymentInstance.hasTokenizationParams(); // true * + * // if query string has token: (full page redirect flow) + * // ?token=abcdefg123456789 + * localPaymentInstance.hasTokenizationParams(); // true + * * // if query string is missing required params * localPaymentInstance.hasTokenizationParams(); // false * @@ -883,7 +887,7 @@ LocalPayment.prototype._formatTokenizePayload = function (response) { LocalPayment.prototype.hasTokenizationParams = function () { var params = querystring.parse(); - if (params.errorcode) { + if (params.errorcode || params.token) { return true; } diff --git a/src/paypal-checkout/paypal-checkout.js b/src/paypal-checkout/paypal-checkout.js index 00949894..d4cc7e06 100644 --- a/src/paypal-checkout/paypal-checkout.js +++ b/src/paypal-checkout/paypal-checkout.js @@ -575,6 +575,8 @@ PayPalCheckout.prototype._setupFrameService = function (client) { * @returns {(promise|void)} returns a promise if no callback is provided. */ PayPalCheckout.prototype.createPayment = function (options) { + var self = this; + if (!options || !constants.FLOW_ENDPOINTS.hasOwnProperty(options.flow)) { return Promise.reject( new BraintreeError(errors.PAYPAL_FLOW_OPTION_REQUIRED) @@ -593,6 +595,8 @@ PayPalCheckout.prototype.createPayment = function (options) { flowToken = response.agreementSetup.tokenId; } + self._contextId = flowToken; + return flowToken; }); }; @@ -729,9 +733,12 @@ PayPalCheckout.prototype.updatePayment = function (options) { var endpoint = "paypal_hermes/patch_payment_resource"; if (!options || this._hasMissingOption(options, constants.REQUIRED_OPTIONS)) { - analytics.sendEvent( + analytics.sendEventPlus( self._clientPromise, - "paypal-checkout.updatePayment.missing-options" + "paypal-checkout.updatePayment.missing-options", + { + paypal_context_id: self._contextId, // eslint-disable-line camelcase + } ); return Promise.reject( @@ -740,9 +747,12 @@ PayPalCheckout.prototype.updatePayment = function (options) { } if (!this._verifyConsistentCurrency(options)) { - analytics.sendEvent( + analytics.sendEventPlus( self._clientPromise, - "paypal-checkout.updatePayment.inconsistent-currencies" + "paypal-checkout.updatePayment.inconsistent-currencies", + { + paypal_context_id: self._contextId, // eslint-disable-line camelcase + } ); return Promise.reject( @@ -759,7 +769,13 @@ PayPalCheckout.prototype.updatePayment = function (options) { ); } - analytics.sendEvent(this._clientPromise, "paypal-checkout.updatePayment"); + analytics.sendEventPlus( + this._clientPromise, + "paypal-checkout.updatePayment", + { + paypal_context_id: self._contextId, // eslint-disable-line camelcase + } + ); return this._clientPromise .then(function (client) { @@ -773,9 +789,12 @@ PayPalCheckout.prototype.updatePayment = function (options) { var status = err.details && err.details.httpStatus; if (status === 422) { - analytics.sendEvent( + analytics.sendEventPlus( self._clientPromise, - "paypal-checkout.updatePayment.invalid" + "paypal-checkout.updatePayment.invalid", + { + paypal_context_id: self._contextId, // eslint-disable-line camelcase + } ); return Promise.reject( @@ -790,9 +809,12 @@ PayPalCheckout.prototype.updatePayment = function (options) { ); } - analytics.sendEvent( + analytics.sendEventPlus( self._clientPromise, - "paypal-checkout.updatePayment." + errors.PAYPAL_FLOW_FAILED.code + "paypal-checkout.updatePayment." + errors.PAYPAL_FLOW_FAILED.code, + { + paypal_context_id: self._contextId, // eslint-disable-line camelcase + } ); return Promise.reject( @@ -839,9 +861,12 @@ PayPalCheckout.prototype.startVaultInitiatedCheckout = function (options) { var self = this; if (this._vaultInitiatedCheckoutInProgress) { - analytics.sendEvent( + analytics.sendEventPlus( this._clientPromise, - "paypal-checkout.startVaultInitiatedCheckout.error.already-in-progress" + "paypal-checkout.startVaultInitiatedCheckout.error.already-in-progress", + { + paypal_context_id: self._contextId, // eslint-disable-line camelcase + } ); return Promise.reject( @@ -874,9 +899,12 @@ PayPalCheckout.prototype.startVaultInitiatedCheckout = function (options) { flow: "checkout", }); - analytics.sendEvent( + analytics.sendEventPlus( this._clientPromise, - "paypal-checkout.startVaultInitiatedCheckout.started" + "paypal-checkout.startVaultInitiatedCheckout.started", + { + paypal_context_id: self._contextId, // eslint-disable-line camelcase + } ); return this._waitForVaultInitiatedCheckoutDependencies() @@ -907,9 +935,12 @@ PayPalCheckout.prototype.startVaultInitiatedCheckout = function (options) { self._removeModalBackdrop(); if (err.code === "FRAME_SERVICE_FRAME_CLOSED") { - analytics.sendEvent( + analytics.sendEventPlus( self._clientPromise, - "paypal-checkout.startVaultInitiatedCheckout.canceled.by-customer" + "paypal-checkout.startVaultInitiatedCheckout.canceled.by-customer", + { + paypal_context_id: self._contextId, // eslint-disable-line camelcase + } ); return Promise.reject( @@ -927,9 +958,12 @@ PayPalCheckout.prototype.startVaultInitiatedCheckout = function (options) { err.code && err.code.indexOf("FRAME_SERVICE_FRAME_OPEN_FAILED") > -1 ) { - analytics.sendEvent( + analytics.sendEventPlus( self._clientPromise, - "paypal-checkout.startVaultInitiatedCheckout.failed.popup-not-opened" + "paypal-checkout.startVaultInitiatedCheckout.failed.popup-not-opened", + { + paypal_context_id: self._contextId, // eslint-disable-line camelcase + } ); return Promise.reject( @@ -954,9 +988,12 @@ PayPalCheckout.prototype.startVaultInitiatedCheckout = function (options) { self._frameService.close(); self._vaultInitiatedCheckoutInProgress = false; self._removeModalBackdrop(); - analytics.sendEvent( + analytics.sendEventPlus( self._clientPromise, - "paypal-checkout.startVaultInitiatedCheckout.succeeded" + "paypal-checkout.startVaultInitiatedCheckout.succeeded", + { + paypal_context_id: self._contextId, // eslint-disable-line camelcase + } ); return Promise.resolve(response); @@ -1012,9 +1049,12 @@ PayPalCheckout.prototype._removeModalBackdrop = function () { */ PayPalCheckout.prototype.closeVaultInitiatedCheckoutWindow = function () { if (this._vaultInitiatedCheckoutInProgress) { - analytics.sendEvent( + analytics.sendEventPlus( this._clientPromise, - "paypal-checkout.startVaultInitiatedCheckout.canceled.by-merchant" + "paypal-checkout.startVaultInitiatedCheckout.canceled.by-merchant", + { + paypal_context_id: this._contextId, // eslint-disable-line camelcase + } ); } @@ -1147,9 +1187,12 @@ PayPalCheckout.prototype.tokenizePayment = function (tokenizeOptions) { options.vault = shouldVault; - analytics.sendEvent( + analytics.sendEventPlus( this._clientPromise, - "paypal-checkout.tokenization.started" + "paypal-checkout.tokenization.started", + { + paypal_context_id: self._contextId, // eslint-disable-line camelcase + } ); return this._clientPromise @@ -1163,14 +1206,20 @@ PayPalCheckout.prototype.tokenizePayment = function (tokenizeOptions) { .then(function (response) { payload = self._formatTokenizePayload(response); - analytics.sendEvent( + analytics.sendEventPlus( self._clientPromise, - "paypal-checkout.tokenization.success" + "paypal-checkout.tokenization.success", + { + paypal_context_id: self._contextId, // eslint-disable-line camelcase + } ); if (payload.creditFinancingOffered) { - analytics.sendEvent( + analytics.sendEventPlus( self._clientPromise, - "paypal-checkout.credit.accepted" + "paypal-checkout.credit.accepted", + { + paypal_context_id: self._contextId, // eslint-disable-line camelcase + } ); } @@ -1181,9 +1230,12 @@ PayPalCheckout.prototype.tokenizePayment = function (tokenizeOptions) { return Promise.reject(self._setupError); } - analytics.sendEvent( + analytics.sendEventPlus( self._clientPromise, - "paypal-checkout.tokenization.failed" + "paypal-checkout.tokenization.failed", + { + paypal_context_id: self._contextId, // eslint-disable-line camelcase + } ); return Promise.reject( @@ -1592,9 +1644,12 @@ PayPalCheckout.prototype._formatUpdatePaymentData = function (options) { /* shippingAddress not supported yet */ if (options.hasOwnProperty("shippingAddress")) { - analytics.sendEvent( + analytics.sendEventPlus( self._clientPromise, - "paypal-checkout.updatePayment.shippingAddress.provided.by-the-merchant" + "paypal-checkout.updatePayment.shippingAddress.provided.by-the-merchant", + { + paypal_context_id: self._contextId, // eslint-disable-line camelcase + } ); paymentResource.line1 = options.shippingAddress.line1; diff --git a/src/three-d-secure/external/three-d-secure.js b/src/three-d-secure/external/three-d-secure.js index 56741fa9..b28c0614 100644 --- a/src/three-d-secure/external/three-d-secure.js +++ b/src/three-d-secure/external/three-d-secure.js @@ -66,7 +66,7 @@ var FRAMEWORKS = require("./frameworks"); * @property {string} threeDSecureInfo.cavv Cardholder authentication verification value or CAVV. The main encrypted message issuers and card networks use to verify authentication has occurred. Mastercard uses an AVV message and American Express uses an AEVV message, each of which should also be passed in the cavv parameter. * @property {string} threeDSecureInfo.dsTransactionId Transaction identifier resulting from 3D Secure 2 authentication. * @property {string} threeDSecureInfo.eciFlag The value of the electronic commerce indicator (ECI) flag, which indicates the outcome of the 3DS authentication. This will be a two-digit value. - * @property {boolean} threeDSecureInfo.enrolled Indicates the status of 3D Secure authentication eligibility with the card issuer. + * @property {string} threeDSecureInfo.enrolled Indicates the status of 3D Secure authentication eligibility with the card issuer. * @property {boolean} threeDSecureInfo.liabilityShifted Indicates whether the liability for fraud has been shifted away from the merchant. * @property {boolean} threeDSecureInfo.liabilityShiftPossible Indicates whether liability shift is still possible on a retry. * @property {string} threeDSecureInfo.paresStatus Transaction status result identifier. diff --git a/src/venmo/venmo.js b/src/venmo/venmo.js index 0c5c0d65..d2fc9079 100644 --- a/src/venmo/venmo.js +++ b/src/venmo/venmo.js @@ -723,7 +723,9 @@ Venmo.prototype.cancelTokenization = function () { Venmo.prototype._tokenizeWebLoginWithRedirect = function () { var self = this; - analytics.sendEvent(self._createPromise, "venmo.tokenize.web-login.start"); + analytics.sendEvent(self._createPromise, "venmo.tokenize.web-login.start", { + paypal_context_id: self._venmoPaymentContextId, + }); this._tokenizePromise = new ExtendedPromise(); return this.getUrl().then(function (url) { @@ -740,7 +742,10 @@ Venmo.prototype._tokenizeWebLoginWithRedirect = function () { .then(function (payload) { analytics.sendEvent( self._createPromise, - "venmo.tokenize.web-login.success" + "venmo.tokenize.web-login.success", + { + paypal_context_id: self._venmoPaymentContextId, + } ); self._tokenizePromise.resolve({ @@ -753,7 +758,10 @@ Venmo.prototype._tokenizeWebLoginWithRedirect = function () { .catch(function (err) { analytics.sendEvent( self._createPromise, - "venmo.tokenize.web-login.failure" + "venmo.tokenize.web-login.failure", + { + paypal_context_id: self._venmoPaymentContextId, + } ); self._tokenizePromise.reject(err); @@ -807,7 +815,10 @@ Venmo.prototype._checkPaymentContextStatusAndProcessResult = function ( analytics.sendEvent( self._createPromise, - "venmo.tokenize.web-login.status-change" + "venmo.tokenize.web-login.status-change", + { + paypal_context_id: self._venmoPaymentContextId, + } ); switch (resultStatus) { @@ -892,7 +903,10 @@ Venmo.prototype._pollForStatusChange = function () { analytics.sendEvent( self._createPromise, "venmo.tokenize.manual-return.status-change." + - newStatus.toLowerCase() + newStatus.toLowerCase(), + { + paypal_context_id: self._venmoPaymentContextId, + } ); switch (newStatus) { @@ -927,7 +941,10 @@ Venmo.prototype._tokenizeForMobileWithManualReturn = function () { analytics.sendEvent( this._createPromise, - "venmo.tokenize.manual-return.start" + "venmo.tokenize.manual-return.start", + { + paypal_context_id: self._venmoPaymentContextId, + } ); this._mobilePollingContextExpiresIn = @@ -938,7 +955,10 @@ Venmo.prototype._tokenizeForMobileWithManualReturn = function () { .then(function (payload) { analytics.sendEvent( self._createPromise, - "venmo.tokenize.manual-return.success" + "venmo.tokenize.manual-return.success", + { + paypal_context_id: self._venmoPaymentContextId, + } ); self._tokenizePromise.resolve({ @@ -951,7 +971,10 @@ Venmo.prototype._tokenizeForMobileWithManualReturn = function () { .catch(function (err) { analytics.sendEvent( self._createPromise, - "venmo.tokenize.manual-return.failure" + "venmo.tokenize.manual-return.failure", + { + paypal_context_id: self._venmoPaymentContextId, + } ); self._tokenizePromise.reject(err); @@ -1223,7 +1246,10 @@ Venmo.prototype.processHashChangeFlowResults = function (hash) { .then(function (payload) { analytics.sendEvent( self._createPromise, - "venmo.appswitch.handle.payment-context-status-query.success" + "venmo.appswitch.handle.payment-context-status-query.success", + { + paypal_context_id: self._venmoPaymentContextId, + } ); return resolve({ @@ -1243,7 +1269,10 @@ Venmo.prototype.processHashChangeFlowResults = function (hash) { analytics.sendEvent( self._createPromise, - "venmo.process-results.payment-context-status-query-failed" + "venmo.process-results.payment-context-status-query-failed", + { + paypal_context_id: self._venmoPaymentContextId, + } ); // If the polling request fails, but not because of cancelization, we will rely on the params provided from the hash resolve(params); diff --git a/test/lib/unit/analytics.js b/test/lib/unit/analytics.js index 0f351272..6f212ea0 100644 --- a/test/lib/unit/analytics.js +++ b/test/lib/unit/analytics.js @@ -150,5 +150,61 @@ describe("analytics", () => { done(); }); + + describe("add additional data to events", () => { + it("adds specified fields to event metadata", (done) => { + const client = testContext.client; + const expectedContextId = "venmo-1234-id"; + const expectedEventName = "api.module.thing.happened"; + var actualEventSent, actualTracking; + + analytics.sendEventPlus( + client, + expectedEventName, + { + paypal_context_id: expectedContextId, + }, + () => { + actualEventSent = + /* eslint-disable camelcase */ + client._request.mock.calls[0][0].data; + expect(actualEventSent).not.toBeNull(); + actualTracking = actualEventSent.tracking[0]; + expect(actualTracking).not.toBeNull(); + expect(actualTracking.paypal_context_id).toBe(expectedContextId); + } + ); + + done(); + }); + + it("does not clobber preset metadata fields", (done) => { + const altComponent = "overwritten"; + const client = testContext.client; + const expectedContextId = "venmo-1234-id"; + const expectedEventName = "api.module.thing.happened"; + var actualEventSent, actualTracking; + + analytics.sendEventPlus( + client, + expectedEventName, + { + paypal_context_id: expectedContextId, + component: altComponent, + }, + () => { + actualEventSent = + /* eslint-disable camelcase */ + client._request.mock.calls[0][0].data; + expect(actualEventSent).not.toBeNull(); + actualTracking = actualEventSent.tracking[0]; + expect(actualTracking).not.toBeNull(); + expect(actualTracking.component).toBe("braintreeclientsdk"); + } + ); + + done(); + }); + }); }); }); diff --git a/test/local-payment/unit/external/local-payment.js b/test/local-payment/unit/external/local-payment.js index 44f3aeee..536b106d 100644 --- a/test/local-payment/unit/external/local-payment.js +++ b/test/local-payment/unit/external/local-payment.js @@ -1805,6 +1805,14 @@ describe("LocalPayment", () => { expect(testContext.localPayment.hasTokenizationParams()).toBe(true); }); + it("returns true if token is a url param (full page redirect flow)", () => { + querystring.parse.mockReturnValue({ + token: "example-token", + }); + + expect(testContext.localPayment.hasTokenizationParams()).toBe(true); + }); + it("returns false if no query params", () => { querystring.parse.mockReturnValue({}); diff --git a/test/paypal-checkout/unit/paypal-checkout.js b/test/paypal-checkout/unit/paypal-checkout.js index 8e5d6f9f..60c9ff3c 100644 --- a/test/paypal-checkout/unit/paypal-checkout.js +++ b/test/paypal-checkout/unit/paypal-checkout.js @@ -1115,17 +1115,22 @@ describe("PayPalCheckout", () => { currency: "USD", paymentId: "pay-token-123-abc", }; + analytics.sendEventPlus = jest.fn(); + testContext.paypalCheckout._contextId = "pay-token-123-abc"; }); - it("sends analytics event when udpatePayment is called", () => { - analytics.sendEvent.mockClear(); + it("sends analytics event when udpatePayment is called", async () => { + analytics.sendEventPlus.mockClear(); - testContext.paypalCheckout.updatePayment(testContext.options); + await testContext.paypalCheckout.updatePayment(testContext.options); - expect(analytics.sendEvent).toHaveBeenCalledTimes(1); - expect(analytics.sendEvent).toHaveBeenCalledWith( + expect(analytics.sendEventPlus).toHaveBeenCalledTimes(1); + expect(analytics.sendEventPlus).toHaveBeenCalledWith( testContext.paypalCheckout._clientPromise, - "paypal-checkout.updatePayment" + "paypal-checkout.updatePayment", + { + paypal_context_id: testContext.options.paymentId, // eslint-disable-line camelcase + } ); }); @@ -1137,7 +1142,7 @@ describe("PayPalCheckout", () => { details: { httpStatus: 422 }, }); - analytics.sendEvent.mockClear(); + analytics.sendEventPlus.mockClear(); testContext.client.request.mockRejectedValue(unprocessableError); @@ -1150,16 +1155,19 @@ describe("PayPalCheckout", () => { expect(details).toEqual({ originalError: unprocessableError, }); - expect(analytics.sendEvent).toHaveBeenCalledTimes(2); - expect(analytics.sendEvent).toHaveBeenCalledWith( + expect(analytics.sendEventPlus).toHaveBeenCalledTimes(2); + expect(analytics.sendEventPlus).toHaveBeenCalledWith( testContext.paypalCheckout._clientPromise, - "paypal-checkout.updatePayment.invalid" + "paypal-checkout.updatePayment.invalid", + { + paypal_context_id: testContext.paypalCheckout._contextId, // eslint-disable-line camelcase + } ); }); }); it("handles network error (HTTP status 404)", () => { - analytics.sendEvent.mockClear(); + analytics.sendEventPlus.mockClear(); const originalError = new Error("Something bad happened"); @@ -1554,7 +1562,7 @@ describe("PayPalCheckout", () => { }); it("sends analytics event when includes shipping address", () => { - analytics.sendEvent.mockClear(); + analytics.sendEventPlus.mockClear(); testContext.options.amount = 1; testContext.options.shippingAddress = { @@ -1574,10 +1582,13 @@ describe("PayPalCheckout", () => { testContext.options ); - expect(analytics.sendEvent).toHaveBeenCalledTimes(1); - expect(analytics.sendEvent).toHaveBeenCalledWith( + expect(analytics.sendEventPlus).toHaveBeenCalledTimes(1); + expect(analytics.sendEventPlus).toHaveBeenCalledWith( testContext.paypalCheckout._clientPromise, - "paypal-checkout.updatePayment.shippingAddress.provided.by-the-merchant" + "paypal-checkout.updatePayment.shippingAddress.provided.by-the-merchant", + { + paypal_context_id: testContext.paypalCheckout._contextId, // eslint-disable-line camelcase + } ); }); @@ -1663,6 +1674,7 @@ describe("PayPalCheckout", () => { redirectUrl: "https://example.com/redirect", }, }); + testContext.paypalCheckout._contextId = "context-id"; jest .spyOn(testContext.paypalCheckout, "tokenizePayment") @@ -1670,6 +1682,8 @@ describe("PayPalCheckout", () => { nonce: "new-fake-nonce", type: "PayPalAccount", }); + + analytics.sendEventPlus = jest.fn(); }); it("rejects if auth is already in progress", async () => { @@ -1687,9 +1701,12 @@ describe("PayPalCheckout", () => { message: "Vault initiated checkout already in progress.", }); - expect(analytics.sendEvent).toBeCalledWith( + expect(analytics.sendEventPlus).toBeCalledWith( expect.anything(), - "paypal-checkout.startVaultInitiatedCheckout.error.already-in-progress" + "paypal-checkout.startVaultInitiatedCheckout.error.already-in-progress", + { + paypal_context_id: testContext.paypalCheckout._contextId, // eslint-disable-line camelcase + } ); await firstAttempt; @@ -1824,18 +1841,24 @@ describe("PayPalCheckout", () => { expect(data.type).toBe("PayPalAccount"); }); - it("sends analtyic events for success", async () => { + it("sends analytic events for success", async () => { await testContext.paypalCheckout.startVaultInitiatedCheckout( testContext.options ); - expect(analytics.sendEvent).toBeCalledWith( + expect(analytics.sendEventPlus).toBeCalledWith( expect.anything(), - "paypal-checkout.startVaultInitiatedCheckout.succeeded" + "paypal-checkout.startVaultInitiatedCheckout.succeeded", + { + paypal_context_id: testContext.paypalCheckout._contextId, // eslint-disable-line camelcase + } ); - expect(analytics.sendEvent).toBeCalledWith( + expect(analytics.sendEventPlus).toBeCalledWith( expect.anything(), - "paypal-checkout.startVaultInitiatedCheckout.started" + "paypal-checkout.startVaultInitiatedCheckout.started", + { + paypal_context_id: testContext.paypalCheckout._contextId, // eslint-disable-line camelcase + } ); }); @@ -1986,9 +2009,12 @@ describe("PayPalCheckout", () => { message: "Customer closed PayPal popup before authorizing.", }); - expect(analytics.sendEvent).toBeCalledWith( + expect(analytics.sendEventPlus).toBeCalledWith( expect.anything(), - "paypal-checkout.startVaultInitiatedCheckout.canceled.by-customer" + "paypal-checkout.startVaultInitiatedCheckout.canceled.by-customer", + { + paypal_context_id: testContext.paypalCheckout._contextId, // eslint-disable-line camelcase + } ); expect(testContext.fakeFrameService.close).toBeCalledTimes(0); }); @@ -2013,9 +2039,12 @@ describe("PayPalCheckout", () => { }, }); - expect(analytics.sendEvent).toBeCalledWith( + expect(analytics.sendEventPlus).toBeCalledWith( expect.anything(), - "paypal-checkout.startVaultInitiatedCheckout.failed.popup-not-opened" + "paypal-checkout.startVaultInitiatedCheckout.failed.popup-not-opened", + { + paypal_context_id: testContext.paypalCheckout._contextId, // eslint-disable-line camelcase + } ); expect(testContext.fakeFrameService.close).toBeCalledTimes(1); }); @@ -2056,6 +2085,12 @@ describe("PayPalCheckout", () => { }); describe("closeVaultInitiatedCheckoutWindow", () => { + beforeEach(() => { + testContext.paypalCheckout._contextId = "context-id"; + + analytics.sendEventPlus = jest.fn(); + }); + it("calls close on the frame service", async () => { await testContext.paypalCheckout.closeVaultInitiatedCheckoutWindow(); @@ -2065,9 +2100,12 @@ describe("PayPalCheckout", () => { it("sends an analytics event when vault initiated checkout is in progress", async () => { await testContext.paypalCheckout.closeVaultInitiatedCheckoutWindow(); - expect(analytics.sendEvent).not.toBeCalledWith( + expect(analytics.sendEventPlus).not.toBeCalledWith( expect.anything(), - "paypal-checkout.startVaultInitiatedCheckout.canceled.by-merchant" + "paypal-checkout.startVaultInitiatedCheckout.canceled.by-merchant", + { + paypal_context_id: testContext.paypalCheckout._contextId, // eslint-disable-line camelcase + } ); const promise = testContext.paypalCheckout.startVaultInitiatedCheckout({ @@ -2078,9 +2116,12 @@ describe("PayPalCheckout", () => { await testContext.paypalCheckout.closeVaultInitiatedCheckoutWindow(); - expect(analytics.sendEvent).toBeCalledWith( + expect(analytics.sendEventPlus).toBeCalledWith( expect.anything(), - "paypal-checkout.startVaultInitiatedCheckout.canceled.by-merchant" + "paypal-checkout.startVaultInitiatedCheckout.canceled.by-merchant", + { + paypal_context_id: testContext.paypalCheckout._contextId, // eslint-disable-line camelcase + } ); await promise; @@ -2088,6 +2129,12 @@ describe("PayPalCheckout", () => { }); describe("tokenizePayment", () => { + beforeEach(() => { + testContext.paypalCheckout._contextId = "context-id"; + + analytics.sendEventPlus = jest.fn(); + }); + it("rejects with a BraintreeError if a non-Braintree error comes back from the client", () => { const error = new Error("Error"); @@ -2338,9 +2385,12 @@ describe("PayPalCheckout", () => { testContext.paypalCheckout .tokenizePayment({ billingToken: "token" }) .then(() => { - expect(analytics.sendEvent).toHaveBeenCalledWith( + expect(analytics.sendEventPlus).toHaveBeenCalledWith( testContext.paypalCheckout._clientPromise, - "paypal-checkout.tokenization.started" + "paypal-checkout.tokenization.started", + { + paypal_context_id: testContext.paypalCheckout._contextId, // eslint-disable-line camelcase + } ); })); @@ -2361,9 +2411,12 @@ describe("PayPalCheckout", () => { client.request.mockResolvedValue({}); return testContext.paypalCheckout.tokenizePayment({}).then(() => { - expect(analytics.sendEvent).toHaveBeenCalledWith( + expect(analytics.sendEventPlus).toHaveBeenCalledWith( testContext.paypalCheckout._clientPromise, - "paypal-checkout.tokenization.success" + "paypal-checkout.tokenization.success", + { + paypal_context_id: testContext.paypalCheckout._contextId, // eslint-disable-line camelcase + } ); }); }); @@ -2384,9 +2437,12 @@ describe("PayPalCheckout", () => { }); return testContext.paypalCheckout.tokenizePayment({}).then(() => { - expect(analytics.sendEvent).toHaveBeenCalledWith( + expect(analytics.sendEventPlus).toHaveBeenCalledWith( testContext.paypalCheckout._clientPromise, - "paypal-checkout.credit.accepted" + "paypal-checkout.credit.accepted", + { + paypal_context_id: testContext.paypalCheckout._contextId, // eslint-disable-line camelcase + } ); }); }); @@ -2724,9 +2780,12 @@ describe("PayPalCheckout", () => { client.request.mockRejectedValue(new Error("Error")); return testContext.paypalCheckout.tokenizePayment({}).catch(() => { - expect(analytics.sendEvent).toHaveBeenCalledWith( + expect(analytics.sendEventPlus).toHaveBeenCalledWith( testContext.paypalCheckout._clientPromise, - "paypal-checkout.tokenization.failed" + "paypal-checkout.tokenization.failed", + { + paypal_context_id: testContext.paypalCheckout._contextId, // eslint-disable-line camelcase + } ); }); }); diff --git a/test/venmo/unit/venmo.js b/test/venmo/unit/venmo.js index 30544db0..18828328 100644 --- a/test/venmo/unit/venmo.js +++ b/test/venmo/unit/venmo.js @@ -2598,11 +2598,15 @@ describe("Venmo", () => { expect(analytics.sendEvent).toBeCalledWith( expect.anything(), - "venmo.tokenize.manual-return.start" + "venmo.tokenize.manual-return.start", + { + paypal_context_id: "context-id", // eslint-disable-line camelcase + } ); expect(analytics.sendEvent).toBeCalledWith( expect.anything(), - "venmo.tokenize.manual-return.success" + "venmo.tokenize.manual-return.success", + { paypal_context_id: "context-id" } // eslint-disable-line camelcase ); expect(analytics.sendEvent).toBeCalledWith( expect.anything(), @@ -2735,7 +2739,10 @@ describe("Venmo", () => { ); expect(analytics.sendEvent).toBeCalledWith( expect.anything(), - "venmo.tokenize.manual-return.failure" + "venmo.tokenize.manual-return.failure", + { + paypal_context_id: "context-id", // eslint-disable-line camelcase + } ); expect(err.code).toBe( @@ -2764,7 +2771,10 @@ describe("Venmo", () => { ); expect(analytics.sendEvent).toBeCalledWith( expect.anything(), - `venmo.tokenize.manual-return.status-change.${status.toLowerCase()}` + `venmo.tokenize.manual-return.status-change.${status.toLowerCase()}`, + { + paypal_context_id: "context-id", // eslint-disable-line camelcase + } ); }); } @@ -2799,15 +2809,23 @@ describe("Venmo", () => { expect(analytics.sendEvent).toBeCalledWith( expect.anything(), - "venmo.tokenize.manual-return.status-change.scanned" + "venmo.tokenize.manual-return.status-change.scanned", + { paypal_context_id: "context-id" } // eslint-disable-line camelcase + ); + expect(analytics.sendEvent).toBeCalledWith( + expect.anything(), + "venmo.tokenize.manual-return.status-change.unknown_status_we_do_not_account_for", + { paypal_context_id: "context-id" } // eslint-disable-line camelcase ); expect(analytics.sendEvent).toBeCalledWith( expect.anything(), - "venmo.tokenize.manual-return.status-change.unknown_status_we_do_not_account_for" + "venmo.tokenize.manual-return.status-change.approved", + { paypal_context_id: "context-id" } // eslint-disable-line camelcase ); expect(analytics.sendEvent).toBeCalledWith( expect.anything(), - "venmo.tokenize.manual-return.status-change.approved" + "venmo.tokenize.manual-return.success", + { paypal_context_id: "context-id" } // eslint-disable-line camelcase ); // once to create the payment context @@ -3347,12 +3365,14 @@ describe("Venmo", () => { expect(analytics.sendEvent).toHaveBeenNthCalledWith( 3, expect.anything(), - expectedStartEvent + expectedStartEvent, + { paypal_context_id: "some-context-id" } // eslint-disable-line camelcase ); expect(analytics.sendEvent).toHaveBeenNthCalledWith( 4, expect.anything(), - expectedApprovedEvent + expectedApprovedEvent, + { paypal_context_id: "some-context-id" } // eslint-disable-line camelcase ); }); @@ -3366,7 +3386,8 @@ describe("Venmo", () => { expect(analytics.sendEvent).toHaveBeenNthCalledWith( 4, expect.anything(), - expectedApprovedEvent + expectedApprovedEvent, + { paypal_context_id: "some-context-id" } // eslint-disable-line camelcase ); }); }); @@ -3380,7 +3401,8 @@ describe("Venmo", () => { expect(analytics.sendEvent).toHaveBeenNthCalledWith( 3, expect.anything(), - expectedApprovedEvent + expectedApprovedEvent, + { paypal_context_id: "some-context-id" } // eslint-disable-line camelcase ); }); });