Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(connect): add initial support for connect component #2280

Merged
merged 33 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e980158
demo axo component
siddy2181 Nov 3, 2023
358c9d4
rename to connect, move code directly for now
cgdibble Nov 6, 2023
b6fa60d
add connect interface unit test file
cgdibble Nov 6, 2023
ccd3a09
remove axo zoid, unit tests
siddy2181 Nov 6, 2023
055b030
add route for connect module
siddy2181 Nov 7, 2023
ccb4b5f
refactor: move axo/connect under src
siddy2181 Nov 7, 2023
699338f
update loader config
siddy2181 Nov 8, 2023
d75daed
proposed payload for PPCP AXO loader
siddy2181 Nov 8, 2023
30bb926
removing sessionId default because we want to require that prop
cgdibble Nov 8, 2023
b43bfac
couple test adjsutments
cgdibble Nov 9, 2023
97a6a06
add tests, cleanup
siddy2181 Nov 9, 2023
743e2ce
update parameters supporting AXO modifications
siddy2181 Nov 14, 2023
0fa02c0
update axoloader version; fix lint,test,typecheck
siddy2181 Nov 14, 2023
c764222
Merge branch 'main' of https://github.com/paypal/paypal-checkout-comp…
siddy2181 Nov 14, 2023
4990156
pass metadata to loadAxo
siddy2181 Nov 20, 2023
93f4dc9
Merge branch 'main' of https://github.com/paypal/paypal-checkout-comp…
siddy2181 Nov 20, 2023
15968cf
update package version for npm publish
siddy2181 Nov 20, 2023
a61744a
update type for Checkout Component
siddy2181 Nov 27, 2023
3b4711b
add some basic analytics
cgdibble Nov 27, 2023
17698f3
test second trycatch, loose metric tests, remove run for vitest
cgdibble Nov 27, 2023
1023547
fix flow and such
cgdibble Nov 28, 2023
708c390
remove debug
cgdibble Nov 28, 2023
4ad56c0
Update package.json
cgdibble Nov 28, 2023
77afd1a
prettiered
cgdibble Nov 28, 2023
17e9bff
add vitest command to not disturb ci, test failing test
cgdibble Nov 28, 2023
e11089f
move temp sendCountMetric to new file to mock and better test, add er…
cgdibble Nov 28, 2023
169985e
add sendcountrmetric todo
cgdibble Nov 28, 2023
90e2c88
restore test opts
cgdibble Nov 29, 2023
1a570bd
restore dist button files
cgdibble Nov 29, 2023
77514e6
restore dist button files
cgdibble Nov 29, 2023
92addb5
restore dist button files
cgdibble Nov 29, 2023
a1fcb95
lint fixes
cgdibble Nov 29, 2023
43d79d3
minified true for axo
cgdibble Nov 29, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions __sdk__.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ module.exports = {
entry: "./src/interface/wallet",
globals,
},
connect: {
entry: "./src/connect/interface",
globals,
},
// @deprecated - renamed to payment-fields to be removed
fields: {
entry: "./src/interface/fields",
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"percy-screenshot": "npx playwright install && babel-node ./test/percy/server/createButtonConfigs.js && percy exec -- playwright test --config=./test/percy/playwright.config.js --reporter=dot --pass-with-no-tests",
"typecheck": "npm run flow-typed && npm run flow",
"version": "./scripts/version.sh",
"vitest": "vitest",
"webpack": "babel-node $(npm bin)/webpack",
"webpack-size": "npm run webpack -- --config webpack.config.size",
"prepare": "husky install",
Expand Down Expand Up @@ -109,6 +110,7 @@
"@krakenjs/zoid": "^10.3.1",
"@paypal/common-components": "^1.0.35",
"@paypal/funding-components": "^1.0.31",
"@paypal/connect-loader-component": "^1.1.0",
"@paypal/sdk-client": "^4.0.176",
"@paypal/sdk-constants": "^1.0.133",
"@paypal/sdk-logos": "^2.2.6"
Expand Down
79 changes: 79 additions & 0 deletions src/connect/component.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/* @flow */
import { loadAxo } from "@paypal/connect-loader-component";
import { stringifyError } from "@krakenjs/belter/src";
import {
getClientID,
getClientMetadataID,
getUserIDToken,
getLogger,
} from "@paypal/sdk-client/src";

import { sendCountMetric } from "./sendCountMetric";

// $FlowFixMe
export const getConnectComponent = async (merchantProps) => {
sendCountMetric({
name: "pp.app.paypal_sdk.connect.init.count",
dimensions: {},
});

const cmid = getClientMetadataID();
const clientID = getClientID();
Comment on lines +20 to +21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these be camel case? I don't know anymore when it comes to "id".

Suggested change
const cmid = getClientMetadataID();
const clientID = getClientID();
const cmId = getClientMetadataID();
const clientId = getClientID();

const userIdToken = getUserIDToken();
const { metadata } = merchantProps;

let loadResult = {};
try {
loadResult = await loadAxo({
platform: "PPCP",
btSdkVersion: "3.97.3-connect-alpha.6.1",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far this has to stay hardcoded. More to come, though.

minified: true,
metadata,
});
} catch (error) {
sendCountMetric({
name: "pp.app.paypal_sdk.connect.init.error.count",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be pp.app.paypal_sdk.connect.load.error.count ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are keeping all errors under the same event name. Which then will be funneled based on loadvs create based on the event dimension param

event: "error",
dimensions: {
errorName: "connect_load_error",
},
});

getLogger().error("load_axo_error", { err: stringifyError(error) });

throw new Error(error);
}

try {
const connect = await window.braintree.connect.create({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember one issue where I was unable to rely on window.braintree because the npm module (if npm-installed) did not attach itself to the window object - just mentioning it in case the integration pattern should support npm install braintree-web

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're ok here because the AXO module we load above is what will append that window object.

...loadResult.metadata, // returns a localeURL for assets
...merchantProps, // AXO specific props
platformOptions: {
platform: "PPCP",
userIdToken,
clientID,
clientMetadataID: cmid,
},
});

sendCountMetric({
name: "pp.app.paypal_sdk.connect.init.success.count",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you follow a guideline for this name? (I'm about to do the same with another component but want to standardize with what you've done here!)

Copy link
Contributor Author

@cgdibble cgdibble Nov 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jshawl we referenced the card-fields stuff we'd done before but otherwise no we didn't know of a guide. Does one exist?

event: "success",
dimensions: {},
});

return connect;
} catch (error) {
sendCountMetric({
name: "pp.app.paypal_sdk.connect.init.error.count",
event: "error",
dimensions: {
errorName: "connect_init_error",
},
});

getLogger().error("init_axo_error", { err: stringifyError(error) });

throw new Error(error);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should also log these errors log.error() so that we can investigate them in the future. I don't think we need logs for anything else, the metrics will be enough for the init and success cases. We'd want to see the errors themselves in cal though if theres an issue

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wsbrunson done!

}
};
89 changes: 89 additions & 0 deletions src/connect/component.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/* @flow */

import {
getClientID,
getClientMetadataID,
getUserIDToken,
} from "@paypal/sdk-client/src";
import { loadAxo } from "@paypal/connect-loader-component";
import { describe, expect, test, vi } from "vitest";

import { getConnectComponent } from "./component";
import { sendCountMetric } from "./sendCountMetric";

vi.mock("@paypal/sdk-client/src", () => {
return {
getClientID: vi.fn(() => "mock-client-id"),
getClientMetadataID: vi.fn(() => "mock-cmid"),
getUserIDToken: vi.fn(() => "mock-uid"),
getLogger: vi.fn(() => ({ metric: vi.fn(), error: vi.fn() })),
};
});

vi.mock("@paypal/connect-loader-component", () => {
return {
loadAxo: vi.fn(),
};
});

vi.mock("./sendCountMetric", () => {
return {
sendCountMetric: vi.fn(),
};
});

describe("getConnectComponent: returns ConnectComponent", () => {
const mockAxoMetadata = { someData: "data" };
const mockProps = { someProp: "value" };
beforeEach(() => {
vi.clearAllMocks();
window.braintree = {
connect: {
create: vi.fn(),
},
};

loadAxo.mockResolvedValue({ metadata: mockAxoMetadata });
});

test("loadAxo and window.braintree.connect.create are called with proper data", async () => {
await getConnectComponent(mockProps);

expect(getClientID).toHaveBeenCalled();
expect(getClientMetadataID).toHaveBeenCalled();
expect(getUserIDToken).toHaveBeenCalled();
expect(loadAxo).toHaveBeenCalled();

expect(window.braintree.connect.create).toHaveBeenCalledWith({
...mockAxoMetadata,
...mockProps,
platformOptions: {
platform: "PPCP",
clientID: "mock-client-id",
clientMetadataID: "mock-cmid",
userIdToken: "mock-uid",
},
});
expect(sendCountMetric).toBeCalledTimes(2);
});

test("loadAxo failure is handled", async () => {
const errorMessage = "Something went wrong";
loadAxo.mockRejectedValue(errorMessage);

await expect(() => getConnectComponent(mockProps)).rejects.toThrow(
errorMessage
);
expect(sendCountMetric).toHaveBeenCalledTimes(2);
});

test("connect create failure is handled", async () => {
const expectedError = "create failed";
window.braintree.connect.create.mockRejectedValue(expectedError);

await expect(() => getConnectComponent(mockProps)).rejects.toThrow(
expectedError
);
expect(sendCountMetric).toBeCalledTimes(2);
});
});
16 changes: 16 additions & 0 deletions src/connect/interface.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* eslint-disable flowtype/no-weak-types */
/* @flow */
// flow-disable

import { getConnectComponent } from "./component";

type ConnectComponent = (merchantProps: any) => ConnectComponent;
// $FlowFixMe
export const Connect: ConnectComponent = async (
merchantProps: any
): ConnectComponent => {
// $FlowFixMe
return await getConnectComponent(merchantProps);
};

/* eslint-enable flowtype/no-weak-types */
19 changes: 19 additions & 0 deletions src/connect/interface.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* @flow */

import { describe, expect, vi } from "vitest";

import { getConnectComponent } from "./component";
import { Connect } from "./interface";

describe("interface.js", () => {
vi.mock("./component", () => {
return {
getConnectComponent: vi.fn(),
};
});
it("should call getConnectComponent with merchant props", async () => {
const merchantProps = { props: "someProps" };
await Connect(merchantProps);
expect(getConnectComponent).toBeCalledWith(merchantProps);
});
});
25 changes: 25 additions & 0 deletions src/connect/sendCountMetric.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* @flow */
import { getLogger } from "@paypal/sdk-client/src";

// TODO: This will be pulled in to a shared sdk-client util
export const sendCountMetric = ({
dimensions,
event = "unused",
name,
value = 1,
}: {|
event?: string,
name: string,
value?: number,
dimensions: {
[string]: mixed,
},
// $FlowIssue return type
|}) =>
getLogger().metric({
dimensions,
metricEventName: event,
metricNamespace: name,
metricValue: value,
metricType: "counter",
});
3 changes: 3 additions & 0 deletions test/declarations.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
/* eslint import/unambiguous: 0 */

declare var jest;
declare var after: Function;
declare var afterAll: Function;
declare var beforeAll: Function;
declare var beforeEach: Function;
declare var afterEach: Function;
declare var it: Function;
declare var describe: Function;
declare var test: Function;