From df55375a0e166365bcf41c994db053338f494f1a Mon Sep 17 00:00:00 2001 From: antoniof Date: Tue, 5 Sep 2023 16:34:20 +0200 Subject: [PATCH 1/5] updates state with storedetails --- .../lib/src/components/Giftcard/Giftcard.tsx | 6 +++- .../Giftcard/components/GiftcardComponent.tsx | 22 +++++++++++++-- .../components/Giftcard/components/types.ts | 1 + .../MealVoucherFR/MealVoucherFR.tsx | 28 +++++++++++++------ .../internal/StoreDetails/StoreDetails.tsx | 7 ++++- .../PaymentMethodsResponse/filters.ts | 2 +- packages/lib/src/index.ts | 2 +- 7 files changed, 53 insertions(+), 15 deletions(-) diff --git a/packages/lib/src/components/Giftcard/Giftcard.tsx b/packages/lib/src/components/Giftcard/Giftcard.tsx index 1b8133e442..c8dfa8b9d3 100644 --- a/packages/lib/src/components/Giftcard/Giftcard.tsx +++ b/packages/lib/src/components/Giftcard/Giftcard.tsx @@ -34,6 +34,10 @@ export class GiftcardElement extends UIElement { }; } + formatBalanceCheckData(): GiftCardElementData { + return this.formatData(); + } + get isValid() { return !!this.state.isValid; } @@ -92,7 +96,7 @@ export class GiftcardElement extends UIElement { this.setStatus('loading'); - this.handleBalanceCheck(this.formatData()) + this.handleBalanceCheck(this.formatBalanceCheckData()) .then(({ balance, transactionLimit = {} as PaymentAmount }) => { if (!balance) throw new Error('card-error'); // card doesn't exist if (balance?.currency !== this.props.amount?.currency) throw new Error('currency-error'); diff --git a/packages/lib/src/components/Giftcard/components/GiftcardComponent.tsx b/packages/lib/src/components/Giftcard/components/GiftcardComponent.tsx index 1582107422..60f171f4ff 100644 --- a/packages/lib/src/components/Giftcard/components/GiftcardComponent.tsx +++ b/packages/lib/src/components/Giftcard/components/GiftcardComponent.tsx @@ -7,6 +7,8 @@ import { PaymentAmount } from '../../../types'; import { GIFT_CARD } from '../../internal/SecuredFields/lib/configuration/constants'; import { GiftCardFields } from './GiftcardFields'; import { GiftcardFieldsProps } from './types'; +import StoreDetails from '../../internal/StoreDetails'; +import RedirectButton from '../../internal/RedirectButton'; interface GiftcardComponentProps { onChange: (state) => void; @@ -45,13 +47,19 @@ class Giftcard extends Component { public sfp; - public onChange = sfpState => { + public handleSecureFieldsChange = sfpState => { this.props.onChange({ data: sfpState.data, isValid: sfpState.isSfpValid }); }; + public handleOnStoreDetails = storedDetails => { + this.props.onChange({ + storePaymentMethod: storedDetails + }); + }; + public showValidation = () => { this.sfp.showValidation(); }; @@ -85,6 +93,10 @@ class Giftcard extends Component { return ; } + if (props.storedPaymentMethodId) { + return ; + } + const getCardErrorMessage = sfpState => { if (sfpState.errors.encryptedCardNumber) return i18n.get(sfpState.errors.encryptedCardNumber); @@ -109,7 +121,7 @@ class Giftcard extends Component { ref={ref => { this.sfp = ref; }} - onChange={this.onChange} + onChange={this.handleSecureFieldsChange} onFocus={this.handleFocus} type={GIFT_CARD} render={({ setRootNode, setFocusOn }, sfpState) => @@ -120,11 +132,15 @@ class Giftcard extends Component { getCardErrorMessage: getCardErrorMessage, setRootNode: setRootNode, setFocusOn: setFocusOn, - sfpState: sfpState + sfpState: sfpState, + // TODO maybe remove this? + ...props }) } /> + {props.enableStoreDetails && } + {this.props.showPayButton && this.props.payButton({ status: this.state.status, diff --git a/packages/lib/src/components/Giftcard/components/types.ts b/packages/lib/src/components/Giftcard/components/types.ts index 3fe7bedfc8..ba5ff14042 100644 --- a/packages/lib/src/components/Giftcard/components/types.ts +++ b/packages/lib/src/components/Giftcard/components/types.ts @@ -10,6 +10,7 @@ export type GiftcardFieldsProps = { focusedElement; setFocusOn; label?: string; + enableStoreDetails?: boolean; }; export type GiftcardFieldProps = { diff --git a/packages/lib/src/components/MealVoucherFR/MealVoucherFR.tsx b/packages/lib/src/components/MealVoucherFR/MealVoucherFR.tsx index 3b37f3094a..e67019316a 100644 --- a/packages/lib/src/components/MealVoucherFR/MealVoucherFR.tsx +++ b/packages/lib/src/components/MealVoucherFR/MealVoucherFR.tsx @@ -9,6 +9,7 @@ export class MealVoucherFRElement extends GiftcardElement { ...props, pinRequired: true, expiryDateRequired: true, + enableStoreDetails: true, fieldsLayoutComponent: MealVoucherFields }); } @@ -20,19 +21,30 @@ export class MealVoucherFRElement extends GiftcardElement { }; } + private formatPaymentMethod() { + return { + type: this.constructor['type'], + brand: this.props.brand, + encryptedCardNumber: this.state.data?.encryptedCardNumber, + encryptedSecurityCode: this.state.data?.encryptedSecurityCode, + encryptedExpiryMonth: this.state.data?.encryptedExpiryMonth, + encryptedExpiryYear: this.state.data?.encryptedExpiryYear + }; + } + /** * Formats the component data output */ formatData() { return { - paymentMethod: { - type: this.constructor['type'], - brand: this.props.brand, - encryptedCardNumber: this.state.data?.encryptedCardNumber, - encryptedSecurityCode: this.state.data?.encryptedSecurityCode, - encryptedExpiryMonth: this.state.data?.encryptedExpiryMonth, - encryptedExpiryYear: this.state.data?.encryptedExpiryYear - } + paymentMethod: this.formatPaymentMethod(), + ...(this.state.storePaymentMethod && { storePaymentMethod: this.state.storePaymentMethod }) + }; + } + + formatBalanceCheckData() { + return { + paymentMethod: this.formatPaymentMethod() }; } } diff --git a/packages/lib/src/components/internal/StoreDetails/StoreDetails.tsx b/packages/lib/src/components/internal/StoreDetails/StoreDetails.tsx index 421b5dc5fc..38427c1b25 100644 --- a/packages/lib/src/components/internal/StoreDetails/StoreDetails.tsx +++ b/packages/lib/src/components/internal/StoreDetails/StoreDetails.tsx @@ -3,10 +3,15 @@ import { h } from 'preact'; import useCoreContext from '../../../core/Context/useCoreContext'; import Checkbox from '../FormFields/Checkbox'; +interface StoreDetailsProps { + storeDetails?: boolean; + onChange: any; +} + /** * "Store details" generic checkbox */ -function StoreDetails({ storeDetails = false, ...props }) { +function StoreDetails({ storeDetails = false, ...props }: StoreDetailsProps) { const { i18n } = useCoreContext(); const [value, setValue] = useState(storeDetails); diff --git a/packages/lib/src/core/ProcessResponse/PaymentMethodsResponse/filters.ts b/packages/lib/src/core/ProcessResponse/PaymentMethodsResponse/filters.ts index c73245f676..a966c21991 100644 --- a/packages/lib/src/core/ProcessResponse/PaymentMethodsResponse/filters.ts +++ b/packages/lib/src/core/ProcessResponse/PaymentMethodsResponse/filters.ts @@ -10,7 +10,7 @@ export function filterEcomStoredPaymentMethods(pm) { return !!pm && !!pm.supportedShopperInteractions && pm.supportedShopperInteractions.includes('Ecommerce'); } -const supportedStoredPaymentMethods = ['scheme', 'blik', 'twint', 'ach', 'cashapp']; +const supportedStoredPaymentMethods = ['scheme', 'blik', 'twint', 'ach', 'cashapp', 'mealVoucher_FR']; export function filterSupportedStoredPaymentMethods(pm) { return !!pm && !!pm.type && supportedStoredPaymentMethods.includes(pm.type); diff --git a/packages/lib/src/index.ts b/packages/lib/src/index.ts index b8ac2f7850..dfb40e083c 100644 --- a/packages/lib/src/index.ts +++ b/packages/lib/src/index.ts @@ -2,7 +2,7 @@ if (process.env.NODE_ENV === 'development') { // Must use require here as import statements are only allowed // to exist at the top of a file. - // require('preact/debug'); + require('preact/debug'); } import { CoreOptions } from './core/types'; From c55c6a28d37ac4f3d700905910ef54a7b6d4193c Mon Sep 17 00:00:00 2001 From: antoniof Date: Wed, 8 Nov 2023 15:10:47 +0100 Subject: [PATCH 2/5] fix: update payment name and supported filter --- .../lib/src/components/Giftcard/Giftcard.tsx | 9 +++++++++ .../Giftcard/components/GiftcardComponent.tsx | 19 +++++++++++++++---- .../PaymentMethodsResponse/filters.ts | 11 ++++++++++- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/packages/lib/src/components/Giftcard/Giftcard.tsx b/packages/lib/src/components/Giftcard/Giftcard.tsx index c8dfa8b9d3..384ade1625 100644 --- a/packages/lib/src/components/Giftcard/Giftcard.tsx +++ b/packages/lib/src/components/Giftcard/Giftcard.tsx @@ -39,6 +39,10 @@ export class GiftcardElement extends UIElement { } get isValid() { + if (this.props.storedPaymentMethodId) { + return true; + } + return !!this.state.isValid; } @@ -47,6 +51,11 @@ export class GiftcardElement extends UIElement { } get displayName() { + if (this.props.storedPaymentMethodId && this.props.lastFour) { + // this applies for MealVoucher since it has the logic for lastFour + return `•••• ${this.props.lastFour}`; + } + return this.props.brandsConfiguration[this.props.brand]?.name || this.props.name; } diff --git a/packages/lib/src/components/Giftcard/components/GiftcardComponent.tsx b/packages/lib/src/components/Giftcard/components/GiftcardComponent.tsx index 60f171f4ff..93c183b080 100644 --- a/packages/lib/src/components/Giftcard/components/GiftcardComponent.tsx +++ b/packages/lib/src/components/Giftcard/components/GiftcardComponent.tsx @@ -8,7 +8,6 @@ import { GIFT_CARD } from '../../internal/SecuredFields/lib/configuration/consta import { GiftCardFields } from './GiftcardFields'; import { GiftcardFieldsProps } from './types'; import StoreDetails from '../../internal/StoreDetails'; -import RedirectButton from '../../internal/RedirectButton'; interface GiftcardComponentProps { onChange: (state) => void; @@ -20,10 +19,14 @@ interface GiftcardComponentProps { amount: PaymentAmount; showPayButton?: boolean; payButton: (config) => any; + brand: string; pinRequired: boolean; expiryDateRequired?: boolean; fieldsLayoutComponent: FunctionComponent; + + enableStoreDetails: boolean; + storedPaymentMethodId: string; } class Giftcard extends Component { @@ -61,7 +64,10 @@ class Giftcard extends Component { }; public showValidation = () => { - this.sfp.showValidation(); + // in case it's a stored gift card (stored mealvoucher) there will be no SFP + if (this.sfp) { + this.sfp.showValidation(); + } }; setStatus(status) { @@ -83,7 +89,7 @@ class Giftcard extends Component { this.setState({ balance, transactionLimit }); }; - render(props, { focusedElement, balance, transactionLimit }) { + render(props: GiftcardComponentProps, { focusedElement, balance, transactionLimit }) { const { i18n } = useCoreContext(); const transactionAmount = transactionLimit?.value < balance?.value ? transactionLimit : balance; @@ -94,7 +100,12 @@ class Giftcard extends Component { } if (props.storedPaymentMethodId) { - return ; + return this.props.payButton({ + status: this.state.status, + onClick: this.props.onBalanceCheck, + label: i18n.get('applyGiftcard'), + classNameModifiers: ['standalone'] + }); } const getCardErrorMessage = sfpState => { diff --git a/packages/lib/src/core/ProcessResponse/PaymentMethodsResponse/filters.ts b/packages/lib/src/core/ProcessResponse/PaymentMethodsResponse/filters.ts index a966c21991..bc1ba5b090 100644 --- a/packages/lib/src/core/ProcessResponse/PaymentMethodsResponse/filters.ts +++ b/packages/lib/src/core/ProcessResponse/PaymentMethodsResponse/filters.ts @@ -10,7 +10,16 @@ export function filterEcomStoredPaymentMethods(pm) { return !!pm && !!pm.supportedShopperInteractions && pm.supportedShopperInteractions.includes('Ecommerce'); } -const supportedStoredPaymentMethods = ['scheme', 'blik', 'twint', 'ach', 'cashapp', 'mealVoucher_FR']; +const supportedStoredPaymentMethods = [ + 'scheme', + 'blik', + 'twint', + 'ach', + 'cashapp', + 'mealVoucher_FR_groupeup', + 'mealVoucher_FR_sodexo', + 'mealVoucher_FR_natixis' +]; export function filterSupportedStoredPaymentMethods(pm) { return !!pm && !!pm.type && supportedStoredPaymentMethods.includes(pm.type); From 6a8135d4a5dd3c22b2126a5a2244ab55a15ba2c4 Mon Sep 17 00:00:00 2001 From: antoniof Date: Wed, 8 Nov 2023 15:31:06 +0100 Subject: [PATCH 3/5] chore: add changeset --- .changeset/neat-tigers-sniff.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/neat-tigers-sniff.md diff --git a/.changeset/neat-tigers-sniff.md b/.changeset/neat-tigers-sniff.md new file mode 100644 index 0000000000..8b6249a05c --- /dev/null +++ b/.changeset/neat-tigers-sniff.md @@ -0,0 +1,5 @@ +--- +'@adyen/adyen-web': minor +--- + +Feature: Allow to store French MealVoucher cards From 330a3447a647a320e6d54a4dff65bbad9dedb25a Mon Sep 17 00:00:00 2001 From: antoniof Date: Wed, 22 Nov 2023 11:48:34 +0100 Subject: [PATCH 4/5] wip: display cvc when stored --- .../Giftcard/components/GiftcardComponent.tsx | 43 ++++++++++----- .../components/StoredGiftCardFields.tsx | 52 +++++++++++++++++++ 2 files changed, 82 insertions(+), 13 deletions(-) create mode 100644 packages/lib/src/components/Giftcard/components/StoredGiftCardFields.tsx diff --git a/packages/lib/src/components/Giftcard/components/GiftcardComponent.tsx b/packages/lib/src/components/Giftcard/components/GiftcardComponent.tsx index 93c183b080..c0d389afe1 100644 --- a/packages/lib/src/components/Giftcard/components/GiftcardComponent.tsx +++ b/packages/lib/src/components/Giftcard/components/GiftcardComponent.tsx @@ -8,6 +8,7 @@ import { GIFT_CARD } from '../../internal/SecuredFields/lib/configuration/consta import { GiftCardFields } from './GiftcardFields'; import { GiftcardFieldsProps } from './types'; import StoreDetails from '../../internal/StoreDetails'; +import { StoredGiftCardFields } from './StoredGiftCardFields'; interface GiftcardComponentProps { onChange: (state) => void; @@ -27,6 +28,8 @@ interface GiftcardComponentProps { enableStoreDetails: boolean; storedPaymentMethodId: string; + expiryMonth?: number; + expiryYear?: number; } class Giftcard extends Component { @@ -99,15 +102,6 @@ class Giftcard extends Component { return ; } - if (props.storedPaymentMethodId) { - return this.props.payButton({ - status: this.state.status, - onClick: this.props.onBalanceCheck, - label: i18n.get('applyGiftcard'), - classNameModifiers: ['standalone'] - }); - } - const getCardErrorMessage = sfpState => { if (sfpState.errors.encryptedCardNumber) return i18n.get(sfpState.errors.encryptedCardNumber); @@ -135,8 +129,31 @@ class Giftcard extends Component { onChange={this.handleSecureFieldsChange} onFocus={this.handleFocus} type={GIFT_CARD} - render={({ setRootNode, setFocusOn }, sfpState) => - this.props.fieldsLayoutComponent({ + render={({ setRootNode, setFocusOn }, sfpState) => { + if (props.storedPaymentMethodId) { + // return this.props.payButton({ + // status: this.state.status, + // onClick: this.props.onBalanceCheck, + // label: i18n.get('applyGiftcard'), + // classNameModifiers: ['standalone'] + // }); + return ( + + ); + } + + return this.props.fieldsLayoutComponent({ i18n: i18n, pinRequired: this.props.pinRequired, focusedElement: focusedElement, @@ -146,8 +163,8 @@ class Giftcard extends Component { sfpState: sfpState, // TODO maybe remove this? ...props - }) - } + }); + }} /> {props.enableStoreDetails && } diff --git a/packages/lib/src/components/Giftcard/components/StoredGiftCardFields.tsx b/packages/lib/src/components/Giftcard/components/StoredGiftCardFields.tsx new file mode 100644 index 0000000000..780075e383 --- /dev/null +++ b/packages/lib/src/components/Giftcard/components/StoredGiftCardFields.tsx @@ -0,0 +1,52 @@ +import { h } from 'preact'; +import { GiftcardPinField } from './GiftcardPinField'; +import { GiftcardFieldsProps } from './types'; +import useCoreContext from '../../../core/Context/useCoreContext'; +import Field from '../../internal/FormFields/Field'; +import InputText from '../../internal/FormFields/InputText'; + +interface StoredGiftCardFieldsProps extends GiftcardFieldsProps { + expiryMonth: number; + expiryYear: number; +} + +export const StoredGiftCardFields = (props: Readonly) => { + const { pinRequired, expiryMonth, expiryYear } = props; + const { i18n } = useCoreContext(); + // const storedCardDescription = i18n.get('creditCard.storedCard.description.ariaLabel').replace('%@', lastFour); + // const storedCardDescriptionSuffix = + // expiryMonth && expiryYear ? ` ${i18n.get('creditCard.expiryDateField.title')} ${expiryMonth}/${expiryYear}` : ''; + // const ariaLabel = `${storedCardDescription}${storedCardDescriptionSuffix}`; + + // const getError = (errors, fieldType) => { + // const errorMessage = errors[fieldType] ? i18n.get(errors[fieldType]) : null; + // return errorMessage; + // }; + + return ( + // TODO missing aria-label +
+
+ {expiryMonth && expiryYear && ( + + + + )} + {pinRequired && } +
+
+ ); +}; From 099cb62b5b892a364bcac45cfa0c70500d368906 Mon Sep 17 00:00:00 2001 From: antoniof Date: Fri, 19 Jan 2024 17:18:22 +0100 Subject: [PATCH 5/5] fix: setRootNode in stored giftcard --- packages/lib/src/components/Giftcard/Giftcard.tsx | 8 ++++---- .../Giftcard/components/StoredGiftCardFields.tsx | 4 ++-- .../lib/src/components/MealVoucherFR/MealVoucherFR.tsx | 3 ++- packages/playground/src/config/paymentsConfig.js | 3 ++- packages/playground/src/services.js | 9 +++++---- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/lib/src/components/Giftcard/Giftcard.tsx b/packages/lib/src/components/Giftcard/Giftcard.tsx index 384ade1625..5ef8eeda8e 100644 --- a/packages/lib/src/components/Giftcard/Giftcard.tsx +++ b/packages/lib/src/components/Giftcard/Giftcard.tsx @@ -29,7 +29,8 @@ export class GiftcardElement extends UIElement { type: this.constructor['type'], brand: this.props.brand, encryptedCardNumber: this.state.data?.encryptedCardNumber, - encryptedSecurityCode: this.state.data?.encryptedSecurityCode + encryptedSecurityCode: this.state.data?.encryptedSecurityCode, + ...(this.props.storedPaymentMethodId && { storedPaymentMethodId: this.props.storedPaymentMethodId }) } }; } @@ -104,13 +105,12 @@ export class GiftcardElement extends UIElement { } this.setStatus('loading'); - this.handleBalanceCheck(this.formatBalanceCheckData()) - .then(({ balance, transactionLimit = {} as PaymentAmount }) => { + .then(({ balance, transactionLimit = {} as PaymentAmount }: { balance: PaymentAmount; transactionLimit: PaymentAmount }) => { if (!balance) throw new Error('card-error'); // card doesn't exist if (balance?.currency !== this.props.amount?.currency) throw new Error('currency-error'); if (balance?.value <= 0) throw new Error('no-balance'); - + debugger; this.componentRef.setBalance({ balance, transactionLimit }); if (this.props.amount.value > balance.value || this.props.amount.value > transactionLimit.value) { diff --git a/packages/lib/src/components/Giftcard/components/StoredGiftCardFields.tsx b/packages/lib/src/components/Giftcard/components/StoredGiftCardFields.tsx index 780075e383..5c3c46122f 100644 --- a/packages/lib/src/components/Giftcard/components/StoredGiftCardFields.tsx +++ b/packages/lib/src/components/Giftcard/components/StoredGiftCardFields.tsx @@ -11,7 +11,7 @@ interface StoredGiftCardFieldsProps extends GiftcardFieldsProps { } export const StoredGiftCardFields = (props: Readonly) => { - const { pinRequired, expiryMonth, expiryYear } = props; + const { setRootNode, pinRequired, expiryMonth, expiryYear } = props; const { i18n } = useCoreContext(); // const storedCardDescription = i18n.get('creditCard.storedCard.description.ariaLabel').replace('%@', lastFour); // const storedCardDescriptionSuffix = @@ -26,7 +26,7 @@ export const StoredGiftCardFields = (props: Readonly) return ( // TODO missing aria-label
-
+
{expiryMonth && expiryYear && ( export const makePayment = (data, config = {}) => { // NOTE: Merging data object. DO NOT do this in production. const paymentRequest = { ...paymentsConfig, ...config, ...data }; - if (paymentRequest.order) { - delete paymentRequest.amount; - } + // if (paymentRequest.order) { + // delete paymentRequest.amount; + // } return httpPost('payments', paymentRequest) .then(response => { if (response.error) throw 'Payment initiation failed'; @@ -49,7 +49,8 @@ export const getOriginKey = (originKeyOrigin = document.location.origin) => httpPost('originKeys', { originDomains: [originKeyOrigin] }).then(response => response.originKeys[originKeyOrigin]); export const checkBalance = data => { - return httpPost('paymentMethods/balance', data) + const payload = { amount: paymentsConfig.amount, ...data }; + return httpPost('paymentMethods/balance', payload) .then(response => { if (response.error) throw 'Balance call failed'; return response;