diff --git a/.changeset/shaggy-poets-eat.md b/.changeset/shaggy-poets-eat.md
new file mode 100644
index 0000000000..7f408215bd
--- /dev/null
+++ b/.changeset/shaggy-poets-eat.md
@@ -0,0 +1,5 @@
+---
+'@adyen/adyen-web': patch
+---
+
+Payconic - Adjusted QR code message and removed unused button label.
diff --git a/.changeset/sharp-cars-clean.md b/.changeset/sharp-cars-clean.md
new file mode 100644
index 0000000000..981e4d45d1
--- /dev/null
+++ b/.changeset/sharp-cars-clean.md
@@ -0,0 +1,5 @@
+---
+'@adyen/adyen-web': minor
+---
+
+PayMe - Improved instructions UI
diff --git a/.changeset/tricky-pets-tap.md b/.changeset/tricky-pets-tap.md
new file mode 100644
index 0000000000..a857cfe4d6
--- /dev/null
+++ b/.changeset/tricky-pets-tap.md
@@ -0,0 +1,5 @@
+---
+'@adyen/adyen-web': minor
+---
+
+PayNow - Adding instructions to scan QR code on mobile view
diff --git a/packages/lib/src/components/BcmcMobile/BcmcMobile.ts b/packages/lib/src/components/BcmcMobile/BcmcMobile.ts
index c240756d48..178ed233d5 100644
--- a/packages/lib/src/components/BcmcMobile/BcmcMobile.ts
+++ b/packages/lib/src/components/BcmcMobile/BcmcMobile.ts
@@ -7,12 +7,10 @@ class BCMCMobileElement extends QRLoaderContainer {
public static txVariants = [TxVariants.bcmc_mobile, TxVariants.bcmc_mobile_QR];
formatProps(props) {
- const isMobile = window.matchMedia('(max-width: 768px)').matches && /Android|iPhone|iPod/.test(navigator.userAgent);
-
return {
delay: STATUS_INTERVAL,
countdownTime: COUNTDOWN_MINUTES,
- buttonLabel: isMobile ? 'openApp' : 'generateQRCode',
+ timeToPay: 'payme.timeToPay',
...super.formatProps(props)
};
}
diff --git a/packages/lib/src/components/PayMe/Instructions.scss b/packages/lib/src/components/PayMe/Instructions.scss
deleted file mode 100644
index d45cfb9100..0000000000
--- a/packages/lib/src/components/PayMe/Instructions.scss
+++ /dev/null
@@ -1,15 +0,0 @@
-@import 'styles/variable-generator';
-
-.adyen-checkout-payme-instructions {
- font-size: token(text-body-font-size);
- text-align: center;
- color: token(color-label-secondary);
- line-height: token(text-body-line-height);
-
- &__steps {
- list-style-position: inside;
- padding-inline-start: 0;
- margin: token(spacer-070) 0;
- padding-bottom: token(spacer-040);
- }
-}
diff --git a/packages/lib/src/components/PayMe/Instructions.test.tsx b/packages/lib/src/components/PayMe/Instructions.test.tsx
deleted file mode 100644
index f7758d1a1a..0000000000
--- a/packages/lib/src/components/PayMe/Instructions.test.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { render, screen } from '@testing-library/preact';
-import { h } from 'preact';
-import { CoreProvider } from '../../core/Context/CoreProvider';
-import Instructions from './Instructions';
-
-describe('Instructions', () => {
- const customRender = (ui: h.JSX.Element) => {
- return render(
-
- {ui}
-
- );
- };
-
- test('should see a list of instructions and footnote', async () => {
- customRender();
- expect(await screen.findByText('Open the PayMe app', { exact: false })).toBeInTheDocument();
- expect(await screen.findByText('Scan the QR code', { exact: false })).toBeInTheDocument();
- expect(await screen.findByText('Complete the payment in the app', { exact: false })).toBeInTheDocument();
- expect(await screen.findByText('Please do not close this page before the payment is completed', { exact: false })).toBeInTheDocument();
- });
-});
diff --git a/packages/lib/src/components/PayMe/Instructions.tsx b/packages/lib/src/components/PayMe/Instructions.tsx
deleted file mode 100644
index 6311e67d19..0000000000
--- a/packages/lib/src/components/PayMe/Instructions.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { useCoreContext } from '../../core/Context/CoreProvider';
-import { h } from 'preact';
-import './Instructions.scss';
-
-export default function Instructions() {
- const { i18n } = useCoreContext();
- const steps = i18n.get('payme.instructions.steps');
- const footnote = i18n.get('payme.instructions.footnote');
-
- return (
-
-
- {steps.split('%@').map((step, index) => (
- - {step}
- ))}
-
-
{footnote}
-
- );
-}
diff --git a/packages/lib/src/components/PayMe/PayMe.ts b/packages/lib/src/components/PayMe/PayMe.ts
index f98a434008..09d60bd7f2 100644
--- a/packages/lib/src/components/PayMe/PayMe.ts
+++ b/packages/lib/src/components/PayMe/PayMe.ts
@@ -1,5 +1,6 @@
import QRLoaderContainer from '../helpers/QRLoaderContainer';
-import Instructions from './Instructions';
+import { PayMeInstructions } from './components/PayMeInstructions';
+import { PayMeIntroduction } from './components/PayMeIntroduction';
class PayMeElement extends QRLoaderContainer {
public static type = 'payme';
@@ -11,10 +12,10 @@ class PayMeElement extends QRLoaderContainer {
delay: PayMeElement.defaultDelay,
countdownTime: PayMeElement.defaultCountdown,
redirectIntroduction: 'payme.openPayMeApp',
- introduction: 'payme.scanQrCode',
timeToPay: 'payme.timeToPay',
buttonLabel: 'payme.redirectButtonLabel',
- instructions: Instructions,
+ introduction: PayMeIntroduction,
+ instructions: PayMeInstructions,
...super.formatProps(props)
};
}
diff --git a/packages/lib/src/components/PayMe/components/PayMeInstructions.tsx b/packages/lib/src/components/PayMe/components/PayMeInstructions.tsx
new file mode 100644
index 0000000000..2f2074f376
--- /dev/null
+++ b/packages/lib/src/components/PayMe/components/PayMeInstructions.tsx
@@ -0,0 +1,23 @@
+import { useIsMobile } from '../../../utils/useIsMobile';
+import { useCoreContext } from '../../../core/Context/CoreProvider';
+import { Timeline, TimelineWrapper } from '../../internal/Timeline';
+import { h } from 'preact';
+
+const PayMeInstructions = () => {
+ const { i18n } = useCoreContext();
+ const { isMobileScreenSize } = useIsMobile();
+
+ if (isMobileScreenSize) {
+ return null;
+ }
+
+ const instructions = i18n.get('payme.instructions.steps').split('%@');
+
+ return (
+
+
+
+ );
+};
+
+export { PayMeInstructions };
diff --git a/packages/lib/src/components/PayMe/components/PayMeIntroduction.tsx b/packages/lib/src/components/PayMe/components/PayMeIntroduction.tsx
new file mode 100644
index 0000000000..296a0c2c9b
--- /dev/null
+++ b/packages/lib/src/components/PayMe/components/PayMeIntroduction.tsx
@@ -0,0 +1,21 @@
+import { useCoreContext } from '../../../core/Context/CoreProvider';
+import { useIsMobile } from '../../../utils/useIsMobile';
+import { Timeline, TimelineWrapper } from '../../internal/Timeline';
+import { Fragment, h } from 'preact';
+
+const PayMeIntroduction = () => {
+ const { i18n } = useCoreContext();
+ const { isMobileScreenSize } = useIsMobile();
+
+ const instructions = i18n.get('payme.instructions.steps').split('%@');
+
+ return isMobileScreenSize ? (
+
+
+
+ ) : (
+ {i18n.get('payme.scanQrCode')}
+ );
+};
+
+export { PayMeIntroduction };
diff --git a/packages/lib/src/components/PayNow/PayNow.test.ts b/packages/lib/src/components/PayNow/PayNow.test.ts
index 8027f53e9f..bc58e748f6 100644
--- a/packages/lib/src/components/PayNow/PayNow.test.ts
+++ b/packages/lib/src/components/PayNow/PayNow.test.ts
@@ -1,4 +1,11 @@
import PayNow from './PayNow';
+import { render, screen, within } from '@testing-library/preact';
+import { mock } from 'jest-mock-extended';
+import { Resources } from '../../core/Context/Resources';
+import checkPaymentStatus from '../../core/Services/payment-status';
+import { SRPanel } from '../../core/Errors/SRPanel';
+
+jest.mock('../../core/Services/payment-status');
describe('PayNow', () => {
describe('isValid', () => {
@@ -21,4 +28,53 @@ describe('PayNow', () => {
expect(paynow.render()).not.toBe(null);
});
});
+
+ test('should render mobile instructions', async () => {
+ // Mocks matchMedia to return 'matches: true' when checking (max-width: 1024px)
+ Object.defineProperty(window, 'matchMedia', {
+ writable: true,
+ value: jest.fn().mockImplementation(() => ({
+ matches: true
+ }))
+ });
+
+ jest.useFakeTimers();
+
+ const srPanel = mock();
+ const resources = mock();
+ resources.getImage.mockReturnValue((icon: string) => `https://checkout-adyen.com/${icon}`);
+
+ // @ts-ignore mockResolvedValue not inferred
+ checkPaymentStatus.mockResolvedValue({
+ payload: 'Ab02b4c0!BQABAgBLLk9evMb+rScNdE...',
+ resultCode: 'pending',
+ type: 'complete'
+ });
+
+ const paynow = new PayNow(global.core, {
+ loadingContext: 'checkoutshopper.com/',
+ modules: { resources, analytics: global.analytics, srPanel },
+ i18n: global.i18n,
+ paymentData: 'Ab02b4c0!BQABAgBH1f8hqfFxOvbfK..',
+ qrCodeImage: '',
+ paymentMethodType: 'paynow',
+ qrCodeData: '00020126580009SG...'
+ });
+
+ render(paynow.mount('body'));
+
+ // Triggers the execution of the setTimeout that makes the /status API request
+ jest.runAllTimers();
+
+ await screen.findAllByText(/Scan the QR code using the PayNow app to complete the payment/);
+
+ const div = within(screen.queryByTestId('paynow-introduction'));
+ div.getByText(/Take a screenshot of the QR code./);
+ div.getByText(/Open the PayNow bank or payment app./);
+ div.getByText(/Select the option to scan a QR code./);
+ div.getByText(/Choose the option to upload a QR and select the screenshot./);
+ div.getByText(/Complete the transaction./);
+
+ jest.resetAllMocks();
+ });
});
diff --git a/packages/lib/src/components/PayNow/PayNow.ts b/packages/lib/src/components/PayNow/PayNow.ts
index e52ccfff21..d3db9050bb 100644
--- a/packages/lib/src/components/PayNow/PayNow.ts
+++ b/packages/lib/src/components/PayNow/PayNow.ts
@@ -1,12 +1,17 @@
import QRLoaderContainer from '../helpers/QRLoaderContainer/QRLoaderContainer';
import { delay, countdownTime } from './config';
import { TxVariants } from '../tx-variants';
+import { PayNowIntroduction } from './components/PayNowIntroduction';
+import { PayNowInstructions } from './components/PayNowInstructions';
class PayNowElement extends QRLoaderContainer {
public static type = TxVariants.paynow;
formatProps(props) {
return {
+ introduction: PayNowIntroduction,
+ instructions: PayNowInstructions,
+ timeToPay: 'payme.timeToPay',
delay,
countdownTime,
...super.formatProps(props)
diff --git a/packages/lib/src/components/PayNow/components/PayNowInstructions.scss b/packages/lib/src/components/PayNow/components/PayNowInstructions.scss
new file mode 100644
index 0000000000..174ae7ff5e
--- /dev/null
+++ b/packages/lib/src/components/PayNow/components/PayNowInstructions.scss
@@ -0,0 +1,13 @@
+@import 'styles/variable-generator';
+
+.adyen-checkout-paynow__instructions {
+ font-size: token(text-body-font-size);
+ font-weight: token(text-body-font-weight);
+ line-height: token(text-body-line-height);
+ color: token(color-label-primary);
+ text-align: center;
+}
+
+.adyen-checkout-paynow__instructions > p {
+ margin-bottom: 0;
+}
diff --git a/packages/lib/src/components/PayNow/components/PayNowInstructions.tsx b/packages/lib/src/components/PayNow/components/PayNowInstructions.tsx
new file mode 100644
index 0000000000..eeee0ac284
--- /dev/null
+++ b/packages/lib/src/components/PayNow/components/PayNowInstructions.tsx
@@ -0,0 +1,21 @@
+import { h } from 'preact';
+import { useCoreContext } from '../../../core/Context/CoreProvider';
+import ContentSeparator from '../../internal/ContentSeparator';
+import './PayNowInstructions.scss';
+import { useIsMobile } from '../../../utils/useIsMobile';
+
+const PayNowInstructions = () => {
+ const { i18n } = useCoreContext();
+ const { isMobileScreenSize } = useIsMobile();
+
+ if (!isMobileScreenSize) return;
+
+ return (
+
+
+
{i18n.get('paynow.scanQrCode')}
+
+ );
+};
+
+export { PayNowInstructions };
diff --git a/packages/lib/src/components/PayNow/components/PayNowIntroduction.scss b/packages/lib/src/components/PayNow/components/PayNowIntroduction.scss
new file mode 100644
index 0000000000..cdec4c3c63
--- /dev/null
+++ b/packages/lib/src/components/PayNow/components/PayNowIntroduction.scss
@@ -0,0 +1,9 @@
+@import 'styles/variable-generator';
+
+.adyen-checkout-paynow__introduction {
+ font-size: token(text-body-font-size);
+ font-weight: token(text-body-font-weight);
+ line-height: token(text-body-line-height);
+ color: token(color-label-primary);
+ text-align: center;
+}
diff --git a/packages/lib/src/components/PayNow/components/PayNowIntroduction.tsx b/packages/lib/src/components/PayNow/components/PayNowIntroduction.tsx
new file mode 100644
index 0000000000..2bd0e34080
--- /dev/null
+++ b/packages/lib/src/components/PayNow/components/PayNowIntroduction.tsx
@@ -0,0 +1,32 @@
+import { h } from 'preact';
+import { useCoreContext } from '../../../core/Context/CoreProvider';
+import './PayNowIntroduction.scss';
+import { TimelineWrapper, Timeline } from '../../internal/Timeline';
+import { useIsMobile } from '../../../utils/useIsMobile';
+
+const PayNowIntroduction = () => {
+ const { i18n } = useCoreContext();
+ const { isMobileScreenSize } = useIsMobile();
+
+ const instructions = [
+ i18n.get('paynow.mobileViewInstruction.step1'),
+ i18n.get('paynow.mobileViewInstruction.step2'),
+ i18n.get('paynow.mobileViewInstruction.step3'),
+ i18n.get('paynow.mobileViewInstruction.step4'),
+ i18n.get('paynow.mobileViewInstruction.step5')
+ ];
+
+ return (
+
+ {isMobileScreenSize ? (
+
+
+
+ ) : (
+ i18n.get('paynow.scanQrCode')
+ )}
+
+ );
+};
+
+export { PayNowIntroduction };
diff --git a/packages/lib/src/components/Swish/Swish.scss b/packages/lib/src/components/Swish/Swish.scss
new file mode 100644
index 0000000000..70c0024044
--- /dev/null
+++ b/packages/lib/src/components/Swish/Swish.scss
@@ -0,0 +1,3 @@
+.adyen-checkout__qr-loader--swish > .adyen-checkout__qr-loader__instructions {
+ text-align: center;
+}
diff --git a/packages/lib/src/components/Swish/Swish.ts b/packages/lib/src/components/Swish/Swish.ts
index 162186f72d..b7c6e6e7b9 100644
--- a/packages/lib/src/components/Swish/Swish.ts
+++ b/packages/lib/src/components/Swish/Swish.ts
@@ -1,5 +1,6 @@
import QRLoaderContainer from '../helpers/QRLoaderContainer/QRLoaderContainer';
import { TxVariants } from '../tx-variants';
+import './Swish.scss';
class SwishElement extends QRLoaderContainer {
public static type = TxVariants.swish;
diff --git a/packages/lib/src/components/helpers/QRLoaderContainer/types.ts b/packages/lib/src/components/helpers/QRLoaderContainer/types.ts
index 66de326ec4..5729ffd99c 100644
--- a/packages/lib/src/components/helpers/QRLoaderContainer/types.ts
+++ b/packages/lib/src/components/helpers/QRLoaderContainer/types.ts
@@ -16,10 +16,11 @@ export interface QRLoaderConfiguration extends UIElementProps {
brandLogo?: string;
buttonLabel?: string;
qrCodeImage?: string;
+ qrCodeData?: string;
paymentData?: string;
- introduction?: string;
redirectIntroduction?: string;
timeToPay?: string;
- instructions?: string | (() => h.JSX.Element);
copyBtn?: boolean;
+ introduction?: string | (() => h.JSX.Element);
+ instructions?: string | (() => h.JSX.Element);
}
diff --git a/packages/lib/src/components/internal/ContentSeparator/ContentSeparator.scss b/packages/lib/src/components/internal/ContentSeparator/ContentSeparator.scss
index faef810749..eaa5722e52 100644
--- a/packages/lib/src/components/internal/ContentSeparator/ContentSeparator.scss
+++ b/packages/lib/src/components/internal/ContentSeparator/ContentSeparator.scss
@@ -6,16 +6,17 @@
display: flex;
justify-content: center;
align-items: center;
- color: token(color-label-secondary);
+ color: token(color-label-primary);
white-space: nowrap;
- font-size: 13px;
+ text-transform: capitalize;
+ font-size: token(text-body-font-size);
line-height: token(text-caption-line-height);
&::after,
&::before {
content: '';
flex: 1;
- border-bottom: 1px solid token(color-outline-tertiary);
+ border-bottom: 1px solid token(color-separator-primary);
}
&::after {
diff --git a/packages/lib/src/components/internal/QRLoader/QRLoader.scss b/packages/lib/src/components/internal/QRLoader/QRLoader.scss
index e864b1847d..41b9fe5bd4 100644
--- a/packages/lib/src/components/internal/QRLoader/QRLoader.scss
+++ b/packages/lib/src/components/internal/QRLoader/QRLoader.scss
@@ -3,13 +3,17 @@
.adyen-checkout__qr-loader {
background: token(color-background-primary);
padding: token(spacer-110);
+ padding-bottom: token(spacer-090);
border: token(border-width-s) solid token(color-outline-secondary);
border-radius: token(border-radius-m);
- text-align: center;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
}
.adyen-checkout__qr-loader--result {
padding: 100px;
+ gap: token(spacer-090);
}
.adyen-checkout__qr-loader--app {
@@ -18,25 +22,24 @@
padding: 0;
}
+.adyen-checkout__qr-loader__brand-logo-wrapper {
+ border-radius: token(border-radius-s);
+ box-shadow: token(shadow-low);
+ margin-bottom: token(spacer-090);
+ overflow: hidden;
+}
+
.adyen-checkout__qr-loader__brand-logo {
- width: 74px;
- border-radius: 3px;
+ width: 80px;
+ display: block;
}
.adyen-checkout__qr-loader__subtitle {
max-width: 400px;
- margin: token(spacer-100) auto 0;
-}
-
-.adyen-checkout__qr-loader__subtitle--result {
- margin-bottom: token(spacer-100);
-}
-
-.adyen-checkout__qr-loader__subtitle,
-.adyen-checkout__qr-loader__payment_amount {
color: token(color-label-primary);
- font-size: token(text-subtitle-font-size);
- line-height: token(text-caption-line-height);
+ font-size: token(text-body-font-size);
+ line-height: token(text-body-line-height);
+ text-align: center;
}
.adyen-checkout__qr-loader__icon {
@@ -45,16 +48,20 @@
}
.adyen-checkout__qr-loader__payment_amount {
- font-weight: bold;
+ color: token(color-label-primary);
+ font-weight: token(text-title-l-font-weight);
+ font-size: token(text-title-l-font-size);
+ line-height: token(text-body-wide-line-height);
+ margin-bottom: token(spacer-090);
}
.adyen-checkout__qr-loader__progress {
height: token(spacer-020);
background: token(color-outline-secondary);
border-radius: token(border-radius-l);
- margin: token(spacer-100) auto token(spacer-060);
width: 152px;
padding-right: 3%;
+ margin-bottom: token(spacer-060);
[dir="rtl"] & {
padding-right: 0;
@@ -70,10 +77,11 @@
}
.adyen-checkout__qr-loader__countdown {
- color: token(color-label-secondary);
- font-size: token(text-body-font-size);
+ font-size: token(text-caption-font-size);
+ text-align: center;
}
+
.adyen-checkout__qr-loader > .adyen-checkout__spinner__wrapper {
margin: 60px 0;
}
@@ -88,10 +96,11 @@
}
.adyen-checkout__qr-loader__instructions {
+ max-width: 300px;
color: token(color-label-tertiary);
font-size: token(text-subtitle-font-size);
line-height: 1.5;
- margin-top: token(spacer-100);
+ margin-top: token(spacer-090);
}
.adyen-checkout__qr-loader__actions {
@@ -101,8 +110,9 @@
margin-top: token(spacer-100);
}
-@media only screen and (max-device-width: 1200px) {
+@media (max-width: 1024px) {
.adyen-checkout__qr-loader__app-link {
display: block;
+ min-width: 220px;
}
}
diff --git a/packages/lib/src/components/internal/QRLoader/QRLoader.tsx b/packages/lib/src/components/internal/QRLoader/QRLoader.tsx
index ba5b43c44d..0a10d5ce92 100644
--- a/packages/lib/src/components/internal/QRLoader/QRLoader.tsx
+++ b/packages/lib/src/components/internal/QRLoader/QRLoader.tsx
@@ -164,7 +164,7 @@ class QRLoader extends Component {
src={getImage({ imageFolder: 'components/' })(image)}
alt={status}
/>
- {status}
+ {status}
);
};
@@ -180,7 +180,11 @@ class QRLoader extends Component {
if (loading) {
return (
- {brandLogo &&
![{brandName}]({brandLogo})
}
+ {brandLogo && (
+
+
![{brandName}]({brandLogo})
+
+ )}
);
@@ -193,7 +197,11 @@ class QRLoader extends Component {
return (
- {brandLogo &&
![{brandName}]({brandLogo})
}
+ {brandLogo && (
+
+
![{brandName}]({brandLogo})
+
+ )}
{amount && amount.value && amount.currency && (
{i18n.amount(amount.value, amount.currency)}
@@ -211,7 +219,7 @@ class QRLoader extends Component
{
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}
- {i18n.get(this.props.introduction)}
+ {typeof this.props.introduction === 'string' ? i18n.get(this.props.introduction) : this.props.introduction?.()}
{
{timeToPayString[1]}
- {typeof this.props.instructions === 'string' ? (
- {i18n.get(this.props.instructions)}
- ) : (
- this.props.instructions?.()
+ {this.props.instructions && (
+
+ {typeof this.props.instructions === 'string' ? i18n.get(this.props.instructions) : this.props.instructions?.()}
+
)}
{this.props.copyBtn && (
diff --git a/packages/lib/src/components/internal/QRLoader/types.ts b/packages/lib/src/components/internal/QRLoader/types.ts
index 2431f89a4f..c86c49fdd5 100644
--- a/packages/lib/src/components/internal/QRLoader/types.ts
+++ b/packages/lib/src/components/internal/QRLoader/types.ts
@@ -23,9 +23,9 @@ export interface QRLoaderProps {
brandLogo?: string;
brandName?: string;
buttonLabel?: string;
- introduction?: string;
redirectIntroduction?: string;
timeToPay?: string;
+ introduction?: string | (() => h.JSX.Element);
instructions?: string | (() => h.JSX.Element);
copyBtn?: boolean;
onActionHandled?: (rtnObj: ActionHandledReturnObject) => void;
diff --git a/packages/lib/src/components/internal/Timeline/Timeline.scss b/packages/lib/src/components/internal/Timeline/Timeline.scss
new file mode 100644
index 0000000000..2091a468a3
--- /dev/null
+++ b/packages/lib/src/components/internal/Timeline/Timeline.scss
@@ -0,0 +1,73 @@
+@import 'styles/variable-generator';
+
+.adyen-checkout-timeline {
+ scrollbar-color: token(color-background-quaternary) transparent;
+}
+
+.adyen-checkout-timeline__items {
+ all: unset;
+}
+
+.adyen-checkout-timeline-item {
+ scrollbar-color: token(color-background-quaternary) transparent;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ list-style-type: none;
+ max-width: 500px;
+
+ &:last-child {
+ .adyen-checkout-timeline-item__separator {
+ display: none;
+ }
+
+ .adyen-checkout-timeline-item__content {
+ padding-bottom: 0;
+ }
+ }
+}
+
+.adyen-checkout-timeline-item__row {
+ display: flex;
+ gap: token(spacer-070);
+}
+
+.adyen-checkout-timeline-item__marker {
+ display: flex;
+ flex-flow: column;
+ width: 100%;
+ max-width: token(spacer-070);
+ margin-top: token(spacer-010);
+}
+
+.adyen-checkout-timeline-item__separator {
+ flex-grow: 1;
+ position: relative;
+
+ &::before {
+ background-color: token(color-separator-secondary);
+ content: "";
+ height: 100%;
+ left: 50%;
+ position: absolute;
+ transform: translate(-50%);
+ width: 1px;
+ }
+}
+
+.adyen-checkout-timeline-item__content {
+ align-items: flex-start;
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+ padding-bottom: token(spacer-060);
+}
+
+.adyen-checkout-timeline-item__title {
+ font-size: token(text-caption-font-size);
+ font-weight: token(text-body-font-weight);
+ line-height: token(text-body-line-height);
+ letter-spacing: 0;
+ text-align: left;
+ color: token(color-label-primary);
+ margin-bottom: token(spacer-010);
+}
diff --git a/packages/lib/src/components/internal/Timeline/Timeline.tsx b/packages/lib/src/components/internal/Timeline/Timeline.tsx
new file mode 100644
index 0000000000..14852c12d5
--- /dev/null
+++ b/packages/lib/src/components/internal/Timeline/Timeline.tsx
@@ -0,0 +1,39 @@
+import { h } from 'preact';
+import './Timeline.scss';
+
+interface Timeline {
+ instructions: string[];
+}
+
+const Timeline = ({ instructions }: Timeline) => {
+ if (!instructions || instructions.length === 0) {
+ return null;
+ }
+
+ return (
+
+
+ {instructions.map((value, index) => (
+ -
+
+
+ ))}
+
+
+ );
+};
+
+export { Timeline };
diff --git a/packages/lib/src/components/internal/Timeline/TimelineWrapper.scss b/packages/lib/src/components/internal/Timeline/TimelineWrapper.scss
new file mode 100644
index 0000000000..acecdc6e1c
--- /dev/null
+++ b/packages/lib/src/components/internal/Timeline/TimelineWrapper.scss
@@ -0,0 +1,8 @@
+@import 'styles/variable-generator';
+
+.adyen-checkout-timeline-wrapper {
+ padding: 16px;
+ border-radius: 8px;
+ max-width: 300px;
+ background-color: token(color-background-secondary);
+}
diff --git a/packages/lib/src/components/internal/Timeline/TimelineWrapper.tsx b/packages/lib/src/components/internal/Timeline/TimelineWrapper.tsx
new file mode 100644
index 0000000000..870997ab7c
--- /dev/null
+++ b/packages/lib/src/components/internal/Timeline/TimelineWrapper.tsx
@@ -0,0 +1,13 @@
+import { ComponentChildren, h } from 'preact';
+import './TimelineWrapper.scss';
+
+interface TimelineWrapperProps {
+ children: ComponentChildren;
+ className?: string;
+}
+
+const TimelineWrapper = ({ children, className }: TimelineWrapperProps) => {
+ return {children}
;
+};
+
+export { TimelineWrapper };
diff --git a/packages/lib/src/components/internal/Timeline/index.ts b/packages/lib/src/components/internal/Timeline/index.ts
new file mode 100644
index 0000000000..e4bfcc24a2
--- /dev/null
+++ b/packages/lib/src/components/internal/Timeline/index.ts
@@ -0,0 +1,2 @@
+export * from './TimelineWrapper';
+export * from './Timeline';
diff --git a/packages/lib/src/utils/useIsMobile.ts b/packages/lib/src/utils/useIsMobile.ts
new file mode 100644
index 0000000000..c5a8a2249d
--- /dev/null
+++ b/packages/lib/src/utils/useIsMobile.ts
@@ -0,0 +1,25 @@
+import { useCallback, useEffect, useState } from 'preact/hooks';
+
+function isTabletWidthOrSmaller() {
+ return window.matchMedia('(max-width: 1024px)').matches;
+}
+
+const useIsMobile = () => {
+ const [isMobileScreenSize, setIsMobileScreenSize] = useState(isTabletWidthOrSmaller());
+
+ const handleWindowResize = useCallback(() => {
+ const isMobileScreenSize = isTabletWidthOrSmaller();
+ setIsMobileScreenSize(isMobileScreenSize);
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener('resize', handleWindowResize);
+ return () => {
+ window.removeEventListener('resize', handleWindowResize);
+ };
+ }, [handleWindowResize]);
+
+ return { isMobileScreenSize };
+};
+
+export { useIsMobile };
diff --git a/packages/lib/storybook/stories/components/PayMe.stories.tsx b/packages/lib/storybook/stories/components/PayMe.stories.tsx
new file mode 100644
index 0000000000..047700b86b
--- /dev/null
+++ b/packages/lib/storybook/stories/components/PayMe.stories.tsx
@@ -0,0 +1,24 @@
+import { MetaConfiguration, PaymentMethodStoryProps, StoryConfiguration } from '../types';
+import { QRLoaderConfiguration } from '../../../src/types';
+import { ComponentContainer } from '../ComponentContainer';
+import { Checkout } from '../Checkout';
+import PayMe from '../../../src/components/PayMe/PayMe';
+
+type PayMeStory = StoryConfiguration;
+
+const meta: MetaConfiguration = {
+ title: 'Components/PayMe'
+};
+
+const render = ({ componentConfiguration, ...checkoutConfig }: PaymentMethodStoryProps) => (
+ {checkout => }
+);
+
+export const Default: PayMeStory = {
+ render,
+ args: {
+ countryCode: 'HK'
+ }
+};
+
+export default meta;
diff --git a/packages/lib/storybook/stories/components/PayNow.stories.tsx b/packages/lib/storybook/stories/components/PayNow.stories.tsx
new file mode 100644
index 0000000000..8448c4765f
--- /dev/null
+++ b/packages/lib/storybook/stories/components/PayNow.stories.tsx
@@ -0,0 +1,24 @@
+import { MetaConfiguration, PaymentMethodStoryProps, StoryConfiguration } from '../types';
+import { QRLoaderConfiguration } from '../../../src/types';
+import { ComponentContainer } from '../ComponentContainer';
+import { Checkout } from '../Checkout';
+import PayNow from '../../../src/components/PayNow/PayNow';
+
+type PayNowStory = StoryConfiguration;
+
+const meta: MetaConfiguration = {
+ title: 'Components/PayNow'
+};
+
+const render = ({ componentConfiguration, ...checkoutConfig }: PaymentMethodStoryProps) => (
+ {checkout => }
+);
+
+export const Default: PayNowStory = {
+ render,
+ args: {
+ countryCode: 'SG'
+ }
+};
+
+export default meta;
diff --git a/packages/lib/storybook/stories/components/Payconic.stories.tsx b/packages/lib/storybook/stories/components/Payconic.stories.tsx
new file mode 100644
index 0000000000..6dc2571b74
--- /dev/null
+++ b/packages/lib/storybook/stories/components/Payconic.stories.tsx
@@ -0,0 +1,26 @@
+import { MetaConfiguration, PaymentMethodStoryProps, StoryConfiguration } from '../types';
+import { QRLoaderConfiguration } from '../../../src/types';
+import { ComponentContainer } from '../ComponentContainer';
+import { Checkout } from '../Checkout';
+import BcmcMobile from '../../../src/components/BcmcMobile';
+
+type PayconicStory = StoryConfiguration;
+
+const meta: MetaConfiguration = {
+ title: 'Components/Payconic'
+};
+
+const render = ({ componentConfiguration, ...checkoutConfig }: PaymentMethodStoryProps) => (
+
+ {checkout => }
+
+);
+
+export const Default: PayconicStory = {
+ render,
+ args: {
+ countryCode: 'BE'
+ }
+};
+
+export default meta;
diff --git a/packages/lib/storybook/stories/components/Swish.stories.tsx b/packages/lib/storybook/stories/components/Swish.stories.tsx
new file mode 100644
index 0000000000..43a643e0fd
--- /dev/null
+++ b/packages/lib/storybook/stories/components/Swish.stories.tsx
@@ -0,0 +1,24 @@
+import { MetaConfiguration, PaymentMethodStoryProps, StoryConfiguration } from '../types';
+import { QRLoaderConfiguration } from '../../../src/types';
+import { ComponentContainer } from '../ComponentContainer';
+import { Checkout } from '../Checkout';
+import Swish from '../../../src/components/Swish';
+
+type SwishStory = StoryConfiguration;
+
+const meta: MetaConfiguration = {
+ title: 'Components/Swish'
+};
+
+const render = ({ componentConfiguration, ...checkoutConfig }: PaymentMethodStoryProps) => (
+ {checkout => }
+);
+
+export const Default: SwishStory = {
+ render,
+ args: {
+ countryCode: 'SE'
+ }
+};
+
+export default meta;
diff --git a/packages/lib/storybook/stories/internals/Timeline.stories.tsx b/packages/lib/storybook/stories/internals/Timeline.stories.tsx
new file mode 100644
index 0000000000..49ff2b2e88
--- /dev/null
+++ b/packages/lib/storybook/stories/internals/Timeline.stories.tsx
@@ -0,0 +1,24 @@
+import { Meta, StoryObj } from '@storybook/preact';
+import { Timeline } from '../../../src/components/internal/Timeline';
+
+const meta: Meta = {
+ title: 'Internals/Timeline',
+ component: Timeline
+};
+
+export const Default: StoryObj = {
+ render: () => {
+ const instructions = [
+ 'Open the banking app',
+ "Enter your email, ID, name, surname, fax number, grandmother's name, postal code, secret word",
+ 'Wait for the OTP',
+ 'Complete the payment in the app and wait for the confirmation here'
+ ];
+ return ;
+ },
+ parameters: {
+ controls: { exclude: ['useSessions', 'countryCode', 'shopperLocale', 'amount', 'showPayButton'] }
+ }
+};
+
+export default meta;
diff --git a/packages/server/translations/is-IS.json b/packages/server/translations/is-IS.json
index 01173c63e7..f01b99b0cb 100644
--- a/packages/server/translations/is-IS.json
+++ b/packages/server/translations/is-IS.json
@@ -325,4 +325,4 @@
"paynow.mobileViewInstruction.step3": "Veldu valkostinn til að skanna QR-kóða.",
"paynow.mobileViewInstruction.step4": "Veldu valkostinn til að hlaða upp QR-kóða og veldu skjámyndina.",
"paynow.mobileViewInstruction.step5": "Ljúktu færslunni."
-}
+}
\ No newline at end of file