Skip to content

Commit

Permalink
draft stories
Browse files Browse the repository at this point in the history
  • Loading branch information
ribeiroguilherme committed Nov 25, 2024
1 parent dfa4a75 commit 697fae5
Show file tree
Hide file tree
Showing 18 changed files with 720 additions and 20 deletions.
6 changes: 0 additions & 6 deletions packages/lib/src/components/PayPal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,6 @@ import { AddressData } from '../../types/global-types';
import { UIElementProps } from '../internal/UIElement/types';
import PaypalElement from './Paypal';

declare global {
interface Window {
paypal: object;
}
}

export interface PayPalConfiguration extends UIElementProps {
/**
* Configuration returned by the backend
Expand Down
81 changes: 81 additions & 0 deletions packages/lib/src/components/PayPalFastlane/FastlaneSDK.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { resolveEnvironments } from '../../core/Environment';
import requestFastlaneToken from './services/request-fastlane-token';
import Script from '../../utils/Script';

import type { Fastlane, AuthenticatedCustomerResult, ShowShippingAddressSelectorResult } from './types';
import type { FastlaneTokenData } from './services/request-fastlane-token';
import type { CoreConfiguration } from '../../core/types';

export interface FastlaneSDKConfiguration {
clientKey: string;
locale?: 'en_us' | 'es_us' | 'fr_rs' | 'zh_us';
environment?: CoreConfiguration['environment'];
}

class FastlaneSDK {
private readonly clientKey: string;
private readonly checkoutShopperURL: string;
private readonly locale: string;

private fastlaneSdk: Fastlane;

constructor(configuration: FastlaneSDKConfiguration) {
const { apiUrl } = resolveEnvironments(configuration.environment);

this.checkoutShopperURL = apiUrl;
this.clientKey = configuration.clientKey;
this.locale = configuration.locale || 'en_us';
}

public async initialize(): Promise<FastlaneSDK> {
const tokenData = await this.requestClientToken();
await this.fetchSdk(tokenData.value, tokenData.clientId);
await this.initializeFastlane();
return this;
}

public async authenticate(email: string): Promise<AuthenticatedCustomerResult> {
const { customerContextId } = await this.fastlaneSdk.identity.lookupCustomerByEmail(email);

if (customerContextId) {
return this.fastlaneSdk.identity.triggerAuthenticationFlow(customerContextId);
} else {
return {
authenticationState: 'not_found',
profileData: undefined
};
}
}

public showShippingAddressSelector(): Promise<ShowShippingAddressSelectorResult> {
if (!this.fastlaneSdk.profile) return null;
return this.fastlaneSdk.profile.showShippingAddressSelector();
}

public async mountWatermark(container: HTMLElement | string, options?) {
const component = await this.fastlaneSdk.FastlaneWatermarkComponent(options);
component.render(container);
}

private requestClientToken(): Promise<FastlaneTokenData> {
return requestFastlaneToken(this.checkoutShopperURL, this.clientKey);
}

private async fetchSdk(clientToken: string, clientId: string) {
const url = `https://www.paypal.com/sdk/js?client-id=${clientId}&components=buttons,fastlane`;
const script = new Script(url, 'body', {}, { sdkClientToken: clientToken });

try {
await script.load();
} catch (error) {
console.error(error);
}
}

private async initializeFastlane() {
this.fastlaneSdk = await window.paypal.Fastlane({});
this.fastlaneSdk.setLocale(this.locale);
}
}

export default FastlaneSDK;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import FastlaneSDK from './FastlaneSDK';
import type { FastlaneSDKConfiguration } from './FastlaneSDK';

async function initializeFastlane(configuration: FastlaneSDKConfiguration): Promise<FastlaneSDK> {
const fastlane = new FastlaneSDK(configuration);
return await fastlane.initialize();
}

export default initializeFastlane;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { httpPost } from '../../../core/Services/http';

export interface FastlaneTokenData {
id: string;
clientId: string;
value: string;
expiresAt: string;
}

function requestFastlaneToken(url: string, clientKey: string): Promise<FastlaneTokenData> {
// @ts-ignore ignore for now
const path = `utility/v1/payPalFastlane/tokens?clientKey=${clientKey}`;

return Promise.resolve({
id: '2747bd08-783a-45c6-902b-3efbda5497b7',
clientId: 'AXy9hIzWB6h_LjZUHjHmsbsiicSIbL4GKOrcgomEedVjduUinIU4C2llxkW5p0OG0zTNgviYFceaXEnj',
merchantId: 'C3UCKQHMW4948',
value: 'eyJraWQiOiJkMTA2ZTUwNjkzOWYxMWVlYjlkMTAyNDJhYzEyMDAwMiIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJpc3MiOiJodHRwczovL2FwaS5zYW5kYm94LnBheXBhbC5jb20iLCJhdWQiOlsiaHR0cHM6Ly9hcGkuYnJhaW50cmVlZ2F0ZXdheS5jb20iLCJjaGVja291dC1wbGF5Z3JvdW5kLm5ldGxpZnkuYXBwIl0sInN1YiI6Ik02VE5BRVNaNUZHTk4iLCJhY3IiOlsiY2xpZW50Il0sInNjb3BlIjpbIkJyYWludHJlZTpWYXVsdCJdLCJvcHRpb25zIjp7fSwiYXoiOiJjY2cxOC5zbGMiLCJleHRlcm5hbF9pZCI6WyJQYXlQYWw6QzNVQ0tRSE1XNDk0OCIsIkJyYWludHJlZTozZGI4aG5rdHJ0bXpzMmd0Il0sImV4cCI6MTczMDg5Nzk4NSwiaWF0IjoxNzMwODk3MDg1LCJqdGkiOiJVMkFBS05JdjBkbjZxaWtEQUMweVctdmJKSWhra3VPYTVSQ2MwMlJNdXVMWWVFUUQ2NE85UjJ1eWtRcFpucjZPanhyT3I3OVdLd0ZadGtwdi1LdUZiWHBHWkxFLU9uUEJEXzdUb1Z0RzI2dE9rM2ZNeHEyaVNna2RUd3UzRk5wQSJ9.p2lVHnIM29OsQQq4Q6N5UeHAs3AWDWs9OZ0DmYG-aMng_Dul6j1zdK4T5WoyWMu8eoM3DXHuRSQfZvD4eUPjLA',
expiresAt: '2024-11-01T13:34:01.804+00:00'
});
// return httpPost<FastlaneTokenData>({ loadingContext: url, path, errorLevel: 'fatal' });
}

export default requestFastlaneToken;
103 changes: 103 additions & 0 deletions packages/lib/src/components/PayPalFastlane/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
export type FastlaneConstructor = (options: FastlaneOptions) => Promise<Fastlane>;

/**
* PayPal Fastlane Reference:
* https://developer.paypal.com/docs/checkout/fastlane/reference/#link-customizeyourintegration
*/

// TODO: Verify if we pass options here
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface FastlaneOptions {}

export interface Fastlane {
identity: {
lookupCustomerByEmail: (email: string) => Promise<{ customerContextId: string }>;
triggerAuthenticationFlow: (customerContextId: string, options?: AuthenticationFlowOptions) => Promise<AuthenticatedCustomerResult>;
};
profile: {
showShippingAddressSelector: () => Promise<ShowShippingAddressSelectorResult>;
showCardSelector: () => ShowCardSelectorResult;
};
setLocale: (locale: string) => void;
FastlaneWatermarkComponent: (options: FastlaneWatermarkOptions) => Promise<FastlaneWatermarkComponent>;
}

interface FastlaneWatermarkOptions {
includeAdditionalInfo: boolean;
}
interface FastlaneWatermarkComponent {
render: (container) => null;
}

// TODO: fill this in after workshop
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface AuthenticationFlowOptions {}

/**
* The AuthenticatedCustomerResult object type is returned from the identity.triggerAuthenticationFlow() call.
*/
export interface AuthenticatedCustomerResult {
authenticationState: 'succeeded' | 'failed' | 'canceled' | 'not_found';
profileData: FastlaneProfile;
}
interface FastlaneProfile {
name: Name;
shippingAddress: FastlaneShipping;
card: PaymentToken;
}

interface Name {
firstName: string;
lastName: string;
fullName: string;
}

interface Phone {
nationalNumber: string;
countryCode: string;
}

export interface FastlaneAddress {
addressLine1: string;
addressLine2: string;
adminArea1: string;
adminArea2: string;
postalCode: string;
countryCode: string;
phone: Phone;
}

export interface FastlaneShipping {
name: Name;
address: FastlaneAddress;
phoneNumber: Phone;
}

interface PaymentToken {
id: string;
paymentSource: PaymentSource;
}
interface PaymentSource {
card: CardPaymentSource;
}

interface CardPaymentSource {
brand: string;
expiry: string;
lastDigits: string;
name: string;
billingAddress: FastlaneAddress;
}

/**
* Profile method reference types
*/
export interface ShowShippingAddressSelectorResult {
selectionChanged: boolean;
selectedAddress: FastlaneShipping;
}

interface ShowCardSelectorResult {
selectionChanged: boolean;
selectedCard: PaymentToken;
}
4 changes: 4 additions & 0 deletions packages/lib/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ export { default as ANCV } from './ANCV';
export { default as Giftcard } from './Giftcard';
export { default as MealVoucherFR } from './MealVoucherFR';

/** Utilities */
export { default as initializeFastlane } from './PayPalFastlane/initializeFastlane';
export { default as FastlaneSDK } from './PayPalFastlane/FastlaneSDK';

/** Internal */
export { default as Address } from './Address';
export { default as BankTransfer } from './BankTransfer';
Expand Down
11 changes: 11 additions & 0 deletions packages/lib/src/types/custom.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import type { FastlaneConstructor } from '../components/PayPalFastlane/types';

declare module '*.scss' {
const content: { [className: string]: string };
export default content;
}

declare global {
interface Window {
ApplePaySession?: ApplePaySession;
paypal?: {
Fastlane?: FastlaneConstructor;
};
}
}

interface Window {
AdyenWeb: any;
VISA_SDK?: {
Expand Down
13 changes: 5 additions & 8 deletions packages/lib/storybook/helpers/create-advanced-checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ async function createAdvancedFlowCheckout({
countryCode,
shopperLocale,
amount,
allowedPaymentTypes = [],
paymentMethodsOverride,
...restCheckoutProps
}: AdyenCheckoutProps): Promise<Checkout> {
Expand All @@ -28,16 +29,12 @@ async function createAdvancedFlowCheckout({
const paymentMethodsResponse = !paymentMethodsOverride
? _paymentMethodsResponse
: {
storedPaymentMethods: [
...(_paymentMethodsResponse.storedPaymentMethods ? _paymentMethodsResponse.storedPaymentMethods : []),
...(paymentMethodsOverride.storedPaymentMethods ? paymentMethodsOverride.storedPaymentMethods : [])
],
paymentMethods: [
...(_paymentMethodsResponse.paymentMethods ? _paymentMethodsResponse.paymentMethods : []),
...(paymentMethodsOverride.paymentMethods ? paymentMethodsOverride.paymentMethods : [])
]
storedPaymentMethods: paymentMethodsOverride.storedPaymentMethods ? paymentMethodsOverride.storedPaymentMethods : [],
paymentMethods: paymentMethodsOverride.paymentMethods ? paymentMethodsOverride.paymentMethods : []
};

paymentMethodsResponse.paymentMethods = paymentMethodsResponse.paymentMethods.filter(pm => allowedPaymentTypes.includes(pm.type));

const checkout = await AdyenCheckout({
clientKey: process.env.CLIENT_KEY,
// @ts-ignore CLIENT_ENV has valid value
Expand Down
15 changes: 9 additions & 6 deletions packages/lib/storybook/helpers/create-checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ import Core from '../../src/core';
async function createCheckout(checkoutConfig: GlobalStoryProps): Promise<Core> {
const { useSessions, ...rest } = checkoutConfig;

const overidenPaymentMethodsAmount =
const overriddenPaymentMethodsAmount =
(rest.paymentMethodsOverride?.paymentMethods?.length || 0) + (rest.paymentMethodsOverride?.storedPaymentMethods?.length || 0);
const hasPaymentOveride = overidenPaymentMethodsAmount > 0;
const hasPaymentOverridden = overriddenPaymentMethodsAmount > 0;

if (useSessions && !hasPaymentOveride) {
return await createSessionsCheckout(rest);
} else if (useSessions && hasPaymentOveride) {
console.warn('🟢 Checkout Storybook: paymentMethodsOverride is defined while using Sessions, forcing advance flow.');
if (useSessions) {
if (!hasPaymentOverridden && !rest.allowedPaymentTypes) {
return await createSessionsCheckout(rest);
} else {
console.warn('🟢 Checkout Storybook: Forcing advance flow.');
}
}

return await createAdvancedFlowCheckout(rest);
}

Expand Down
1 change: 1 addition & 0 deletions packages/lib/storybook/stories/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type AdyenCheckoutProps = {
shopperLocale: string;
amount: number;
sessionData?: PaymentMethodsResponse;
allowedPaymentTypes?: string[];
paymentMethodsOverride?: PaymentMethodsResponse;
onPaymentCompleted?: (data: any, element?: UIElement) => void;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { MetaConfiguration, StoryConfiguration } from '../../types';
import { FastlaneInSinglePageApp } from './FastlaneInSinglePageApp';

type FastlaneStory = StoryConfiguration<{}>;

const meta: MetaConfiguration<FastlaneStory> = {
title: 'Wallets/Fastlane'
};

export const Default: FastlaneStory = {
render: checkoutConfig => {
const allowedPaymentTypes = ['scheme', 'paypal'];
return <FastlaneInSinglePageApp checkoutConfig={{ allowedPaymentTypes, ...checkoutConfig }} />;
}
};

export default meta;
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { h } from 'preact';
import { useState } from 'preact/hooks';

import Dropin from '../../../../src/components/Dropin';
import Card from '../../../../src/components/Card';
import PayPal from '../../../../src/components/PayPal';

import { Checkout } from '../../Checkout';
import { ComponentContainer } from '../../ComponentContainer';
import { GuestShopperForm } from './components/GuestShopperForm';
import './components/FastlaneStory.scss';

import { GlobalStoryProps } from '../../types';

interface Props {
checkoutConfig: GlobalStoryProps;
}

export const FastlaneInSinglePageApp = ({ checkoutConfig }: Props) => {
const [fastlaneData, setFastlaneData] = useState<any>(null);

const handleOnCheckoutStep = (fastlaneSdk, fastlaneData) => {
setFastlaneData(fastlaneData);
};

if (!fastlaneData) {
return <GuestShopperForm onCheckoutStep={handleOnCheckoutStep} />;
}

return (
<Checkout checkoutConfig={checkoutConfig}>
{checkout => (
<ComponentContainer element={new Dropin(checkout, { showStoredPaymentMethods: false, paymentMethodComponents: [Card, PayPal] })} />
)}
</Checkout>
);
};
Loading

0 comments on commit 697fae5

Please sign in to comment.