-
Notifications
You must be signed in to change notification settings - Fork 568
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
feature(3DS): Implement show method and return response with enriched nonce #2452
Open
siddy2181
wants to merge
64
commits into
main
Choose a base branch
from
fastlane-3ds-e2e
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 61 commits
Commits
Show all changes
64 commits
Select commit
Hold shift + click to select a range
2a9545d
chore: prettier
mchoun dd3c570
allow three-domain-secure component
mchoun 673d98d
refactor threedomainsecure component to class
mchoun 71c8127
correct typo
mchoun 62afa53
refactor test for class component
mchoun 89020d4
chore: fix lint
mchoun 93e1b36
chore: fix flow issues
mchoun c718c95
pin flow-remove-types and hermes-parser version due to flow errors
mchoun 295d091
return methods only instead of entire class
mchoun 4fd9cef
modify interface to reflect future state
mchoun 89be40e
resolve WIP stash merge
mchoun 1e06a6d
implement isEligible request to API
mchoun 58db542
Merge branch 'main' into feature/DTPPCPSDK-2660-3ds-eligibility
mchoun 8cadea9
change sdktoken to idtoken
mchoun 3d3fe62
modify protectedExport to Local or Stage check
mchoun 149fa12
change protectedexport to local/stage export
mchoun 5ae9ced
pass transaction context as received
siddy2181 b3aa99f
fix flow type errors
mchoun 714509c
linting / flow fixes and skipping test for now
mchoun dba7fc4
add isEligible test skeleton
mchoun 7e193e2
check for payer-action rel in links
mchoun 68bc87d
throw error on API error isntead of false
mchoun ed1879f
wip: add test for isEligible
mchoun bb1fe54
remove comments
mchoun 04124f1
additional test for isEligble
mchoun 1fa80ab
remove console logs
mchoun ab27dab
add threeDS overlay component
siddy2181 2819404
add styling for 3DS iframe overlay
siddy2181 28f0a16
update overlay, save 3ds comp on eligibility to class variable
siddy2181 9bd1803
fix stage url,nit
siddy2181 139db48
fix lint
siddy2181 dd42464
fix lint/typecheck
siddy2181 811872d
fix tests
siddy2181 1f4bbc7
fix tests
siddy2181 a26e760
fix something please
siddy2181 e7a8f01
test with dub in stage
siddy2181 f56383c
remove port
siddy2181 a814d6d
test api subdomain
siddy2181 62ead51
add sdkMeta, logs, fix css
siddy2181 5cf3f99
Merge branch 'main' into fastlane-3ds-e2e
siddy2181 58581a5
wip
imbrian 13993a1
pass braintree-version header to gql
imbrian c74de53
fix lint
siddy2181 f0163ce
fix typecheck
siddy2181 9155667
skip test
siddy2181 a6e4620
add domain
siddy2181 ea7b44f
Merge branch 'main' of https://github.com/paypal/paypal-checkout-comp…
siddy2181 0f42cd5
testing only fix
siddy2181 a29fbcd
add allowParentDomain
siddy2181 b768595
add globals and enable automatic config
siddy2181 a843e7e
render zoid in parent, pass payerActionUrl prop
siddy2181 a34ea3a
fix lint
siddy2181 009d5d8
fix show() response back to merchant
siddy2181 53f155f
call eligibility api with an lsat
siddy2181 9093e4c
fix response back to the merchant
siddy2181 5b0fb8b
fix response back to the merchant
siddy2181 4792f9c
console logs cleanup
siddy2181 67542a5
add tests for interface and api
siddy2181 650e451
add test - utils.jsx
siddy2181 a83adb6
add devOnlyExport, cleanup
siddy2181 7e7a6a7
reverted dist content
siddy2181 99836cd
fix test for protected export
siddy2181 9d345fb
fix test for protected export
siddy2181 5073359
address review comments
siddy2181 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ module.exports = { | |
__HOST__: true, | ||
__PATH__: true, | ||
__COMPONENTS__: true, | ||
$Shape: true, | ||
}, | ||
|
||
rules: { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
/* @flow */ | ||
import { ZalgoPromise } from "@krakenjs/zalgo-promise/src"; | ||
import { request } from "@krakenjs/belter/src"; | ||
import { getSessionID, getPartnerAttributionID } from "@paypal/sdk-client/src"; | ||
|
||
import { callRestAPI } from "../lib"; | ||
import { HEADERS } from "../constants/api"; | ||
|
||
type HTTPRequestOptions = {| | ||
// eslint-disable-next-line flowtype/no-weak-types | ||
data: any, | ||
baseURL?: string, | ||
accessToken?: string, | ||
method?: string, // TODO do we have an available type for this in Flow? | ||
|}; | ||
|
||
interface HTTPClientType { | ||
accessToken: ?string; | ||
baseURL: ?string; | ||
} | ||
|
||
type HTTPClientOptions = {| | ||
accessToken: ?string, | ||
baseURL: ?string, | ||
|}; | ||
|
||
export class HTTPClient implements HTTPClientType { | ||
accessToken: ?string; | ||
baseURL: ?string; | ||
|
||
constructor(options?: $Shape<HTTPClientOptions> = {}) { | ||
this.accessToken = options.accessToken; | ||
this.baseURL = options.baseURL; | ||
} | ||
|
||
setAccessToken(token: string) { | ||
this.accessToken = token; | ||
} | ||
} | ||
|
||
export class RestClient extends HTTPClient { | ||
request({ baseURL, ...rest }: HTTPRequestOptions): ZalgoPromise<{ ... }> { | ||
return callRestAPI({ | ||
url: baseURL ?? this.baseURL ?? "", | ||
accessToken: this.accessToken, | ||
...rest, | ||
}); | ||
} | ||
} | ||
|
||
const GRAPHQL_URI = "/graphql"; | ||
|
||
type GQLQuery = {| | ||
query: string, | ||
variables: { ... }, | ||
|}; | ||
|
||
export function callGraphQLAPI({ | ||
accessToken, | ||
baseURL, | ||
data: query, | ||
headers, | ||
}: {| | ||
accessToken: ?string, | ||
baseURL: string, | ||
data: GQLQuery, | ||
headers: Object, // TODO fix | ||
// eslint-disable-next-line flowtype/no-weak-types | ||
|}): ZalgoPromise<any> { | ||
if (!accessToken) { | ||
throw new Error( | ||
`No access token passed to GraphQL request ${baseURL}${GRAPHQL_URI}` | ||
); | ||
} | ||
|
||
const requestHeaders = { | ||
...headers, | ||
[HEADERS.AUTHORIZATION]: `Bearer ${accessToken}`, | ||
[HEADERS.CONTENT_TYPE]: "application/json", | ||
[HEADERS.PARTNER_ATTRIBUTION_ID]: getPartnerAttributionID() ?? "", | ||
[HEADERS.CLIENT_METADATA_ID]: getSessionID(), | ||
}; | ||
|
||
return request({ | ||
method: "post", | ||
url: `${baseURL}${GRAPHQL_URI}`, | ||
headers: requestHeaders, | ||
json: query, | ||
}).then(({ status, body }) => { | ||
// TODO handle body.errors | ||
if (status !== 200) { | ||
throw new Error(`${baseURL}${GRAPHQL_URI} returned status ${status}`); | ||
} | ||
|
||
return body; | ||
}); | ||
} | ||
|
||
export class GraphQLClient extends HTTPClient { | ||
request({ | ||
baseURL, | ||
data, | ||
accessToken, | ||
headers, | ||
}: // eslint-disable-next-line flowtype/no-weak-types | ||
any): ZalgoPromise<any> { | ||
return callGraphQLAPI({ | ||
accessToken: accessToken ?? this.accessToken, | ||
data, | ||
baseURL: baseURL ?? this.baseURL ?? "", | ||
headers, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
/* @flow */ | ||
import { describe, expect, vi } from "vitest"; | ||
import { request } from "@krakenjs/belter/src"; | ||
|
||
import { callRestAPI } from "../lib"; | ||
import { HEADERS } from "../constants/api"; | ||
|
||
import { RestClient, GraphQLClient, callGraphQLAPI, HTTPClient } from "./api"; | ||
|
||
vi.mock("@krakenjs/belter/src", async () => { | ||
return { | ||
...(await vi.importActual("@krakenjs/belter/src")), | ||
request: vi.fn(), | ||
}; | ||
}); | ||
|
||
vi.mock("@paypal/sdk-client/src", async () => { | ||
return { | ||
...(await vi.importActual("@paypal/sdk-client/src")), | ||
getSessionID: () => "session_id_123", | ||
getPartnerAttributionID: () => "partner_attr_123", | ||
}; | ||
}); | ||
|
||
vi.mock("../lib", () => ({ | ||
callRestAPI: vi.fn(), | ||
})); | ||
|
||
describe("API", () => { | ||
const accessToken = "access_token"; | ||
const baseURL = "http://localhost.paypal.com:8080"; | ||
|
||
afterEach(() => { | ||
vi.clearAllMocks(); | ||
}); | ||
describe("HTTPClient", () => { | ||
it("should set access token and base url in constructor", () => { | ||
const client = new HTTPClient({ accessToken, baseURL }); | ||
expect(client.accessToken).toBe(accessToken); | ||
expect(client.baseURL).toBe(baseURL); | ||
}); | ||
|
||
it("should set access token", () => { | ||
const client = new HTTPClient(); | ||
client.setAccessToken(accessToken); | ||
expect(client.accessToken).toBe(accessToken); | ||
}); | ||
}); | ||
|
||
describe("RestClient", () => { | ||
it("should make a REST API call with correct params", () => { | ||
const data = { test: "data" }; | ||
const requestOptions = { | ||
data, | ||
baseURL, | ||
}; | ||
const client = new RestClient({ accessToken }); | ||
client.request(requestOptions); | ||
expect(callRestAPI).toHaveBeenCalledWith({ | ||
accessToken, | ||
data, | ||
url: baseURL, | ||
}); | ||
}); | ||
}); | ||
|
||
describe("GraphQLClient", () => { | ||
const query = { test: "data" }; | ||
const data = { query }; | ||
const headers = { "Content-Type": "application/json" }; | ||
|
||
it.skip("should make a GraphQL API call with correct params", () => { | ||
vi.spyOn({ callGraphQLAPI }, "callGraphQLAPI").mockResolvedValue({ | ||
data: { test: "data" }, | ||
}); | ||
const client = new GraphQLClient({ accessToken, baseURL }); | ||
client.request({ data, headers }).then(() => { | ||
expect(callGraphQLAPI).toHaveBeenCalledWith({ | ||
accessToken, | ||
baseURL, | ||
data, | ||
headers, | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
describe("callGraphQLAPI", () => { | ||
const query = '{ "test": "data" }'; | ||
const variables = { option: "param1" }; | ||
const gqlQuery = { query, variables }; | ||
|
||
const response = { data: { test: "data" } }; | ||
|
||
it("should throw error if no access token is provided", () => { | ||
expect(() => | ||
callGraphQLAPI({ | ||
accessToken: null, | ||
baseURL, | ||
data: gqlQuery, | ||
headers: {}, | ||
}) | ||
).toThrowError( | ||
new Error( | ||
`No access token passed to GraphQL request ${baseURL}/graphql` | ||
) | ||
); | ||
}); | ||
|
||
it("should make a GraphQL API call with correct params", () => { | ||
vi.mocked(request).mockResolvedValue({ | ||
status: 200, | ||
body: response, | ||
}); | ||
callGraphQLAPI({ | ||
accessToken, | ||
baseURL, | ||
data: gqlQuery, | ||
headers: {}, | ||
}); | ||
expect(request).toHaveBeenCalledWith({ | ||
method: "post", | ||
url: `${baseURL}/graphql`, | ||
headers: { | ||
[HEADERS.AUTHORIZATION]: `Bearer ${accessToken}`, | ||
[HEADERS.CONTENT_TYPE]: "application/json", | ||
[HEADERS.PARTNER_ATTRIBUTION_ID]: "partner_attr_123", | ||
[HEADERS.CLIENT_METADATA_ID]: "session_id_123", | ||
}, | ||
json: gqlQuery, | ||
}); | ||
}); | ||
|
||
it("should resolve with response body on success", async () => { | ||
vi.mocked(request).mockResolvedValue({ | ||
status: 200, | ||
body: response, | ||
}); | ||
const resp = await callGraphQLAPI({ | ||
accessToken, | ||
baseURL, | ||
data: gqlQuery, | ||
headers: {}, | ||
}); | ||
expect(resp).toEqual(response); | ||
}); | ||
|
||
it("should throw error on error status", async () => { | ||
const status = 400; | ||
vi.mocked(request).mockResolvedValue({ | ||
status, | ||
body: { message: "Something went wrong" }, | ||
}); | ||
|
||
try { | ||
await callGraphQLAPI({ | ||
accessToken, | ||
baseURL, | ||
data: gqlQuery, | ||
headers: {}, | ||
}); | ||
} catch (error) { | ||
expect(error.message).toBe( | ||
`${baseURL}/graphql returned status ${status}` | ||
); | ||
} | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to
automatic: true
anymore, correct? I believe we were testing this when we were running into the zoid xprops issue.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe, having
automatic: true
wouldn't hurt. It would make this component available automatically when created. My understanding was that it will prevent collision with the old 3DS component.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
automatic: true
will return this component for everyone that loads the JS SDK. The exported setup() method is what will automatically create the zoid component when the component is loaded.This will probably be fine for now since this component is still protected but we should remove this before we allow this in Production. If we don't,
paypal.ThreeDomainSecureClient
will be available to everyone even if they don't passthree-domain-secure
into the components query param.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should probably remove it now because even though its hidden, it will show up in all bundles served. I'm sure its super small but might as well not ship javascript if we don't have to