Przejdź do głównej zawartości

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, listenery message i 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