Skip to content

Commit

Permalink
[INTER-2976] Added gateway public ID and type to fastlane instance
Browse files Browse the repository at this point in the history
  • Loading branch information
BoldCole committed Apr 19, 2024
1 parent 57e5448 commit 8ad2261
Show file tree
Hide file tree
Showing 5 changed files with 340 additions and 32 deletions.
102 changes: 102 additions & 0 deletions src/fastlane/fastlane.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {IFastlaneInstance} from 'src/types';

// Do not remove. This is here to enforce adherence of this class to the interface
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _: IFastlaneInstance = {} as Fastlane;
type OmittedIFastlaneInstance = Omit<IFastlaneInstance, 'gatewayPublicId' | 'type'>;
type IFastlaneAddress = Awaited<ReturnType<IFastlaneInstance['identity']['triggerAuthenticationFlow']>>['profileData']['shippingAddress'];

Check warning on line 7 in src/fastlane/fastlane.ts

View workflow job for this annotation

GitHub Actions / Build

'IFastlaneAddress' is defined but never used

interface IPayPalAddress {
companyName?: string;
name: {
firstName: 'string';
lastName: 'string';
fullName: 'string';
};
address: {
addressLine1: 'string';
addressLine2?: 'string';
adminArea2: 'string';
adminArea1: 'string';
postalCode: 'string',
countryCode: 'string';
};
phoneNumber: {
nationalNumber: 'string';
countryCode: 'string';
};
}

export default class Fastlane {
public profile: IFastlaneInstance['profile'];
public identity: IFastlaneInstance['identity'];
public FastlaneCardComponent: IFastlaneInstance['FastlaneCardComponent'];
public FastlanePaymentComponent: IFastlaneInstance['FastlanePaymentComponent'];
setLocale: IFastlaneInstance['setLocale'];

constructor(
private instance: OmittedIFastlaneInstance,
public type: IFastlaneInstance['type'],
public gatewayPublicId: string
) {
this.setLocale = instance.setLocale.bind(instance);
this.identity = {
triggerAuthenticationFlow: this.triggerAuthenticationFlow,
lookupCustomerByEmail: instance.identity.lookupCustomerByEmail.bind(instance.identity),
};
this.profile = {
showShippingAddressSelector: this.showShippingAddressSelector,
showCardSelector: instance.profile.showCardSelector.bind(instance.profile.showCardSelector),
};
this.FastlaneCardComponent = instance.FastlaneCardComponent.bind(instance);
this.FastlanePaymentComponent = instance.FastlanePaymentComponent.bind(instance);
}

private showShippingAddressSelector = async () => {
const resp = await this.instance.profile.showShippingAddressSelector();

// No restructuring needed for PPCP unless the selection has not changed
if (this.type === 'braintree' || !resp.selectionChanged) {
return resp;
}

const paypalAddress = resp.selectedAddress as unknown as IPayPalAddress;
return {
selectionChanged: resp.selectionChanged,
selectedAddress: this.transformPaypalAddress(paypalAddress),
};
}

private triggerAuthenticationFlow = async (customerContextId: string) => {
const resp = await this.instance.identity.triggerAuthenticationFlow(customerContextId);

if (this.type === 'braintree' || resp.authenticationState !== 'succeeded') {
return resp;
}

const paypalAddress = resp.profileData.shippingAddress as unknown as IPayPalAddress;
return {
authenticationState: resp.authenticationState,
profileData: {
card: resp.profileData.card,
name: resp.profileData.name,
shippingAddress: this.transformPaypalAddress(paypalAddress),
},
};
}

private transformPaypalAddress = (paypalAddress: IPayPalAddress) => {
return {
firstName: paypalAddress.name.firstName,
lastName: paypalAddress.name.lastName,
locality: paypalAddress.address.adminArea2,
region: paypalAddress.address.adminArea1,
countryCodeAlpha2: paypalAddress.address.countryCode,
phoneNumber: `${paypalAddress.phoneNumber.countryCode}${paypalAddress.phoneNumber.nationalNumber}`,
postalCode: paypalAddress.address.postalCode,
streetAddress: paypalAddress.address.addressLine1,
extendedAddress: paypalAddress.address.addressLine2,
company: paypalAddress.companyName,
};
}
}
17 changes: 12 additions & 5 deletions src/fastlane/initFastlane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ import {
IBraintreeClient,
FastlaneLoadingError,
} from 'src';
import Fastlane from './fastlane';

