feat(payment): Add bank card payment

This commit is contained in:
web@ppanel 2025-03-12 17:15:32 +07:00
parent 70d6a38a29
commit 7fa3a57df4
31 changed files with 867 additions and 39 deletions

View File

@ -1,5 +1,5 @@
// @ts-ignore
// API 更新时间:
// API 唯一标识:
import * as ads from './ads';

View File

@ -1,5 +1,5 @@
// @ts-ignore
// API 更新时间:
// API 唯一标识:
import * as auth from './auth';

View File

@ -106,7 +106,7 @@ export default function Page() {
<dl className='grid gap-3'>
<div className='flex items-center justify-between'>
<dt className='text-muted-foreground'>
{data?.method && <Badge>{t(`methods.${data?.method}`)}</Badge>}
{data?.method && <Badge>{data?.method}</Badge>}
</dt>
</div>
</dl>
@ -223,19 +223,19 @@ export default function Page() {
</div>
)}
{data?.status === 1 && payment?.type === 'stripe' && (
{data?.status === 1 && payment?.type === 'Stripe' && (
<div className='flex flex-col items-center gap-8 text-center'>
<h3 className='text-2xl font-bold tracking-tight'>{t('scanToPay')}</h3>
<h3 className='text-2xl font-bold tracking-tight'>{t('waitingForPayment')}</h3>
<p className='flex items-center text-3xl font-bold'>{countdownDisplay}</p>
{payment.stripe && <StripePayment {...payment.stripe} />}
<div className='flex gap-4'>
{/* <div className='flex gap-4'>
<Button asChild>
<Link href='/subscribe'>{t('productList')}</Link>
</Button>
<Button asChild variant='outline'>
<Link href='/order'>{t('orderList')}</Link>
</Button>
</div>
</div> */}
</div>
)}

View File

@ -1,5 +1,23 @@
import { Elements, useStripe } from '@stripe/react-stripe-js';
import { loadStripe, PaymentIntentResult } from '@stripe/stripe-js';
import {
CardCvcElement,
CardExpiryElement,
CardNumberElement,
Elements,
useElements,
useStripe,
} from '@stripe/react-stripe-js';
import {
loadStripe,
PaymentIntentResult,
StripeCardNumberElementOptions,
StripeElementStyle,
} from '@stripe/stripe-js';
import { Button } from '@workspace/ui/components/button';
import { Input } from '@workspace/ui/components/input';
import { Label } from '@workspace/ui/components/label';
import { CheckCircle } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { useTheme } from 'next-themes';
import { QRCodeCanvas } from 'qrcode.react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
@ -9,6 +27,196 @@ interface StripePaymentProps {
publishable_key: string;
}
interface CardPaymentFormProps {
clientSecret: string;
onError: (message: string) => void;
}
const CardPaymentForm: React.FC<CardPaymentFormProps> = ({ clientSecret, onError }) => {
const stripe = useStripe();
const { theme, systemTheme } = useTheme();
const elements = useElements();
const [processing, setProcessing] = useState(false);
const [succeeded, setSucceeded] = useState(false);
const [errors, setErrors] = useState<{
cardNumber?: string;
cardExpiry?: string;
cardCvc?: string;
name?: string;
}>({});
const [cardholderName, setCardholderName] = useState('');
const t = useTranslations('payment.stripe.card');
const currentTheme = theme === 'system' ? systemTheme : theme;
const elementStyle: StripeElementStyle = {
base: {
'fontSize': '16px',
'color': currentTheme === 'dark' ? '#fff' : '#000',
'::placeholder': {
color: '#aab7c4',
},
},
invalid: {
color: '#EF4444',
iconColor: '#EF4444',
},
};
const elementOptions: StripeCardNumberElementOptions = {
style: elementStyle,
showIcon: true,
};
const handleChange = (event: any, field: keyof typeof errors) => {
if (event.error) {
setErrors((prev) => ({ ...prev, [field]: event.error.message }));
} else {
setErrors((prev) => ({ ...prev, [field]: undefined }));
}
};
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
if (!stripe || !elements) {
onError(t('loading'));
return;
}
if (!cardholderName.trim()) {
setErrors((prev) => ({ ...prev, name: t('name_required') }));
return;
}
setProcessing(true);
const cardNumber = elements.getElement(CardNumberElement);
const cardExpiry = elements.getElement(CardExpiryElement);
const cardCvc = elements.getElement(CardCvcElement);
if (!cardNumber || !cardExpiry || !cardCvc) {
onError(t('element_error'));
setProcessing(false);
return;
}
const { error, paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: cardNumber,
billing_details: {
name: cardholderName,
},
},
});
if (error) {
onError(error.message || t('payment_failed'));
setProcessing(false);
} else if (paymentIntent && paymentIntent.status === 'succeeded') {
setSucceeded(true);
setProcessing(false);
} else {
onError(t('processing'));
setProcessing(false);
}
};
return (
<form onSubmit={handleSubmit}>
{succeeded ? (
<div className='py-6 text-center'>
<div className='mb-4 flex justify-center'>
<CheckCircle className='h-12 w-12 text-green-500' />
</div>
<p className='text-xl font-medium'>{t('success_title')}</p>
<p className='text-muted-foreground mt-2'>{t('success_message')}</p>
</div>
) : (
<>
<div className='space-y-4'>
{/* Cardholder Name */}
<div className='space-y-1'>
<Label htmlFor='cardholderName' className='text-sm font-medium'>
{t('card_name')}
</Label>
<Input
id='cardholderName'
type='text'
value={cardholderName}
onChange={(e) => setCardholderName(e.target.value)}
placeholder={t('name_placeholder')}
className={errors.name ? 'border-red-500' : ''}
/>
{errors.name && <p className='text-xs text-red-500'>{errors.name}</p>}
</div>
{/* Card Number */}
<div className='space-y-1'>
<Label htmlFor='cardNumber' className='text-sm font-medium'>
{t('card_number')}
</Label>
<div className='relative'>
<div
className={`focus-within:border-primary focus-within:ring-primary rounded-md border p-3 focus-within:ring-1 ${errors.cardNumber ? 'border-red-500' : ''}`}
>
<CardNumberElement
id='cardNumber'
options={elementOptions}
onChange={(e) => handleChange(e, 'cardNumber')}
/>
</div>
</div>
{errors.cardNumber && <p className='text-xs text-red-500'>{errors.cardNumber}</p>}
</div>
<div className='grid grid-cols-2 gap-4'>
{/* Expiry Date */}
<div className='space-y-1'>
<Label htmlFor='cardExpiry' className='text-sm font-medium'>
{t('expiry_date')}
</Label>
<div
className={`focus-within:border-primary focus-within:ring-primary rounded-md border p-3 focus-within:ring-1 ${errors.cardExpiry ? 'border-red-500' : ''}`}
>
<CardExpiryElement
id='cardExpiry'
options={{ style: elementStyle }}
onChange={(e) => handleChange(e, 'cardExpiry')}
/>
</div>
{errors.cardExpiry && <p className='text-xs text-red-500'>{errors.cardExpiry}</p>}
</div>
{/* Security Code */}
<div className='space-y-1'>
<Label htmlFor='cardCvc' className='text-sm font-medium'>
{t('security_code')}
</Label>
<div
className={`focus-within:border-primary focus-within:ring-primary rounded-md border p-3 focus-within:ring-1 ${errors.cardCvc ? 'border-red-500' : ''}`}
>
<CardCvcElement
id='cardCvc'
options={{ style: elementStyle }}
onChange={(e) => handleChange(e, 'cardCvc')}
/>
</div>
{errors.cardCvc && <p className='text-xs text-red-500'>{errors.cardCvc}</p>}
</div>
</div>
</div>
<div className='mt-6 flex flex-col space-y-4'>
<Button type='submit' disabled={processing || !stripe || !elements} className='w-full'>
{processing ? t('processing_button') : t('pay_button')}
</Button>
<p className='text-muted-foreground text-center text-xs'>{t('secure_notice')}</p>
</div>
</>
)}
</form>
);
};
const StripePayment: React.FC<StripePaymentProps> = ({
method,
client_secret,
@ -31,6 +239,7 @@ const CheckoutForm: React.FC<Omit<StripePaymentProps, 'publishable_key'>> = ({
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [qrCodeUrl, setQrCodeUrl] = useState<string | null>(null);
const [isSubmitted, setIsSubmitted] = useState(false);
const t = useTranslations('payment.stripe');
const handleError = useCallback((message: string) => {
setErrorMessage(message);
@ -39,7 +248,7 @@ const CheckoutForm: React.FC<Omit<StripePaymentProps, 'publishable_key'>> = ({
const confirmPayment = useCallback(async (): Promise<PaymentIntentResult | null> => {
if (!stripe) {
handleError('Stripe.js is not loaded.');
handleError(t('card.loading'));
return null;
}
@ -50,18 +259,20 @@ const CheckoutForm: React.FC<Omit<StripePaymentProps, 'publishable_key'>> = ({
{ handleActions: false },
);
}
return await stripe.confirmWechatPayPayment(
client_secret,
{
payment_method_options: { wechat_pay: { client: 'web' } },
},
{ handleActions: false },
);
}, [client_secret, method, stripe, handleError]);
if (method === 'wechat_pay') {
return await stripe.confirmWechatPayPayment(
client_secret,
{
payment_method_options: { wechat_pay: { client: 'web' } },
},
{ handleActions: false },
);
}
return null;
}, [client_secret, method, stripe, handleError, t]);
const autoSubmit = useCallback(async () => {
if (isSubmitted) return;
if (isSubmitted || method === 'card') return;
setIsSubmitted(true);
@ -82,25 +293,32 @@ const CheckoutForm: React.FC<Omit<StripePaymentProps, 'publishable_key'>> = ({
setQrCodeUrl(qrUrl || null);
}
} catch (error) {
handleError('An unexpected error occurred');
handleError(t('error'));
}
}, [confirmPayment, isSubmitted, handleError, method]);
}, [confirmPayment, isSubmitted, handleError, method, t]);
useEffect(() => {
autoSubmit();
}, [autoSubmit]);
return qrCodeUrl ? (
<QRCodeCanvas
value={qrCodeUrl}
size={208}
imageSettings={{
src: `/payment/${method}.svg`,
width: 24,
height: 24,
excavate: true,
}}
/>
return method === 'card' ? (
<div className='min-w-80 text-left'>
<CardPaymentForm clientSecret={client_secret} onError={handleError} />
</div>
) : qrCodeUrl ? (
<>
<QRCodeCanvas
value={qrCodeUrl}
size={208}
imageSettings={{
src: `/payment/${method}.svg`,
width: 24,
height: 24,
excavate: true,
}}
/>
<p className='text-muted-foreground mt-4 text-center'>{t(`qrcode.${method}`)}</p>
</>
) : (
errorMessage
);

View File

@ -4,6 +4,7 @@ import { getAvailablePaymentMethods } from '@/services/user/portal';
import { useQuery } from '@tanstack/react-query';
import { Label } from '@workspace/ui/components/label';
import { RadioGroup, RadioGroupItem } from '@workspace/ui/components/radio-group';
import { cn } from '@workspace/ui/lib/utils';
import { useTranslations } from 'next-intl';
import Image from 'next/image';
import React, { memo } from 'react';
@ -33,14 +34,24 @@ const PaymentMethods: React.FC<PaymentMethodsProps> = ({ value, onChange, balanc
<RadioGroup
className='grid grid-cols-2 gap-2 md:grid-cols-5'
value={String(value)}
onValueChange={(val) => onChange(Number(val))}
onValueChange={(val) => {
console.log(val);
onChange(Number(val));
}}
>
{data?.map((item) => (
<div key={item.id}>
<RadioGroupItem value={String(item.id)} id={String(item.id)} className='peer sr-only' />
<div key={item.id} className='relative'>
<RadioGroupItem
value={String(item.id)}
id={String(item.id)}
className='absolute inset-0 z-10 h-full w-full cursor-pointer opacity-0'
/>
<Label
htmlFor={String(item.id)}
className='border-muted bg-popover hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary flex flex-col items-center justify-between rounded-md border-2 py-2'
className={cn(
'border-muted bg-popover hover:bg-accent hover:text-accent-foreground flex flex-col items-center justify-between rounded-md border-2 py-2',
String(value) === String(item.id) ? 'border-primary' : '',
)}
>
<div className='mb-3 size-12'>
<Image

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "Jméno držitele karty",
"card_number": "Číslo karty",
"element_error": "Nelze získat prvek karty.",
"expiry_date": "Datum expirace",
"loading": "Stripe.js není načten. Zkuste to prosím znovu později.",
"name_placeholder": "Zadejte jméno držitele karty",
"name_required": "Jméno držitele karty je povinné",
"pay_button": "Zaplatit nyní",
"payment_failed": "Platba se nezdařila, zkuste to prosím znovu.",
"processing": "Platba se zpracovává, prosím zkontrolujte výsledek později.",
"processing_button": "Zpracovává se...",
"secure_notice": "Vaše platební informace jsou bezpečně šifrovány",
"security_code": "Bezpečnostní kód",
"success_message": "Děkujeme za vaši platbu!",
"success_title": "Platba byla úspěšná"
},
"error": "Došlo k neočekávané chybě",
"qrcode": {
"alipay": "Naskenujte pomocí Alipay pro platbu",
"wechat_pay": "Naskenujte pomocí WeChat pro platbu"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "Karteninhabername",
"card_number": "Kartennummer",
"element_error": "Konnte das Kartenelement nicht abrufen.",
"expiry_date": "Ablaufdatum",
"loading": "Stripe.js ist nicht geladen. Bitte versuchen Sie es später erneut.",
"name_placeholder": "Geben Sie den Namen des Karteninhabers ein",
"name_required": "Der Name des Karteninhabers ist erforderlich",
"pay_button": "Jetzt bezahlen",
"payment_failed": "Zahlung fehlgeschlagen, bitte versuchen Sie es erneut.",
"processing": "Zahlung wird verarbeitet, bitte überprüfen Sie das Ergebnis später.",
"processing_button": "Wird verarbeitet...",
"secure_notice": "Ihre Zahlungsinformationen sind sicher verschlüsselt",
"security_code": "Sicherheitscode",
"success_message": "Vielen Dank für Ihre Zahlung!",
"success_title": "Zahlung Erfolgreich"
},
"error": "Ein unerwarteter Fehler ist aufgetreten",
"qrcode": {
"alipay": "Mit Alipay scannen, um zu bezahlen",
"wechat_pay": "Mit WeChat scannen, um zu bezahlen"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "Cardholder Name",
"card_number": "Card Number",
"element_error": "Unable to get card element.",
"expiry_date": "Expiry Date",
"loading": "Stripe.js is not loaded. Please try again later.",
"name_placeholder": "Enter cardholder name",
"name_required": "Cardholder name is required",
"pay_button": "Pay Now",
"payment_failed": "Payment failed, please try again.",
"processing": "Payment processing, please check the result later.",
"processing_button": "Processing...",
"secure_notice": "Your payment information is securely encrypted",
"security_code": "Security Code",
"success_message": "Thank you for your payment!",
"success_title": "Payment Successful"
},
"error": "An unexpected error occurred",
"qrcode": {
"alipay": "Scan with Alipay to pay",
"wechat_pay": "Scan with WeChat to pay"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "Nombre del titular de la tarjeta",
"card_number": "Número de tarjeta",
"element_error": "No se pudo obtener el elemento de la tarjeta.",
"expiry_date": "Fecha de caducidad",
"loading": "Stripe.js no está cargado. Por favor, inténtalo de nuevo más tarde.",
"name_placeholder": "Introduce el nombre del titular de la tarjeta",
"name_required": "Se requiere el nombre del titular de la tarjeta",
"pay_button": "Pagar Ahora",
"payment_failed": "El pago ha fallado, por favor inténtalo de nuevo.",
"processing": "Procesando el pago, por favor verifica el resultado más tarde.",
"processing_button": "Procesando...",
"secure_notice": "Tu información de pago está encriptada de forma segura",
"security_code": "Código de seguridad",
"success_message": "¡Gracias por tu pago!",
"success_title": "Pago Exitoso"
},
"error": "Ocurrió un error inesperado",
"qrcode": {
"alipay": "Escanea con Alipay para pagar",
"wechat_pay": "Escanea con WeChat para pagar"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "Nombre del titular de la tarjeta",
"card_number": "Número de tarjeta",
"element_error": "No se pudo obtener el elemento de la tarjeta.",
"expiry_date": "Fecha de expiración",
"loading": "Stripe.js no se ha cargado. Por favor, inténtalo de nuevo más tarde.",
"name_placeholder": "Ingresa el nombre del titular de la tarjeta",
"name_required": "Se requiere el nombre del titular de la tarjeta",
"pay_button": "Pagar Ahora",
"payment_failed": "El pago falló, por favor inténtalo de nuevo.",
"processing": "Procesando el pago, por favor verifica el resultado más tarde.",
"processing_button": "Procesando...",
"secure_notice": "Tu información de pago está encriptada de forma segura",
"security_code": "Código de seguridad",
"success_message": "¡Gracias por tu pago!",
"success_title": "Pago Exitoso"
},
"error": "Ocurrió un error inesperado",
"qrcode": {
"alipay": "Escanea con Alipay para pagar",
"wechat_pay": "Escanea con WeChat para pagar"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "نام دارنده کارت",
"card_number": "شماره کارت",
"element_error": "عدم توانایی در دریافت عنصر کارت.",
"expiry_date": "تاریخ انقضا",
"loading": "Stripe.js بارگذاری نشده است. لطفاً بعداً دوباره تلاش کنید.",
"name_placeholder": "نام دارنده کارت را وارد کنید",
"name_required": "نام دارنده کارت الزامی است",
"pay_button": "همین حالا پرداخت کنید",
"payment_failed": "پرداخت ناموفق بود، لطفاً دوباره تلاش کنید.",
"processing": "پرداخت در حال پردازش است، لطفاً نتیجه را بعداً بررسی کنید.",
"processing_button": "در حال پردازش...",
"secure_notice": "اطلاعات پرداخت شما به صورت ایمن رمزگذاری شده است",
"security_code": "کد امنیتی",
"success_message": "از پرداخت شما متشکریم!",
"success_title": "پرداخت موفق"
},
"error": "یک خطای غیرمنتظره رخ داده است",
"qrcode": {
"alipay": "برای پرداخت با Alipay اسکن کنید",
"wechat_pay": "برای پرداخت با WeChat اسکن کنید"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "Kortinhaltijan nimi",
"card_number": "Korttinumero",
"element_error": "Korttielementtiä ei voitu saada.",
"expiry_date": "Voimassaoloaika",
"loading": "Stripe.js ei ole ladattu. Yritä myöhemmin uudelleen.",
"name_placeholder": "Syötä kortinhaltijan nimi",
"name_required": "Kortinhaltijan nimi on pakollinen",
"pay_button": "Maksa nyt",
"payment_failed": "Maksu epäonnistui, yritä uudelleen.",
"processing": "Maksua käsitellään, tarkista tulos myöhemmin.",
"processing_button": "Käsitellään...",
"secure_notice": "Maksutietosi on salattu turvallisesti",
"security_code": "Turvakoodi",
"success_message": "Kiitos maksustasi!",
"success_title": "Maksu onnistui"
},
"error": "Odottamaton virhe tapahtui",
"qrcode": {
"alipay": "Skannaa Alipaylla maksamista varten",
"wechat_pay": "Skannaa WeChatilla maksamista varten"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "Nom du titulaire de la carte",
"card_number": "Numéro de la carte",
"element_error": "Impossible d'obtenir l'élément de carte.",
"expiry_date": "Date d'expiration",
"loading": "Stripe.js n'est pas chargé. Veuillez réessayer plus tard.",
"name_placeholder": "Entrez le nom du titulaire de la carte",
"name_required": "Le nom du titulaire de la carte est requis",
"pay_button": "Payer maintenant",
"payment_failed": "Le paiement a échoué, veuillez réessayer.",
"processing": "Traitement du paiement, veuillez vérifier le résultat plus tard.",
"processing_button": "Traitement...",
"secure_notice": "Vos informations de paiement sont cryptées de manière sécurisée",
"security_code": "Code de sécurité",
"success_message": "Merci pour votre paiement !",
"success_title": "Paiement réussi"
},
"error": "Une erreur inattendue est survenue",
"qrcode": {
"alipay": "Scannez avec Alipay pour payer",
"wechat_pay": "Scannez avec WeChat pour payer"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "कार्डधारक का नाम",
"card_number": "कार्ड नंबर",
"element_error": "कार्ड तत्व प्राप्त करने में असमर्थ।",
"expiry_date": "समाप्ति तिथि",
"loading": "Stripe.js लोड नहीं हुआ है। कृपया बाद में पुनः प्रयास करें।",
"name_placeholder": "कार्डधारक का नाम दर्ज करें",
"name_required": "कार्डधारक का नाम आवश्यक है",
"pay_button": "अभी भुगतान करें",
"payment_failed": "भुगतान विफल, कृपया पुनः प्रयास करें।",
"processing": "भुगतान प्रक्रिया में है, कृपया बाद में परिणाम जांचें।",
"processing_button": "प्रसंस्करण हो रहा है...",
"secure_notice": "आपकी भुगतान जानकारी सुरक्षित रूप से एन्क्रिप्ट की गई है",
"security_code": "सुरक्षा कोड",
"success_message": "आपके भुगतान के लिए धन्यवाद!",
"success_title": "भुगतान सफल"
},
"error": "एक अप्रत्याशित त्रुटि हुई",
"qrcode": {
"alipay": "भुगतान करने के लिए Alipay के साथ स्कैन करें",
"wechat_pay": "भुगतान करने के लिए WeChat के साथ स्कैन करें"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "Kártyabirtokos neve",
"card_number": "Kártyaszám",
"element_error": "Nem sikerült megszerezni a kártya elemet.",
"expiry_date": "Lejárati dátum",
"loading": "A Stripe.js nem töltődött be. Kérjük, próbálja újra később.",
"name_placeholder": "Írd be a kártyabirtokos nevét",
"name_required": "A kártyabirtokos neve kötelező",
"pay_button": "Fizetés most",
"payment_failed": "A fizetés meghiúsult, kérjük, próbálja újra.",
"processing": "Fizetés feldolgozása, kérjük, ellenőrizze az eredményt később.",
"processing_button": "Feldolgozás...",
"secure_notice": "A fizetési információi biztonságosan titkosítva vannak",
"security_code": "Biztonsági kód",
"success_message": "Köszönjük a fizetését!",
"success_title": "Fizetés Sikeres"
},
"error": "Váratlan hiba történt",
"qrcode": {
"alipay": "Fizesd a Alipay segítségével",
"wechat_pay": "Fizesd a WeChat segítségével"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "カード名義人",
"card_number": "カード番号",
"element_error": "カード要素を取得できませんでした。",
"expiry_date": "有効期限",
"loading": "Stripe.jsが読み込まれていません。後でもう一度お試しください。",
"name_placeholder": "カード名義人を入力してください",
"name_required": "カード名義人は必須です",
"pay_button": "今すぐ支払う",
"payment_failed": "支払いに失敗しました。もう一度お試しください。",
"processing": "支払い処理中です。結果を後で確認してください。",
"processing_button": "処理中...",
"secure_notice": "お支払い情報は安全に暗号化されています",
"security_code": "セキュリティコード",
"success_message": "お支払いありがとうございます!",
"success_title": "支払い成功"
},
"error": "予期しないエラーが発生しました",
"qrcode": {
"alipay": "Alipayで支払うにはスキャンしてください",
"wechat_pay": "WeChatで支払うにはスキャンしてください"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "카드 소지자 이름",
"card_number": "카드 번호",
"element_error": "카드 요소를 가져올 수 없습니다.",
"expiry_date": "유효 기간",
"loading": "Stripe.js가 로드되지 않았습니다. 나중에 다시 시도해 주세요.",
"name_placeholder": "카드 소지자 이름을 입력하세요",
"name_required": "카드 소지자 이름은 필수입니다",
"pay_button": "지금 결제하기",
"payment_failed": "결제에 실패했습니다. 다시 시도해 주세요.",
"processing": "결제 처리 중입니다. 결과를 나중에 확인해 주세요.",
"processing_button": "처리 중...",
"secure_notice": "귀하의 결제 정보는 안전하게 암호화됩니다.",
"security_code": "보안 코드",
"success_message": "결제해 주셔서 감사합니다!",
"success_title": "결제 성공"
},
"error": "예기치 않은 오류가 발생했습니다.",
"qrcode": {
"alipay": "Alipay로 스캔하여 결제하세요",
"wechat_pay": "WeChat으로 스캔하여 결제하세요"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "Kortholderens navn",
"card_number": "Kortnummer",
"element_error": "Kunne ikke hente kortelement.",
"expiry_date": "Utløpsdato",
"loading": "Stripe.js er ikke lastet inn. Vennligst prøv igjen senere.",
"name_placeholder": "Skriv inn kortholderens navn",
"name_required": "Kortholderens navn er påkrevd",
"pay_button": "Betal Nå",
"payment_failed": "Betaling mislyktes, vennligst prøv igjen.",
"processing": "Betaling behandles, vennligst sjekk resultatet senere.",
"processing_button": "Behandler...",
"secure_notice": "Din betalingsinformasjon er sikkert kryptert",
"security_code": "Sikkerhetskode",
"success_message": "Takk for betalingen!",
"success_title": "Betaling Vel Lykket"
},
"error": "En uventet feil oppstod",
"qrcode": {
"alipay": "Skann med Alipay for å betale",
"wechat_pay": "Skann med WeChat for å betale"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "Imię i nazwisko posiadacza karty",
"card_number": "Numer karty",
"element_error": "Nie można uzyskać elementu karty.",
"expiry_date": "Data ważności",
"loading": "Stripe.js nie został załadowany. Proszę spróbować ponownie później.",
"name_placeholder": "Wprowadź imię i nazwisko posiadacza karty",
"name_required": "Imię i nazwisko posiadacza karty jest wymagane",
"pay_button": "Zapłać teraz",
"payment_failed": "Płatność nie powiodła się, proszę spróbować ponownie.",
"processing": "Przetwarzanie płatności, proszę sprawdzić wynik później.",
"processing_button": "Przetwarzanie...",
"secure_notice": "Twoje dane płatnicze są bezpiecznie szyfrowane",
"security_code": "Kod zabezpieczający",
"success_message": "Dziękujemy za dokonanie płatności!",
"success_title": "Płatność zakończona sukcesem"
},
"error": "Wystąpił nieoczekiwany błąd",
"qrcode": {
"alipay": "Skanuj z Alipay, aby zapłacić",
"wechat_pay": "Skanuj z WeChat, aby zapłacić"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "Nome do Titular",
"card_number": "Número do Cartão",
"element_error": "Não foi possível obter o elemento do cartão.",
"expiry_date": "Data de Validade",
"loading": "Stripe.js não está carregado. Por favor, tente novamente mais tarde.",
"name_placeholder": "Digite o nome do titular",
"name_required": "O nome do titular é obrigatório",
"pay_button": "Pagar Agora",
"payment_failed": "Pagamento falhou, por favor tente novamente.",
"processing": "Processando pagamento, por favor verifique o resultado mais tarde.",
"processing_button": "Processando...",
"secure_notice": "Suas informações de pagamento estão criptografadas com segurança",
"security_code": "Código de Segurança",
"success_message": "Obrigado pelo seu pagamento!",
"success_title": "Pagamento Bem-Sucedido"
},
"error": "Ocorreu um erro inesperado",
"qrcode": {
"alipay": "Escaneie com Alipay para pagar",
"wechat_pay": "Escaneie com WeChat para pagar"
}
}
}

View File

@ -19,6 +19,7 @@ export default getRequestConfig(async () => {
profile: (await import(`./${locale}/profile.json`)).default,
subscribe: (await import(`./${locale}/subscribe.json`)).default,
order: (await import(`./${locale}/order.json`)).default,
payment: (await import(`./${locale}/payment.json`)).default,
wallet: (await import(`./${locale}/wallet.json`)).default,
ticket: (await import(`./${locale}/ticket.json`)).default,
document: (await import(`./${locale}/document.json`)).default,

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "Numele titularului cardului",
"card_number": "Numărul cardului",
"element_error": "Nu s-a putut obține elementul cardului.",
"expiry_date": "Data expirării",
"loading": "Stripe.js nu este încărcat. Vă rugăm să încercați din nou mai târziu.",
"name_placeholder": "Introduceți numele titularului cardului",
"name_required": "Numele titularului cardului este necesar",
"pay_button": "Plătește acum",
"payment_failed": "Plata a eșuat, vă rugăm să încercați din nou.",
"processing": "Plata este în procesare, vă rugăm să verificați rezultatul mai târziu.",
"processing_button": "Se procesează...",
"secure_notice": "Informațiile dumneavoastră de plată sunt criptate în siguranță",
"security_code": "Cod de securitate",
"success_message": "Vă mulțumim pentru plata dumneavoastră!",
"success_title": "Plata a fost efectuată cu succes"
},
"error": "A apărut o eroare neașteptată",
"qrcode": {
"alipay": "Scanați cu Alipay pentru a plăti",
"wechat_pay": "Scanați cu WeChat pentru a plăti"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "Имя держателя карты",
"card_number": "Номер карты",
"element_error": "Не удалось получить элемент карты.",
"expiry_date": "Срок действия",
"loading": "Stripe.js не загружен. Пожалуйста, попробуйте позже.",
"name_placeholder": "Введите имя держателя карты",
"name_required": "Имя держателя карты обязательно",
"pay_button": "Оплатить сейчас",
"payment_failed": "Платеж не удался, пожалуйста, попробуйте снова.",
"processing": "Обработка платежа, пожалуйста, проверьте результат позже.",
"processing_button": "Обработка...",
"secure_notice": "Ваша платежная информация надежно зашифрована",
"security_code": "Код безопасности",
"success_message": "Спасибо за ваш платеж!",
"success_title": "Платеж успешен"
},
"error": "Произошла неожиданная ошибка",
"qrcode": {
"alipay": "Сканируйте с помощью Alipay для оплаты",
"wechat_pay": "Сканируйте с помощью WeChat для оплаты"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "ชื่อผู้ถือบัตร",
"card_number": "หมายเลขบัตร",
"element_error": "ไม่สามารถดึงข้อมูลองค์ประกอบบัตรได้.",
"expiry_date": "วันหมดอายุ",
"loading": "ไม่สามารถโหลด Stripe.js ได้ กรุณาลองใหม่ในภายหลัง.",
"name_placeholder": "กรอกชื่อผู้ถือบัตร",
"name_required": "ชื่อผู้ถือบัตรเป็นสิ่งจำเป็น",
"pay_button": "ชำระเงินตอนนี้",
"payment_failed": "การชำระเงินล้มเหลว กรุณาลองใหม่อีกครั้ง.",
"processing": "กำลังประมวลผลการชำระเงิน กรุณาตรวจสอบผลในภายหลัง.",
"processing_button": "กำลังประมวลผล...",
"secure_notice": "ข้อมูลการชำระเงินของคุณถูกเข้ารหัสอย่างปลอดภัย",
"security_code": "รหัสความปลอดภัย",
"success_message": "ขอบคุณสำหรับการชำระเงินของคุณ!",
"success_title": "การชำระเงินสำเร็จ"
},
"error": "เกิดข้อผิดพลาดที่ไม่คาดคิด",
"qrcode": {
"alipay": "สแกนด้วย Alipay เพื่อชำระเงิน",
"wechat_pay": "สแกนด้วย WeChat เพื่อชำระเงิน"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "Kart Sahibi Adı",
"card_number": "Kart Numarası",
"element_error": "Kart öğesi alınamadı.",
"expiry_date": "Son Kullanma Tarihi",
"loading": "Stripe.js yüklenmedi. Lütfen daha sonra tekrar deneyin.",
"name_placeholder": "Kart sahibi adını girin",
"name_required": "Kart sahibi adı gereklidir",
"pay_button": "Şimdi Öde",
"payment_failed": "Ödeme başarısız oldu, lütfen tekrar deneyin.",
"processing": "Ödeme işleniyor, lütfen sonucu daha sonra kontrol edin.",
"processing_button": "İşleniyor...",
"secure_notice": "Ödeme bilgileriniz güvenli bir şekilde şifrelenmiştir.",
"security_code": "Güvenlik Kodu",
"success_message": "Ödemeniz için teşekkür ederiz!",
"success_title": "Ödeme Başarılı"
},
"error": "Beklenmedik bir hata oluştu.",
"qrcode": {
"alipay": "Ödemek için Alipay ile tara",
"wechat_pay": "Ödemek için WeChat ile tara"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "Ім'я власника картки",
"card_number": "Номер картки",
"element_error": "Не вдалося отримати елемент картки.",
"expiry_date": "Термін дії",
"loading": "Stripe.js не завантажено. Будь ласка, спробуйте ще раз пізніше.",
"name_placeholder": "Введіть ім'я власника картки",
"name_required": "Ім'я власника картки обов'язкове",
"pay_button": "Оплатити зараз",
"payment_failed": "Платіж не вдався, будь ласка, спробуйте ще раз.",
"processing": "Обробка платежу, будь ласка, перевірте результат пізніше.",
"processing_button": "Обробка...",
"secure_notice": "Ваша платіжна інформація надійно зашифрована",
"security_code": "Код безпеки",
"success_message": "Дякуємо за ваш платіж!",
"success_title": "Платіж успішний"
},
"error": "Сталася несподівана помилка",
"qrcode": {
"alipay": "Скануйте з Alipay для оплати",
"wechat_pay": "Скануйте з WeChat для оплати"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "Tên chủ thẻ",
"card_number": "Số thẻ",
"element_error": "Không thể lấy phần tử thẻ.",
"expiry_date": "Ngày hết hạn",
"loading": "Stripe.js chưa được tải. Vui lòng thử lại sau.",
"name_placeholder": "Nhập tên chủ thẻ",
"name_required": "Tên chủ thẻ là bắt buộc",
"pay_button": "Thanh toán ngay",
"payment_failed": "Thanh toán không thành công, vui lòng thử lại.",
"processing": "Đang xử lý thanh toán, vui lòng kiểm tra kết quả sau.",
"processing_button": "Đang xử lý...",
"secure_notice": "Thông tin thanh toán của bạn được mã hóa an toàn",
"security_code": "Mã bảo mật",
"success_message": "Cảm ơn bạn đã thanh toán!",
"success_title": "Thanh toán thành công"
},
"error": "Đã xảy ra lỗi không mong muốn",
"qrcode": {
"alipay": "Quét với Alipay để thanh toán",
"wechat_pay": "Quét với WeChat để thanh toán"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "持卡人姓名",
"card_number": "卡号",
"element_error": "无法获取卡片元素。",
"expiry_date": "有效期",
"loading": "Stripe.js 未加载完成,请稍后再试。",
"name_placeholder": "输入持卡人姓名",
"name_required": "持卡人姓名为必填项",
"pay_button": "立即支付",
"payment_failed": "支付失败,请重试。",
"processing": "支付处理中,请稍后查看结果。",
"processing_button": "处理中...",
"secure_notice": "您的支付信息已安全加密",
"security_code": "安全码",
"success_message": "感谢您的付款!",
"success_title": "支付成功"
},
"error": "发生意外错误",
"qrcode": {
"alipay": "使用支付宝扫描支付",
"wechat_pay": "使用微信扫描支付"
}
}
}

View File

@ -0,0 +1,26 @@
{
"stripe": {
"card": {
"card_name": "持卡人姓名",
"card_number": "卡號",
"element_error": "無法獲取卡片元素。",
"expiry_date": "到期日",
"loading": "Stripe.js 尚未加載。請稍後再試。",
"name_placeholder": "請輸入持卡人姓名",
"name_required": "持卡人姓名為必填項",
"pay_button": "立即付款",
"payment_failed": "付款失敗,請再試一次。",
"processing": "付款處理中,請稍後查看結果。",
"processing_button": "處理中...",
"secure_notice": "您的付款信息已安全加密",
"security_code": "安全碼",
"success_message": "感謝您的付款!",
"success_title": "付款成功"
},
"error": "發生意外錯誤",
"qrcode": {
"alipay": "用 Alipay 掃描付款",
"wechat_pay": "用 WeChat 掃描付款"
}
}
}

View File

@ -1,5 +1,5 @@
// @ts-ignore
// API 更新时间:
// API 唯一标识:
import * as auth from './auth';

View File

@ -1,5 +1,5 @@
// @ts-ignore
// API 更新时间:
// API 唯一标识:
import * as announcement from './announcement';