Skip to content

Commit

Permalink
PayMe and PayNow improvements (#2910)
Browse files Browse the repository at this point in the history
* PayNow improvements (#2889)

* rendering instructions for mobile

* using bento tokens

* changeset

* draft stepper

* fixed stories

* translations and css changes

* Fixed test

* adjusted timeline component and css fixes

* fixed test

* applied review comments

* PayMe improvements (#2909)

* Payconic labels fix (#2916)

* using token fn
  • Loading branch information
ribeiroguilherme authored Oct 25, 2024
1 parent 04918e1 commit 4fb7df4
Show file tree
Hide file tree
Showing 35 changed files with 541 additions and 98 deletions.
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
23 changes: 23 additions & 0 deletions packages/lib/src/components/PayMe/components/PayMeInstructions.tsx
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 };
21 changes: 21 additions & 0 deletions packages/lib/src/components/PayMe/components/PayMeIntroduction.tsx
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

0 comments on commit 4fb7df4

Please sign in to comment.