Skip to content

Commit

Permalink
Feat: PayMe wallet (#2476)
Browse files Browse the repository at this point in the history
* refactor: scaffold the PayMe wallet

* feat(payme): add the PayMe wallet

* refactor(payme): add the logic for the countdownTime

* refactor(payme): remove comments

* refactor: add translations

* test: add some tests

* refactor: added tests

* refactor: unit test

* chore: add changeset
  • Loading branch information
longyulongyu authored Jan 9, 2024
1 parent d358019 commit 8ea9ded
Show file tree
Hide file tree
Showing 37 changed files with 323 additions and 61 deletions.
5 changes: 5 additions & 0 deletions .changeset/happy-crews-arrive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@adyen/adyen-web': minor
---

Add support for the PayMe payment method.
15 changes: 15 additions & 0 deletions packages/lib/src/components/PayMe/Instructions.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@import '../../style/index';

.adyen-checkout-payme-instructions {
font-size: $font-size-small;
text-align: center;
color: #5c687c;
line-height: 20px;

&__steps {
list-style-position: inside;
padding-inline-start: 0;
margin: 16px 0;
padding-bottom: 8px;
}
}
25 changes: 25 additions & 0 deletions packages/lib/src/components/PayMe/Instructions.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { render, screen } from '@testing-library/preact';
import { h } from 'preact';
import Language from '../../language';
import { Resources } from '../../core/Context/Resources';
import CoreProvider from '../../core/Context/CoreProvider';
import Instructions from './Instructions';

describe('Instructions', () => {
const customRender = (ui: h.JSX.Element) => {
return render(
// @ts-ignore ignore
<CoreProvider i18n={new Language()} loadingContext="test" resources={new Resources()}>
{ui}
</CoreProvider>
);
};

test('should see a list of instructions and footnote', async () => {
customRender(<Instructions />);
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();
});
});
20 changes: 20 additions & 0 deletions packages/lib/src/components/PayMe/Instructions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import useCoreContext from '../../core/Context/useCoreContext';
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 (
<div className="adyen-checkout-payme-instructions">
<ol className="adyen-checkout-payme-instructions__steps">
{steps.split('%@').map((step, index) => (
<li key={`instruction-${index}`}>{step}</li>
))}
</ol>
<span>{footnote}</span>
</div>
);
}
23 changes: 23 additions & 0 deletions packages/lib/src/components/PayMe/PayMe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import QRLoaderContainer from '../helpers/QRLoaderContainer';
import Instructions from './Instructions';

class PayMeElement extends QRLoaderContainer {
public static type = 'payme';
private static defaultCountdown = 10; // min
private static defaultDelay = 2000; // ms

formatProps(props) {
return {
delay: PayMeElement.defaultDelay,
countdownTime: PayMeElement.defaultCountdown,
redirectIntroduction: 'payme.openPayMeApp',
introduction: 'payme.scanQrCode',
timeToPay: 'payme.timeToPay',
buttonLabel: 'payme.redirectButtonLabel',
instructions: Instructions,
...super.formatProps(props)
};
}
}

export default PayMeElement;
1 change: 1 addition & 0 deletions packages/lib/src/components/PayMe/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './PayMe';
4 changes: 3 additions & 1 deletion packages/lib/src/components/helpers/QRLoaderContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export interface QRLoaderContainerProps extends UIElementProps {
qrCodeImage?: string;
paymentData?: string;
introduction: string;
instructions?: string;
redirectIntroduction?: string;
timeToPay?: string;
instructions?: string | (() => h.JSX.Element);
copyBtn?: boolean;
}

Expand Down
2 changes: 2 additions & 0 deletions packages/lib/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import PromptPay from './PromptPay';
import Duitnow from './DuitNow';
import ANCV from './ANCV';
import Trustly from './Trustly';
import PayMe from './PayMe';

