Skip to content
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

PayMe and PayNow improvements #2910

Merged
merged 5 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/shaggy-poets-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@adyen/adyen-web': patch
---

Payconic - Adjusted QR code message and removed unused button label.
5 changes: 5 additions & 0 deletions .changeset/sharp-cars-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@adyen/adyen-web': minor
---

PayMe - Improved instructions UI
5 changes: 5 additions & 0 deletions .changeset/tricky-pets-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@adyen/adyen-web': minor
---

PayNow - Adding instructions to scan QR code on mobile view
4 changes: 1 addition & 3 deletions packages/lib/src/components/BcmcMobile/BcmcMobile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
};
}
Expand Down
15 changes: 0 additions & 15 deletions packages/lib/src/components/PayMe/Instructions.scss

This file was deleted.

22 changes: 0 additions & 22 deletions packages/lib/src/components/PayMe/Instructions.test.tsx

This file was deleted.

20 changes: 0 additions & 20 deletions packages/lib/src/components/PayMe/Instructions.tsx

This file was deleted.

7 changes: 4 additions & 3 deletions packages/lib/src/components/PayMe/PayMe.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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)
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<TimelineWrapper>
<Timeline instructions={instructions} />
</TimelineWrapper>
);
};

export { PayMeInstructions };
Original file line number Diff line number Diff line change
@@ -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 ? (
<TimelineWrapper>
<Timeline instructions={instructions} />
</TimelineWrapper>
) : (
<Fragment>{i18n.get('payme.scanQrCode')}</Fragment>
);
};

export { PayMeIntroduction };
56 changes: 56 additions & 0 deletions packages/lib/src/components/PayNow/PayNow.test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand All @@ -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<SRPanel>();
const resources = mock<Resources>();
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();
});
});
5 changes: 5 additions & 0 deletions packages/lib/src/components/PayNow/PayNow.ts
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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 (
<div className="adyen-checkout-paynow__instructions">
<ContentSeparator />
<p>{i18n.get('paynow.scanQrCode')}</p>
</div>
);
};

export { PayNowInstructions };
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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 (
<div className="adyen-checkout-paynow__introduction" data-testid="paynow-introduction">
{isMobileScreenSize ? (
<TimelineWrapper>
<Timeline instructions={instructions} />
</TimelineWrapper>
) : (
i18n.get('paynow.scanQrCode')
)}
</div>
);
};

export { PayNowIntroduction };
3 changes: 3 additions & 0 deletions packages/lib/src/components/Swish/Swish.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.adyen-checkout__qr-loader--swish > .adyen-checkout__qr-loader__instructions {
text-align: center;
}
1 change: 1 addition & 0 deletions packages/lib/src/components/Swish/Swish.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading
Loading