interface TokenResponse {
is_test_mode: boolean;
client_token: string;
gateway_public_id: string;
type: IFastlaneInstance['type'];
}

interface BraintreeTokenResponse extends TokenResponse {
Expand Down Expand Up @@ -46,8 +49,10 @@ export async function initFastlane(): Promise<IFastlaneInstance> {
client_id: clientId,
type,
is_test_mode: isTest,
gateway_public_id: gatewayPublicId,
} = await resp.json().then(r => r.data) as BraintreeTokenResponse | PPCPTokenResponse;

let fastlane: IFastlaneInstance;
switch (type) {
case 'braintree': {
await Promise.all([
Expand All @@ -62,13 +67,13 @@ export async function initFastlane(): Promise<IFastlaneInstance> {
client: client,
riskCorrelationId: getPublicOrderId(),
});
const fastlane = await braintree.fastlane.create({
fastlane = await braintree.fastlane.create({
client,
authorization: clientToken,
deviceData: dataCollector.deviceData,
});
return fastlane;

break;
}
case 'ppcp': {
const paypal = await loadScript({
Expand All @@ -77,13 +82,15 @@ export async function initFastlane(): Promise<IFastlaneInstance> {
components: 'fastlane',
debug: isTest,
}) as unknown as {Fastlane: () => Promise<IFastlaneInstance>};
const fastlane = await paypal.Fastlane();
fastlane = await paypal.Fastlane();

return fastlane;
break;
}
default:
throw new Error(`unknown type: ${type}`);
}

return new Fastlane(fastlane, type, gatewayPublicId);
} catch (error) {
if (error instanceof Error) {
error.name = FastlaneLoadingError.name;
Expand Down
48 changes: 25 additions & 23 deletions src/types/fastlane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,27 +72,29 @@ export interface IFastlaneAuthenticatedCustomerResult {
}

export interface IFastlaneInstance {
profile: {
showShippingAddressSelector: () => Promise<{
selectionChanged: true;
selectedAddress: IFastlaneAddress;
} | {
selectionChanged: false;
selectedAddress: null;
}>;
showCardSelector: () => Promise<{
selectionChanged: true;
selectedCard: IFastlanePaymentToken;
} | {
selectionChanged: false;
selectedCard: null;
}>;
};
setLocale: (locale: string) => void;
identity: {
lookupCustomerByEmail: (email: string) => Promise<{customerContextId: string}>;
triggerAuthenticationFlow: (customerContextId: string) => Promise<IFastlaneAuthenticatedCustomerResult>
};
FastlanePaymentComponent: (options: IFastlaneComponentOptions) => Promise<IFastlanePaymentComponent>;
FastlaneCardComponent: (options: Omit<IFastlaneComponentOptions, 'shippingAddress'>) => IFastlaneCardComponent;
gatewayPublicId: string;
type: 'ppcp' | 'braintree';
profile: {
showShippingAddressSelector: () => Promise<{
selectionChanged: true;
selectedAddress: IFastlaneAddress;
} | {
selectionChanged: false;
selectedAddress: null;
}>;
showCardSelector: () => Promise<{
selectionChanged: true;
selectedCard: IFastlanePaymentToken;
} | {
selectionChanged: false;
selectedCard: null;
}>;
};
setLocale: (locale: string) => void;
identity: {
lookupCustomerByEmail: (email: string) => Promise<{customerContextId: string}>;
triggerAuthenticationFlow: (customerContextId: string) => Promise<IFastlaneAuthenticatedCustomerResult>
};
FastlanePaymentComponent: (options: IFastlaneComponentOptions) => Promise<IFastlanePaymentComponent>;
FastlaneCardComponent: (options: Omit<IFastlaneComponentOptions, 'shippingAddress'>) => IFastlaneCardComponent;
}
173 changes: 173 additions & 0 deletions tests/fastlane/fastlane.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import Fastlane from 'src/fastlane/fastlane';

describe('testing Fastlane class', () => {
const fastlaneInstanceMock = {
setLocale: jest.fn(),
FastlaneCardComponent: jest.fn(),
FastlanePaymentComponent: jest.fn(),
identity: {
lookupCustomerByEmail: jest.fn(),
triggerAuthenticationFlow: jest.fn(),
},
profile: {
showCardSelector: jest.fn(),
showShippingAddressSelector: jest.fn(),
},
};

afterEach(() => {
jest.clearAllMocks();
});

test.each([
{
type: 'ppcp',
respAddress: {
companyName: 'company',
name: {
firstName: 'firstName',
lastName: 'lastName',
fullName: 'fullName',
},
address: {
addressLine1: 'addressLine1',
addressLine2: 'addressLine2',
adminArea2: 'adminArea2',
adminArea1: 'adminArea1',
postalCode: 'postalCode',
countryCode: 'countryCode',
},
phoneNumber: {
countryCode: '1',
nationalNumber: '2041234567',
},
},
expected: () => ({
firstName: 'firstName',
lastName: 'lastName',
locality: 'adminArea2',
region: 'adminArea1',
countryCodeAlpha2: 'countryCode',
phoneNumber: '12041234567',
postalCode: 'postalCode',
streetAddress: 'addressLine1',
extendedAddress: 'addressLine2',
company: 'company',
}),
} as const,
{
type: 'braintree',
respAddress: {
firstName: 'firstName',
lastName: 'lastName',
locality: 'adminArea2',
region: 'adminArea1',
countryCodeAlpha2: 'countryCode',
phoneNumber: '12041234567',
postalCode: 'postalCode',
streetAddress: 'addressLine1',
extendedAddress: 'addressLine2',
company: 'company',
},
expected() {
return this.respAddress;
},
} as const,
])('$type showShippingAddressSelector', async (input) => {
// Arranging
fastlaneInstanceMock.profile.showShippingAddressSelector.mockResolvedValue({
selectionChanged: true,
selectedAddress: input.respAddress,
});

// Acting
const fastlane = new Fastlane(fastlaneInstanceMock, input.type, '');
const actualResp = await fastlane.profile.showShippingAddressSelector();

// Asserting
expect(actualResp.selectedAddress).toStrictEqual(input.expected());
});

test.each([
{
type: 'ppcp',
respProfileData: {
card: {},
name: {
firstName: 'firstName',
lastName: 'firstName',
},
shippingAddress: {
companyName: 'company',
name: {
firstName: 'firstName',
lastName: 'lastName',
fullName: 'fullName',
},
address: {
addressLine1: 'addressLine1',
addressLine2: 'addressLine2',
adminArea2: 'adminArea2',
adminArea1: 'adminArea1',
postalCode: 'postalCode',
countryCode: 'countryCode',
},
phoneNumber: {
countryCode: '1',
nationalNumber: '2041234567',
},
}
},
expected: () => ({
firstName: 'firstName',
lastName: 'lastName',
locality: 'adminArea2',
region: 'adminArea1',
countryCodeAlpha2: 'countryCode',
phoneNumber: '12041234567',
postalCode: 'postalCode',
streetAddress: 'addressLine1',
extendedAddress: 'addressLine2',
company: 'company',
}),
} as const,
{
type: 'braintree',
respProfileData: {
card: {},
name: {
firstName: 'firstName',
lastName: 'firstName',
},
shippingAddress: {
firstName: 'firstName',
lastName: 'lastName',
locality: 'adminArea2',
region: 'adminArea1',
countryCodeAlpha2: 'countryCode',
phoneNumber: '12041234567',
postalCode: 'postalCode',
streetAddress: 'addressLine1',
extendedAddress: 'addressLine2',
company: 'company',
},
},
expected() {
return this.respProfileData.shippingAddress;
},
} as const,
])('$type triggerAuthenticationFlow', async (input) => {
// Arranging
fastlaneInstanceMock.identity.triggerAuthenticationFlow.mockResolvedValue({
authenticationState: 'succeeded',
profileData: input.respProfileData,
});

// Acting
const fastlane = new Fastlane(fastlaneInstanceMock, input.type, '');
const actualResp = await fastlane.identity.triggerAuthenticationFlow('1');

// Asserting
expect(actualResp.profileData.shippingAddress).toStrictEqual(input.expected());
});
});
Loading

0 comments on commit 8ad2261

Please sign in to comment.