/**
* Maps each component with a Component element.
Expand Down Expand Up @@ -210,6 +211,7 @@ const componentsMap = {
promptpay: PromptPay,
paynow: PayNow,
duitnow: Duitnow,
payme: PayMe,
/** QRLoader */

/** Await */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class CountdownA11yReporter {

constructor(props: ICountdownA11yService) {
const { srPanel, i18n } = props;

this.srPanel = srPanel;
this.i18n = i18n;
// Force the srPanel to update ariaRelevant
Expand Down
18 changes: 13 additions & 5 deletions packages/lib/src/components/internal/QRLoader/QRLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ class QRLoader extends Component<QRLoaderProps, QRLoaderState> {
throttleTime: 60000,
classNameModifiers: [],
throttledInterval: 10000,
introduction: 'wechatpay.scanqrcode'
introduction: 'wechatpay.scanqrcode',
timeToPay: 'wechatpay.timetopay',
buttonLabel: 'openApp'
};

componentDidMount() {
Expand Down Expand Up @@ -148,7 +150,6 @@ class QRLoader extends Component<QRLoaderProps, QRLoaderState> {
const { i18n, loadingContext } = useCoreContext();
const getImage = useImage();
const qrCodeImage = this.props.qrCodeData ? `${loadingContext}${QRCODE_URL}${this.props.qrCodeData}` : this.props.qrCodeImage;

const finalState = (image, message) => {
const status = i18n.get(message);
useA11yReporter(status);
Expand Down Expand Up @@ -181,7 +182,7 @@ class QRLoader extends Component<QRLoaderProps, QRLoaderState> {
);
}

const timeToPayString = i18n.get('wechatpay.timetopay').split('%@');
const timeToPayString = i18n.get(this.props.timeToPay).split('%@');

const qrSubtitleRef = useAutoFocus();

Expand All @@ -201,7 +202,10 @@ class QRLoader extends Component<QRLoaderProps, QRLoaderState> {

{url && (
<div className="adyen-checkout__qr-loader__app-link">
<Button classNameModifiers={['qr-loader']} onClick={() => this.redirectToApp(url)} label={i18n.get('openApp')} />
{this.props.redirectIntroduction && (
<div className="adyen-checkout__qr-loader__subtitle">{i18n.get(this.props.redirectIntroduction)}</div>
)}
<Button classNameModifiers={['qr-loader']} onClick={() => this.redirectToApp(url)} label={i18n.get(this.props.buttonLabel)} />
<ContentSeparator />
</div>
)}
Expand Down Expand Up @@ -229,7 +233,11 @@ class QRLoader extends Component<QRLoaderProps, QRLoaderState> {
&nbsp;{timeToPayString[1]}
</div>

{this.props.instructions && <div className="adyen-checkout__qr-loader__instructions">{i18n.get(this.props.instructions)}</div>}
{typeof this.props.instructions === 'string' ? (
<div className="adyen-checkout__qr-loader__instructions">{i18n.get(this.props.instructions)}</div>
) : (
this.props.instructions?.()
)}

{this.props.copyBtn && (
<div className="adyen-checkout__qr-loader__actions">
Expand Down
6 changes: 5 additions & 1 deletion packages/lib/src/components/internal/QRLoader/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { PaymentAmount } from '../../../types';
import Language from '../../../language/Language';
import { ActionHandledReturnObject } from '../../types';
import { h } from 'preact';

export interface QRLoaderProps {
delay?: number;
Expand All @@ -21,8 +22,11 @@ export interface QRLoaderProps {
classNameModifiers?: string[];
brandLogo?: string;
brandName?: string;
buttonLabel?: string;
introduction?: string;
instructions?: string;
redirectIntroduction?: string;
timeToPay?: string;
instructions?: string | (() => h.JSX.Element);
copyBtn?: boolean;
onActionHandled?: (rtnObj: ActionHandledReturnObject) => void;
}
Expand Down
10 changes: 8 additions & 2 deletions packages/lib/src/language/locales/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -301,5 +301,11 @@
"ancv.input.label": "تعريف ANCV الخاص بك",
"ancv.confirmPayment": "استخدم تطبيق ANCV الخاص بك لتأكيد الدفع.",
"ancv.form.instruction": "يعد تطبيق Cheque-Vacances ضروريًا للمصادقة على هذه المدفوعات.",
"ancv.beneficiaryId.invalid": "أدخل عنوان بريد إلكتروني صحيحًا أو معرف ANCV"
}
"ancv.beneficiaryId.invalid": "أدخل عنوان بريد إلكتروني صحيحًا أو معرف ANCV",
"payme.openPayMeApp": "أكمل الدفع في تطبيق PayMe من خلال الإذن بالدفع في التطبيق وانتظر التأكيد.",
"payme.redirectButtonLabel": "افتح تطبيق PayMe",
"payme.scanQrCode": "أكمل الدفع باستخدام رمز الاستجابة السريعة",
"payme.timeToPay": "رمز الاستجابة السريعة هذا صالح لـ %@",
"payme.instructions.steps": "افتح تطبيق PayMe. %@امسح رمز الاستجابة السريعة ضوئيًا للإذن بالدفع. %@أكمل عملية الدفع في التطبيق وانتظر التأكيد.",
"payme.instructions.footnote": "يرجى عدم إغلاق هذه الصفحة قبل إتمام الدفع"
}
10 changes: 8 additions & 2 deletions packages/lib/src/language/locales/cs-CZ.json
Original file line number Diff line number Diff line change
Expand Up @@ -297,5 +297,11 @@
"ancv.input.label": "Vaše identifikace ANCV",
"ancv.confirmPayment": "Pro potvrzení platby použijte aplikaci ANCV.",
"ancv.form.instruction": "K potvrzení této platby je nutná aplikace Cheque-Vacances.",
"ancv.beneficiaryId.invalid": "Zadejte platnou e-mailovou adresu nebo ID ANCV"
}
"ancv.beneficiaryId.invalid": "Zadejte platnou e-mailovou adresu nebo ID ANCV",
"payme.openPayMeApp": "Dokončete platbu autorizací v aplikaci PayMe a počkejte na potvrzení.",
"payme.redirectButtonLabel": "Otevřete aplikaci PayMe",
"payme.scanQrCode": "Dokončete platbu pomocí QR kódu",
"payme.timeToPay": "Tento QR kód je platný pro %@",
"payme.instructions.steps": "Otevřete aplikaci PayMe.%@Autorizujte platbu naskenováním QR kódu.%@Dokončete platbu v aplikaci a počkejte na potvrzení.",
"payme.instructions.footnote": "Nezavírejte prosím tuto stránku před dokončením platby."
}
10 changes: 8 additions & 2 deletions packages/lib/src/language/locales/da-DK.json
Original file line number Diff line number Diff line change
Expand Up @@ -298,5 +298,11 @@
"ancv.input.label": "Din ANCV-identifikation",
"ancv.confirmPayment": "Brug din ANCV-applikation til at bekræfte betalingen.",
"ancv.form.instruction": "Cheque-Vacances-applikationen er nødvendig for at validere denne betaling.",
"ancv.beneficiaryId.invalid": "Indtast en gyldig e-mailadresse eller ANCV-id"
}
"ancv.beneficiaryId.invalid": "Indtast en gyldig e-mailadresse eller ANCV-id",
"payme.openPayMeApp": "Gennemfør betalingen i PayMe-appen ved at godkende betalingen i appen og afvente bekræftelsen.",
"payme.redirectButtonLabel": "Åbn PayMe-appen",
"payme.scanQrCode": "Gennemfør din betaling med QR-kode",
"payme.timeToPay": "Denne QR-kode er gyldig i %@",
"payme.instructions.steps": "Åbn PayMe-appen.%@Scan QR-koden for at godkende betalingen.%@Gennemfør betalingen i appen, og afvent bekræftelse.",
"payme.instructions.footnote": "Luk ikke denne side, før betalingen er gennemført"
}
10 changes: 8 additions & 2 deletions packages/lib/src/language/locales/de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -297,5 +297,11 @@
"ancv.input.label": "Ihre ANCV-Identifikation",
"ancv.confirmPayment": "Bestätigen Sie die Zahlung mit Ihrem ANCV-Antrag.",
"ancv.form.instruction": "Zur Validierung dieser Zahlung ist der Antrag „Cheque-Vacances“ erforderlich.",
"ancv.beneficiaryId.invalid": "Geben Sie eine gültige E-Mail-Adresse oder ANCV-ID ein"
}
"ancv.beneficiaryId.invalid": "Geben Sie eine gültige E-Mail-Adresse oder ANCV-ID ein",
"payme.openPayMeApp": "Schließen Sie Ihre Zahlung in der PayMe-App ab, indem Sie die Zahlung in der App autorisieren und auf die Bestätigung warten.",
"payme.redirectButtonLabel": "Öffnen Sie die PayMe-App",
"payme.scanQrCode": "Schließen Sie Ihre Zahlung per QR-Code ab",
"payme.timeToPay": "Dieser QR-Code gilt für %@",
"payme.instructions.steps": "Öffnen Sie die PayMe-App.%@Scannen Sie den QR-Code, um die Zahlung zu autorisieren.%@Schließen Sie die Zahlung in der App ab und warten Sie auf eine Bestätigung.",
"payme.instructions.footnote": "Bitte schließen Sie diese Seite nicht, bevor die Zahlung abgeschlossen ist"
}
10 changes: 8 additions & 2 deletions packages/lib/src/language/locales/el-GR.json
Original file line number Diff line number Diff line change
Expand Up @@ -300,5 +300,11 @@
"ancv.input.label": "Η ταυτότητά σας ANCV",
"ancv.confirmPayment": "Χρησιμοποιήστε την εφαρμογή ANCV για επιβεβαίωση της πληρωμής.",
"ancv.form.instruction": "Η εφαρμογή Cheque-Vacances είναι απαραίτητη για επικύρωση της πληρωμής αυτής.",
"ancv.beneficiaryId.invalid": "Εισαγάγετε έγκυρη διεύθυνση email ή αναγνωριστικό ANCV"
}
"ancv.beneficiaryId.invalid": "Εισαγάγετε έγκυρη διεύθυνση email ή αναγνωριστικό ANCV",
"payme.openPayMeApp": "Ολοκληρώστε την πληρωμή σας στην εφαρμογή PayMe εξουσιοδοτώντας την πληρωμή στην εφαρμογή και περιμένετε την επιβεβαίωση.",
"payme.redirectButtonLabel": "Ανοίξτε την εφαρμογή PayMe",
"payme.scanQrCode": "Ολοκληρώστε την πληρωμή σας με κωδικό QR",
"payme.timeToPay": "Αυτός ο κωδικός QR ισχύει για %@",
"payme.instructions.steps": "Ανοίξτε την εφαρμογή PayMe.%@Σκανάρετε τον κωδικό QR για να εξουσιοδοτήσετε την πληρωμή.%@Ολοκληρώστε την πληρωμή στην εφαρμογή και περιμένετε την επιβεβαίωση.",
"payme.instructions.footnote": "Μην κλείσετε αυτήν τη σελίδα προτού ολοκληρωθεί η πληρωμή."
}
10 changes: 8 additions & 2 deletions packages/lib/src/language/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -302,5 +302,11 @@
"ancv.input.label": "Your ANCV identification",
"ancv.confirmPayment": "Use your ANCV application to confirm the payment.",
"ancv.form.instruction": "The Cheque-Vacances application is necessary to validate this payment.",
"ancv.beneficiaryId.invalid": "Enter a valid email address or ANCV ID"
}
"ancv.beneficiaryId.invalid": "Enter a valid email address or ANCV ID",
"payme.openPayMeApp": "Complete your payment in the PayMe app by authorizing the payment in the app and wait for the confirmation.",
"payme.redirectButtonLabel": "Open PayMe app",
"payme.scanQrCode": "Complete your payment by QR code",
"payme.timeToPay": "This QR code is valid for %@",
"payme.instructions.steps": "Open the PayMe app.%@Scan the QR code to authorize the payment.%@Complete the payment in the app and wait for confirmation.",
"payme.instructions.footnote": "Please do not close this page before the payment is completed"
}
10 changes: 8 additions & 2 deletions packages/lib/src/language/locales/es-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -292,5 +292,11 @@
"ancv.input.label": "Su identificación de la ANCV",
"ancv.confirmPayment": "Utilice su solicitud de la ANCV para confirmar el pago.",
"ancv.form.instruction": "La aplicación de Cheque-Vacances es necesaria para validar este pago.",
"ancv.beneficiaryId.invalid": "Introduzca una dirección de correo electrónico válida o un documento de identidad de la ANCV"
}
"ancv.beneficiaryId.invalid": "Introduzca una dirección de correo electrónico válida o un documento de identidad de la ANCV",
"payme.openPayMeApp": "Completa tu pago en la aplicación PayMe autorizando el pago en la aplicación y espera por la confirmación.",
"payme.redirectButtonLabel": "Abrir aplicación PayMe",
"payme.scanQrCode": "Completa tu pago con código QR",
"payme.timeToPay": "Este código QR es válido para %@",
"payme.instructions.steps": "Abre la aplicación PayMe.%@Escanea el código QR para autorizar el pago.%@Completa el pago en la aplicación y espera por la confirmación.",
"payme.instructions.footnote": "No cierres esta página antes de que se complete el pago"
}
10 changes: 8 additions & 2 deletions packages/lib/src/language/locales/fi-FI.json
Original file line number Diff line number Diff line change
Expand Up @@ -297,5 +297,11 @@
"ancv.input.label": "ANCV-tunnuksesi",
"ancv.confirmPayment": "Vahvista maksusi ANCV-sovelluksella.",
"ancv.form.instruction": "Tämän maksun vahvistaminen edellyttää Cheque-Vacances -sovelluksen.",
"ancv.beneficiaryId.invalid": "Anna kelvollinen sähköpostiosoite tai ANCV-tunnus"
}
"ancv.beneficiaryId.invalid": "Anna kelvollinen sähköpostiosoite tai ANCV-tunnus",
"payme.openPayMeApp": "Viimeistele maksu PayMe-sovelluksessa hyväksymällä maksu sovelluksessa, ja odota vahvistusta.",
"payme.redirectButtonLabel": "Avaa PayMe-sovellus",
"payme.scanQrCode": "Viimeistele maksusi QR-koodilla",
"payme.timeToPay": "Tämä QR-koodi on voimassa %@",
"payme.instructions.steps": "Avaa PayMe-sovellus.%@Hyväksy maksu skannaamalla QR-koodi.%@Viimeistele maksu sovelluksessa, ja odota vahvistusta.",
"payme.instructions.footnote": "Älä sulje tätä sivua ennen kuin maksu on suoritettu"
}
10 changes: 8 additions & 2 deletions packages/lib/src/language/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -300,5 +300,11 @@
"ancv.input.label": "Votre identification ANCV",
"ancv.confirmPayment": "Utilisez votre application ANCV pour confirmer le paiement.",
"ancv.form.instruction": "L'application Chèque-Vacances est nécessaire pour valider ce paiement.",
"ancv.beneficiaryId.invalid": "Saisissez une adresse e-mail ou un identifiant ANCV valide"
}
"ancv.beneficiaryId.invalid": "Saisissez une adresse e-mail ou un identifiant ANCV valide",
"payme.openPayMeApp": "Finalisez votre paiement dans l'application PayMe en autorisant le paiement dans l'application, puis attendez la confirmation.",
"payme.redirectButtonLabel": "Ouvrir l'application PayMe",
"payme.scanQrCode": "Effectuez votre paiement avec un code QR",
"payme.timeToPay": "Ce code QR est valide pendant %@",
"payme.instructions.steps": "Ouvrez l'application PayMe.%@Scannez le code QR pour autoriser le paiement.%@Effectuez le paiement dans l'application et attendez la confirmation.",
"payme.instructions.footnote": "Veuillez ne pas fermer cette page avant que le paiement ne soit terminé."
}
10 changes: 8 additions & 2 deletions packages/lib/src/language/locales/hr-HR.json
Original file line number Diff line number Diff line change
Expand Up @@ -300,5 +300,11 @@
"ancv.input.label": "Vaša ANCV identifikacija",
"ancv.confirmPayment": "Koristite svoju ANCV aplikaciju za potvrdu plaćanja.",
"ancv.form.instruction": "Za potvrdu ove uplate neophodna je aplikacija Cheque-Vacances.",
"ancv.beneficiaryId.invalid": "Unesite valjanu adresu e-pošte ili ANCV ID"
}
"ancv.beneficiaryId.invalid": "Unesite valjanu adresu e-pošte ili ANCV ID",
"payme.openPayMeApp": "Dovršite plaćanje u aplikaciji PayMe: autorizirajte plaćanje u aplikaciji i pričekajte potvrdu.",
"payme.redirectButtonLabel": "Otvaranje aplikacije PayMe",
"payme.scanQrCode": "Dovršite svoje plaćanja QR kodom",
"payme.timeToPay": "Ovaj QR kôd vrijedi za %@",
"payme.instructions.steps": "Otvorite aplikaciju PayMe.%@Skenirajte QR kod za autorizaciju plaćanja.%@Dovršite plaćanje u aplikaciji i pričekajte potvrdu.",
"payme.instructions.footnote": "Ne zatvarajte ovu stranicu prije nego što se plaćanje završi"
}
10 changes: 8 additions & 2 deletions packages/lib/src/language/locales/hu-HU.json
Original file line number Diff line number Diff line change
Expand Up @@ -300,5 +300,11 @@
"ancv.input.label": "Az Ön ANCV-azonosítója",
"ancv.confirmPayment": "A fizetés megerősítéséhez használja az ANCV alkalmazást.",
"ancv.form.instruction": "A fizetés érvényesítéséhez a Cheque-Vacances alkalmazás szükséges.",
"ancv.beneficiaryId.invalid": "Adjon meg egy érvényes e-mail-címet vagy ANCV-azonosítót"
}
"ancv.beneficiaryId.invalid": "Adjon meg egy érvényes e-mail-címet vagy ANCV-azonosítót",
"payme.openPayMeApp": "A fizetésnek a PayMe alkalmazásban való engedélyezésével hajtsa végre a fizetést, és várja meg a visszaigazolást.",
"payme.redirectButtonLabel": "PayMe alkalmazás megnyitása",
"payme.scanQrCode": "Fizetés végrehajtása QR-kóddal",
"payme.timeToPay": "A QR-kód ennyi ideig érvényes: %@",
"payme.instructions.steps": "Nyissa meg a PayMe alkalmazást.%@A fizetés engedélyezéséhez olvassa be a QR-kódot.%@Hajtsa végre a fizetést az alkalmazásban, és várja meg a visszaigazolást.",
"payme.instructions.footnote": "A fizetés befejezése előtt ne zárja be ezt az oldalt"
}
Loading

0 comments on commit 8ea9ded

Please sign in to comment.