add YooMoneyWidget, change payments handler

This commit is contained in:
smanylov
2026-03-03 18:48:09 +07:00
parent 3e3a5df34d
commit a53c1b4b49
5 changed files with 217 additions and 9 deletions
+10 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "no-copy-frontend", "name": "no-copy-frontend",
"version": "0.44.0", "version": "0.49.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "no-copy-frontend", "name": "no-copy-frontend",
"version": "0.44.0", "version": "0.49.0",
"dependencies": { "dependencies": {
"@a2seven/yoo-checkout": "^1.1.4", "@a2seven/yoo-checkout": "^1.1.4",
"@react-input/mask": "^2.0.4", "@react-input/mask": "^2.0.4",
@@ -39,6 +39,7 @@
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "16.0.3", "eslint-config-next": "16.0.3",
"sass": "^1.94.2", "sass": "^1.94.2",
"types-yoomoneycheckoutwidget": "^0.0.2",
"typescript": "^5" "typescript": "^5"
} }
}, },
@@ -7732,6 +7733,13 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/typescript": {
"version": "5.9.3", "version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+1
View File
@@ -40,6 +40,7 @@
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "16.0.3", "eslint-config-next": "16.0.3",
"sass": "^1.94.2", "sass": "^1.94.2",
"types-yoomoneycheckoutwidget": "^0.0.2",
"typescript": "^5" "typescript": "^5"
} }
} }
+23 -5
View File
@@ -3,6 +3,7 @@
import { YooCheckout, ICreatePayment } from '@a2seven/yoo-checkout'; import { YooCheckout, ICreatePayment } from '@a2seven/yoo-checkout';
import { getSessionData, deleteSession } from '@/app/actions/session'; import { getSessionData, deleteSession } from '@/app/actions/session';
import { API_BASE_URL } from '@/app/actions/definitions'; 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' }); 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') { export async function yooKasaCreatePayment(amount: number, tariff: number, operationType: 'TARIFF' | 'TOKEN') {
console.log('yooKasaCreatePayment');
const userEmail = await getSessionData('email'); const userEmail = await getSessionData('email');
const timestamp = Date.now(); const timestamp = Date.now();
@@ -51,21 +53,37 @@ export async function yooKasaCreatePayment(amount: number, tariff: number, opera
value: amount.toLocaleString().replace(/\s/g, ''), value: amount.toLocaleString().replace(/\s/g, ''),
currency: 'RUB' currency: 'RUB'
}, },
payment_method_data: { /* payment_method_data: {
type: 'bank_card' type: 'bank_card'
}, }, */
/* payment_method_id: "3138d708-000f-5001-9000-19865cfcca68", */
confirmation: { confirmation: {
type: 'redirect', type: 'embedded',
return_url: 'test' /* type: 'redirect',
return_url: 'test' */
}, },
metadata: { metadata: {
userMail: userEmail, userMail: userEmail,
tariff: tariff tariff: tariff
}, },
merchant_customer_id: userEmail,
capture: true capture: true
}; };
try { 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 payment = await checkout.createPayment(createPayload, idempotenceKey);
const response = await makePaymentOperation(userEmail, tariff, payment.id, operationType); const response = await makePaymentOperation(userEmail, tariff, payment.id, operationType);
if (response && payment) { if (response && payment) {
@@ -75,7 +93,7 @@ export async function yooKasaCreatePayment(amount: number, tariff: number, opera
} }
} catch (error) { } catch (error) {
return null return null
} } */
} }
export async function fetchPaymentsHistory() { export async function fetchPaymentsHistory() {
+107
View File
@@ -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 ? '' : <div id="payment-form" />}
</>
);
}
+76 -2
View File
@@ -7,6 +7,8 @@ import { useTranslations } from 'next-intl';
import { yooKasaCreatePayment } from '@/app/actions/yooKassa'; import { yooKasaCreatePayment } from '@/app/actions/yooKassa';
import { useState, ReactNode, useEffect } from 'react'; import { useState, ReactNode, useEffect } from 'react';
import ModalWindow from '@/app/components/ModalWindow'; import ModalWindow from '@/app/components/ModalWindow';
import YooMoneyWidget from './YooMoneyWidget';
import { YooMoneyCheckoutWidgetConfig, YooMoneyErrorCallbackResult } from "types-yoomoneycheckoutwidget";
interface Tariff { interface Tariff {
id: number; id: number;
@@ -50,7 +52,7 @@ export default function PaymentTabTariffs() {
} | null>(null); } | null>(null);
const [isProcessing, setIsProcessing] = useState<boolean>(false); const [isProcessing, setIsProcessing] = useState<boolean>(false);
async function createPayment(value: number, tariff: number) { /* async function createPayment(value: number, tariff: number) {
if (isProcessing) return; if (isProcessing) return;
setIsProcessing(true); setIsProcessing(true);
@@ -58,7 +60,8 @@ export default function PaymentTabTariffs() {
try { try {
const response = await yooKasaCreatePayment(value, tariff, 'TARIFF'); const response = await yooKasaCreatePayment(value, tariff, 'TARIFF');
if (response) { if (response) {
window.open(response, '_blank', 'noopener,noreferrer'); console.log('response ok');
//window.open(response, '_blank', 'noopener,noreferrer');
closeModal(false); closeModal(false);
} else { } else {
setErrorMessage('payment-error'); setErrorMessage('payment-error');
@@ -68,8 +71,65 @@ export default function PaymentTabTariffs() {
} finally { } finally {
setIsProcessing(false); setIsProcessing(false);
} }
} */
const [confirmationToken, setConfirmationToken] = useState<string | null>(null);
const [showWidget, setShowWidget] = useState<boolean>(false);
const [yooMoneyConfig, setYooMoneyConfig] = useState<YooMoneyCheckoutWidgetConfig | null>(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) { function openPaymentWindow(value: number, tariff: string, tokens: number, tariffId: number) {
setSelectedTariff({ value, tariff, tokens, tariffId }); setSelectedTariff({ value, tariff, tokens, tariffId });
setIsProcessing(false); 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 ( return (
<> <>
<ModalWindow state={openWindow} callBack={closeModal}> <ModalWindow state={openWindow} callBack={closeModal}>
@@ -217,6 +286,11 @@ export default function PaymentTabTariffs() {
))} ))}
</div> </div>
</div> </div>
{/* Модальное окно с виджетом */}
{showWidget && yooMoneyConfig && (
<YooMoneyWidget config={yooMoneyConfig} onComplete={onComplete} onModalClose={onModalClose} />
)}
</> </>
); );
} }