Integracja w React i Vue
SDK jest niezależne od frameworka. Dwie zasady utrzymują je bez wycieków w aplikacjach SPA:
- Utwórz instancję raz na checkout i trzymaj ją w ref lub store, a nie w stanie renderowania.
- Wywołaj
card.unmount()/paymentic.destroy(), gdy checkout jest odmontowywany. To zwalnia iframe, listenerymessagei przyciski portfeli.
Oba przykłady korzystają z helpera loadPaymenticCard i skopiowanych deklaracji typów z
Instalacja i inicjalizacja.
React
import { useEffect, useRef, useState } from 'react';
import type { CardElement, PaymenticCard } from './types/paymentic/index';
import { loadPaymenticCard } from './loadPaymenticCard';
type Props = { pointId: string; amount: string; onToken: (jwt: string) => Promise<void> };
export function CheckoutForm({ pointId, amount, onToken }: Props) {
const cardHost = useRef<HTMLDivElement>(null);
const googlePayHost = useRef<HTMLDivElement>(null);
const applePayHost = useRef<HTMLDivElement>(null);
const cardRef = useRef<CardElement | null>(null);
const sdkRef = useRef<PaymenticCard | null>(null);
const [complete, setComplete] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
(async () => {
const PaymenticCard = await loadPaymenticCard();
if (cancelled || !cardHost.current) return;
const paymentic = new PaymenticCard({
pointId,
wallet: { payment: { amount, currencyCode: 'PLN', countryCode: 'PL', label: 'Twój Sklep' } },
});
sdkRef.current = paymentic;
paymentic
.on('complete', (data) => void onToken(data.jwt))
.on('error', (data) => setError(data.error));
const card = paymentic.elements().create('card', { lang: 'pl' });
card.on('change', (event) => setComplete(event.complete));
card.mount(cardHost.current);
cardRef.current = card;
if (googlePayHost.current) void paymentic.mountGooglePay(googlePayHost.current);
if (applePayHost.current) void paymentic.mountApplePay(applePayHost.current);
})();
return () => {
cancelled = true;
sdkRef.current?.destroy();
sdkRef.current = null;
cardRef.current = null;
};
}, [pointId, amount, onToken]);
async function pay(event: React.FormEvent) {
event.preventDefault();
if (!sdkRef.current || !cardRef.current) return;
const result = await sdkRef.current.tokenize(cardRef.current);
if (result.error) {
setError(result.error.message);
return;
}
await onToken(result.jwt);
}
return (
<form onSubmit={pay}>
<div ref={cardHost} style={{ minHeight: 48 }} />
{error && <p role="alert">{error}</p>}
<button type="submit" disabled={!complete}>Zapłać</button>
<div ref={googlePayHost} />
<div ref={applePayHost} />
</form>
);
}
Utrzymuj amount stabilne, dopóki formularz jest widoczny. Jeśli kwota się zmienia, albo utwórz
instancję ponownie (jak robi to powyższy efekt), albo przekaż nową kwotę przy wywołaniu
requestGooglePay / requestApplePay.
Vue 3
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue';
import type { CardElement, PaymenticCard } from './types/paymentic/index';
import { loadPaymenticCard } from './loadPaymenticCard';
const host = ref<HTMLDivElement | null>(null);
const complete = ref(false);
let paymentic: PaymenticCard | null = null;
let card: CardElement | null = null;
onMounted(async () => {
const PaymenticCard = await loadPaymenticCard();
paymentic = new PaymenticCard({ pointId: '000cb241' });
card = paymentic.elements().create('card');
card.on('change', (e) => (complete.value = e.complete)).mount(host.value!);
});
onBeforeUnmount(() => paymentic?.destroy());
async function pay() {
const result = await paymentic!.tokenize(card!);
if (result.error) return alert(result.error.message);
await fetch('/api/checkout/pay', { method: 'POST', body: JSON.stringify({ token: result.jwt }) });
}
</script>
<template>
<div ref="host" style="min-height: 48px" />
<button :disabled="!complete" @click="pay">Zapłać</button>
</template>
Co dalej
- Błędy i rozwiązywanie problemów, jeśli pamięć rośnie po wielokrotnym otwarciu checkoutu