diff --git a/package-lock.json b/package-lock.json index 4e803fe..ab65428 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "no-copy-frontend", - "version": "0.44.0", + "version": "0.49.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "no-copy-frontend", - "version": "0.44.0", + "version": "0.49.0", "dependencies": { "@a2seven/yoo-checkout": "^1.1.4", "@react-input/mask": "^2.0.4", @@ -39,6 +39,7 @@ "eslint": "^9", "eslint-config-next": "16.0.3", "sass": "^1.94.2", + "types-yoomoneycheckoutwidget": "^0.0.2", "typescript": "^5" } }, @@ -7732,6 +7733,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/types-yoomoneycheckoutwidget": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/types-yoomoneycheckoutwidget/-/types-yoomoneycheckoutwidget-0.0.2.tgz", + "integrity": "sha512-Vab30GrWEE5qq65CbBcJW5deFjJ9jRVlmh+ZneaUsqnPKPlWHGwhA1ukx+o70LjqEdgK+HgMkxNlFsqchLX7ww==", + "dev": true, + "license": "MIT" + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", diff --git a/package.json b/package.json index ee396ab..7b1edf1 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "eslint": "^9", "eslint-config-next": "16.0.3", "sass": "^1.94.2", + "types-yoomoneycheckoutwidget": "^0.0.2", "typescript": "^5" } } diff --git a/src/app/actions/yooKassa.ts b/src/app/actions/yooKassa.ts index 0e95728..7ef69b5 100644 --- a/src/app/actions/yooKassa.ts +++ b/src/app/actions/yooKassa.ts @@ -3,6 +3,7 @@ import { YooCheckout, ICreatePayment } from '@a2seven/yoo-checkout'; import { getSessionData, deleteSession } from '@/app/actions/session'; import { API_BASE_URL } from '@/app/actions/definitions'; +import { id } from 'zod/v4/locales'; const checkout = new YooCheckout({ shopId: '1276731', secretKey: 'test_0Yns_0NHV5GJf6ypJ5HC4NSfnLO8SJkw-1PwrVWsDl4' }); @@ -36,6 +37,7 @@ export async function makePaymentOperation(email: string, tariffId: number, oper } export async function yooKasaCreatePayment(amount: number, tariff: number, operationType: 'TARIFF' | 'TOKEN') { + console.log('yooKasaCreatePayment'); const userEmail = await getSessionData('email'); const timestamp = Date.now(); @@ -51,21 +53,37 @@ export async function yooKasaCreatePayment(amount: number, tariff: number, opera value: amount.toLocaleString().replace(/\s/g, ''), currency: 'RUB' }, - payment_method_data: { + /* payment_method_data: { type: 'bank_card' - }, + }, */ + /* payment_method_id: "3138d708-000f-5001-9000-19865cfcca68", */ confirmation: { - type: 'redirect', - return_url: 'test' + type: 'embedded', + /* type: 'redirect', + return_url: 'test' */ }, metadata: { userMail: userEmail, tariff: tariff }, + merchant_customer_id: userEmail, capture: true }; try { + const payment = await checkout.createPayment(createPayload, idempotenceKey); + const response = await makePaymentOperation(userEmail, tariff, payment.id, operationType); + console.log(payment); + if (payment && response) { + return payment?.confirmation.confirmation_token; + } else { + return null + } + } catch (error) { + return null + } + + /* try { const payment = await checkout.createPayment(createPayload, idempotenceKey); const response = await makePaymentOperation(userEmail, tariff, payment.id, operationType); if (response && payment) { @@ -75,7 +93,7 @@ export async function yooKasaCreatePayment(amount: number, tariff: number, opera } } catch (error) { return null - } + } */ } export async function fetchPaymentsHistory() { diff --git a/src/app/ui/payment/YooMoneyWidget.tsx b/src/app/ui/payment/YooMoneyWidget.tsx new file mode 100644 index 0000000..025d6ac --- /dev/null +++ b/src/app/ui/payment/YooMoneyWidget.tsx @@ -0,0 +1,107 @@ +import { useCallback, useEffect } from 'react'; +import { + YooMoneyCheckoutWidget, + YooMoneyCheckoutWidgetConfig, + YooMoneyErrorCallbackResult +} from "types-yoomoneycheckoutwidget"; + +let isLoading = false; +let isInited = false; +export default function YooWidget({ config, onComplete, onSuccess, onFail, onModalClose }: { + /** + * Описание https://yookassa.ru/developers/payment-acceptance/integration-scenarios/widget/reference#initialization-parameters + */ + config: YooMoneyCheckoutWidgetConfig, + /** после успешной оплаты*/ + onComplete?: () => void, + /** после успешной оплаты, только если не подписались на onComplete*/ + onSuccess?: () => void, + /** только если вы отключили обработку неуспешных попыток в виджете: пользователь выбрал любой способ оплаты, но платеж не прошел; закончился срок действия токена.*/ + onFail?: () => void, + /** после закрытия модального окна платежной формы если customization.modal==true */ + onModalClose?: () => void, +}) { + const isModal = config?.customization?.modal; + let checkout: YooMoneyCheckoutWidget; + const destroy = () => { + console.log('checkout.destroy'); + checkout.destroy(); + isInited = false; + } + + const initializeWidget = useCallback( + () => { + isInited = true; + isLoading = false; + if (!isModal) { + const userErrorCallBack = config.error_callback; + config.error_callback = (error: YooMoneyErrorCallbackResult) => { + userErrorCallBack(error); + isInited = false; + }; + } + checkout = new window.YooMoneyCheckoutWidget(config); + console.log('checkout.create'); + checkout.on('complete', (result) => { + console.log('on complete', result); + onComplete && onComplete(); + destroy(); + }); + onSuccess && checkout.on('success', (result) => { + console.log('on success', result); + onSuccess(); + destroy(); + }); + onFail && checkout.on('fail', (result) => { + console.warn('on fail', result); + onFail(); + destroy(); + }); + + if (config.customization?.modal) { + checkout.on('modal_close', (result) => { + console.log('on modal_close', result); + onModalClose && onModalClose(); + destroy(); + }); + } + checkout.render(config.customization?.modal ? undefined : 'payment-form'); + + }, [config, onSuccess]); + + useEffect(() => { + console.log('isInited'); + console.log(isInited); + + if (!isLoading && !isInited) { + if (!window.YooMoneyCheckoutWidget) { + isLoading = true; + console.log('load script'); + const script = document.createElement('script'); + script.src = 'https://yookassa.ru/checkout-widget/v1/checkout-widget.js'; + script.async = true; + script.onload = initializeWidget; + document.head.appendChild(script); + return () => { + console.log('unload script'); + document.head.removeChild(script); + }; + } else { + console.log('init without loading'); + initializeWidget(); + } + } + + return () => { + if (!isInited && !isModal) { + destroy(); + } + } + }); + + return ( + <> + {isModal ? '' :
} + + ); +} \ No newline at end of file diff --git a/src/app/ui/payment/payment-tab-tariffs.tsx b/src/app/ui/payment/payment-tab-tariffs.tsx index b914972..df3324e 100644 --- a/src/app/ui/payment/payment-tab-tariffs.tsx +++ b/src/app/ui/payment/payment-tab-tariffs.tsx @@ -7,6 +7,8 @@ import { useTranslations } from 'next-intl'; import { yooKasaCreatePayment } from '@/app/actions/yooKassa'; import { useState, ReactNode, useEffect } from 'react'; import ModalWindow from '@/app/components/ModalWindow'; +import YooMoneyWidget from './YooMoneyWidget'; +import { YooMoneyCheckoutWidgetConfig, YooMoneyErrorCallbackResult } from "types-yoomoneycheckoutwidget"; interface Tariff { id: number; @@ -50,7 +52,7 @@ export default function PaymentTabTariffs() { } | null>(null); const [isProcessing, setIsProcessing] = useState(false); - async function createPayment(value: number, tariff: number) { + /* async function createPayment(value: number, tariff: number) { if (isProcessing) return; setIsProcessing(true); @@ -58,7 +60,8 @@ export default function PaymentTabTariffs() { try { const response = await yooKasaCreatePayment(value, tariff, 'TARIFF'); if (response) { - window.open(response, '_blank', 'noopener,noreferrer'); + console.log('response ok'); + //window.open(response, '_blank', 'noopener,noreferrer'); closeModal(false); } else { setErrorMessage('payment-error'); @@ -68,8 +71,65 @@ export default function PaymentTabTariffs() { } finally { setIsProcessing(false); } + } */ + + const [confirmationToken, setConfirmationToken] = useState(null); + const [showWidget, setShowWidget] = useState(false); + const [yooMoneyConfig, setYooMoneyConfig] = useState(null); + + + + async function createPayment(value: number, tariff: number) { + if (isProcessing) return; + + setIsProcessing(true); + setErrorMessage(null); + + try { + const response = await yooKasaCreatePayment(value, tariff, 'TARIFF'); + + if (response) { + console.log('response ok', response); + setConfirmationToken(response); + setShowWidget(true); + + const config: YooMoneyCheckoutWidgetConfig = { + confirmation_token: response, + error_callback: (result: YooMoneyErrorCallbackResult) => { + console.error('error_callback', result.error); + }, + customization: { + modal: true + } + } + + setYooMoneyConfig(config); + closeModal(false); + } else { + setErrorMessage('payment-error'); + } + } catch (error) { + console.error('Payment creation error:', error); + setErrorMessage('payment-error'); + } finally { + setIsProcessing(false); + } } + const handleWidgetSuccess = () => { + console.log('Payment successful'); + // Здесь можно закрыть модалку или показать сообщение об успехе + setShowWidget(false); + setConfirmationToken(null); + }; + + const handleWidgetError = (error: any) => { + console.error('Widget error:', error); + setErrorMessage('widget-error'); + setShowWidget(false); + setConfirmationToken(null); + }; + function openPaymentWindow(value: number, tariff: string, tokens: number, tariffId: number) { setSelectedTariff({ value, tariff, tokens, tariffId }); setIsProcessing(false); @@ -149,6 +209,15 @@ export default function PaymentTabTariffs() { ); } + const onComplete = () => { + console.log('Payment accepted'); + }; + const onModalClose = () => { + setYooMoneyConfig(null); + setShowWidget(false); + console.log('Payment not finished'); + } + return ( <> @@ -217,6 +286,11 @@ export default function PaymentTabTariffs() { ))}
+ + {/* Модальное окно с виджетом */} + {showWidget && yooMoneyConfig && ( + + )} ); } \ No newline at end of file