Search for a command to run...
Для интеграции платёжного провайдера Т-Касса от Т-Банка в storefront (витрину магазина) на Next.js начните с добавления необходимых UI-компонентов. Таким образом провайдер будет отображаться на странице оформления заказа наряду с другими доступными методами оплаты.
Когда пользователь выбирает Т-Касса, витрина должна вызвать метод с нужными параметрами. Это создаст платёжную сессию через API Т-Касса и подготовит покупателя к перенаправлению. После этого кнопка Place Order должна отправить пользователя на страницу оплаты Т-Касса, где он сможет выбрать предпочтительный способ оплаты.
После завершения оплаты Т-Касса одновременно отправит вебхук и перенаправит покупателя обратно в витрину. То событие, которое придёт первым, завершит корзину и создаст новый заказ в Medusa.
Для витрины на Next.js необходимо внести следующие изменения:
Чтобы сделать Т-Касса доступным в качестве способа оплаты на странице оформления заказа витрины магазина, необходимо добавить её конфигурацию в маппинг платёжных провайдеров в файле с константами вашей витрины. Этот маппинг определяет как каждый провайдер отображается в интерфейсе.
Откройте и добавьте следующий код:
src/lib/constants.tsx1export const paymentInfoMap: Record<2 string,3 { title: string; icon: React.ReactNode }4> = {5 // ... другие провайдеры6 pp_tkassa_tkassa: {7 title: "T-Kassa",8 icon: <CreditCard />,9 },10}1112// Вспомогательная функция для проверки, является ли провайдер Т-Касса13export const isTkassa = (providerId?: string) => {14 return providerId?.startsWith("pp_tkassa")15}
Вы расширяете объект , добавляя в него запись . Эта запись определяет заголовок и иконку, которые будут отображаться для Т-Касса на странице оформления заказа.
Вспомогательная функция проверяет, принадлежит ли переданный к Т-Касса. Это используется при рендеринге UI-компонентов, специфичных для конкретного провайдера.
При подключении Т-Касса настройте политику cookie так, чтобы поддерживались междоменные редиректы. Это нужно для сохранения платёжной сессии при возврате пользователя в магазин.
Откройте и обновите конфигурацию файлов cookie следующим образом:
src/lib/data/cookies.ts1export const setCartId = async (cartId: string) => {2 cookies.set("_medusa_cart_id", cartId, {3 // ... другие настройки cookie4 sameSite: "lax", // Переключено с режима «Strict» для междоменных редиректов5 })6}
Эта вспомогательная функция сохраняет идентификатор корзины в cookie с именем .
Опция установлена в значение вместо . Это изменение гарантирует, что cookie будет отправляться при кросс-доменных запросах во время процесса редиректа через Т-Касса, предотвращая потерю платёжной сессии.
Чтобы перенаправить покупателя в Т-Касса, платёжная сессия должна быть корректно инициализирована с обязательными параметрами, включая return URL для обоих случаев: успешной и неуспешной оплаты.
Откройте и обновите логику инициализации платежа, включив в нее данные корзины и URL возврата для Т-Касса:
src/modules/checkout/components/payment/index.tsx1import { getBaseURL } from "@lib/util/env"23const Payment = ({4 // ...5}) => {6 // ...7 const handleSubmit = async () => {8 setIsLoading(true)9 try {10 // ...11 const countryCode = cart?.shipping_address?.country_code12 if (!checkActiveSession) {13 await initiatePaymentSession(cart, {14 provider_id: selectedPaymentMethod,15 data: {16 SuccessURL: `${getBaseURL()}/api/capture-payment/${cart?.id}?country_code=${countryCode}`,17 FailURL: `${getBaseURL()}/api/capture-payment/${cart?.id}?country_code=${countryCode}`,18 cart: cart,19 }20 })21 }22 }23 }24}
При инициализации платёжной сессии для Т-Касса параметры и определяют, куда будет перенаправлен покупатель после попытки оплаты. Оба URL динамически формируются на основе базового URL витрины, идентификатора корзины и выбранного кода страны.
Объект включается в данные инициализации для формирования чека в соответствии с Федеральным законом № 54.
В витрине для каждого платёжного провайдера необходим отдельный компонент кнопки оплаты. Он отвечает за обработку оформления заказа после подтверждения пользователем и, используя данные платёжного сеанса, перенаправляет его на страницу оплаты Т-Касса.
Откройте и добавьте следующий код:
src/modules/checkout/components/payment-button/index.tsx1import {2 // ...3 isTkassa4} from "@lib/constants"5import { useRouter } from "next/navigation"67const PaymentButton: React.FC<PaymentButtonProps> = ({8 cart,9 "data-testid": dataTestId,10}) => {11 // ...12 switch (true) {13 // ... другие проверки14 case isTkassa(paymentSession?.provider_id):15 return (16 <TkassaPaymentButton17 notReady={notReady}18 cart={cart}19 data-testid={dataTestId}20 />21 )22 // ... другие проверки23 }24}2526// ... другие компоненты кнопок оплаты2728type TkassaPaymentProps = {29 cart: HttpTypes.StoreCart30 notReady: boolean31 "data-testid"?: string32}3334const TkassaPaymentButton: React.FC<TkassaPaymentProps> = ({35 cart,36 notReady,37 "data-testid": dataTestId,38}) => {39 const [submitting, setSubmitting] = useState(false)40 const [errorMessage, setErrorMessage] = useState<string | null>(null)41 const router = useRouter()4243 const paymentSession = cart.payment_collection?.payment_sessions?.find(44 session =>45 session.provider_id === "pp_tkassa_tkassa"46 )4748 const handlePayment = () => {49 setSubmitting(true)50 const paymentUrl = (paymentSession?.data as any).PaymentURL51 if (paymentUrl) {52 router.push(paymentUrl)53 } else {54 setErrorMessage("Payment URL отсутствует")55 setSubmitting(false)56 }57 }5859 return (60 <>61 <Button62 disabled={notReady}63 isLoading={submitting}64 onClick={handlePayment}65 data-testid={dataTestId}66 size="large"67 >68 Place order69 </Button>70 <ErrorMessage71 error={errorMessage}72 data-testid="tkassa-payment-error-message"73 />74 </>75 )76}
Этот компонент находит для Т-Касса в текущей корзине и извлекает , предоставленный бэкендом. При нажатии кнопки Place order покупатель перенаправляется на этот URL для завершения транзакции на странице оплаты Т-Касса.
Если отсутствует, компонент выводит сообщение об ошибке, не позволяя продолжить. Состояние обеспечивает визуальную обратную связь во время подготовки перенаправления.
Компонент определяет, принадлежит ли текущая платёжная сессия к Т-Касса, с помощью вспомогательной функции . Если да, он рендерит для обработки процесса оплаты.
Интеграция этого компонента гарантирует, что процесс оплаты через Т-Касса будет запускаться при оформлении заказа.
После того как покупатель завершает оплату на странице Т-Касса, он перенаправляется обратно на витрину магазина. Необходимо создать API-роут, который обработает этот callback, проверит статус платежа и завершит корзину.
Создайте файл со следующим содержимым:
src/app/api/capture-payment/[cartId]/route.ts1import { NextRequest, NextResponse } from "next/server"2import { revalidateTag } from "next/cache"3import {4 getCacheTag,5 getAuthHeaders,6 removeCartId7} from "@lib/data/cookies"8import { sdk } from "@lib/config"9import { placeOrder } from "@lib/data/cart"1011type Params = Promise<{ cartId: string }>1213export async function GET(req: NextRequest, { params }: { params: Params }) {14 const { cartId } = await params15 const { origin, searchParams } = req.nextUrl1617 const countryCode = searchParams.get("country_code") || ""18 const headers = { ...(await getAuthHeaders()) }1920 // Получить актуальные значения корзины21 const cartCacheTag = await getCacheTag("carts")22 revalidateTag(cartCacheTag)23 const { cart } = await sdk.store.cart.retrieve(cartId, {24 fields: "id, order_link.order_id, items.total"25 },26 headers27 )28 if (!cart) {29 return NextResponse.redirect(`${origin}/${countryCode}`)30 }3132 const orderId = (cart as unknown as Record<string, any>).order_link?.order_id33 if (!orderId) {34 await placeOrder(cartId)35 // Ошибка при неавторизованном платеже36 return NextResponse.redirect(37 `${origin}/${countryCode}/checkout?step=review&error=payment_failed`38 )39 }4041 const orderCacheTag = await getCacheTag("orders")42 revalidateTag(orderCacheTag)43 removeCartId()44 return NextResponse.redirect(45 `${origin}/${countryCode}/order/${orderId}/confirmed`46 )47}
Этот API-роут обрабатывает редирект от Т-Касса после попытки оплаты. Он получает актуальное состояние корзины, чтобы убедиться, что все изменения, внесённые во время оплаты, были отражены.
Если в корзине нет связанного идентификатора заказа, обработчик роута пытается оформить заказ. В случае успеха покупатель перенаправляется на страницу подтверждения заказа. Если же при обработке корзины возникла ошибка, покупатель возвращается на страницу оформления заказа с указанием ошибки и может повторить процесс оплаты заказа.
Когда оплата проходит успешно, роут повторно валидирует кэшированные данные корзины и заказа, удаляет cookie корзины и перенаправляет покупателя на страницу подтверждения заказа. Это гарантирует корректное завершение платёжного процесса и сохранение актуальных данных в витрине.
Вы можете ознакомиться с изменениями, внесенными в стартовый шаблон Medusa Next.js Starter Template, в директории .
Полный код интеграции можно посмотреть в разделе сomparison page, откройте вкладку и изучите различия в каталоге . Или запустите в терминале:
Terminal1git clone https://github.com/gorgojs/medusa-plugins2cd medusa-plugins3git diff @gorgo/medusa-payment-tkassa@0.0.1...main -- examples/payment-tkassa/medusa-storefront