Command Palette

Search for a command to run...

Интеграция ЮKassa в витрину магазина

Для интеграции платёжного провайдера ЮKassa в storefront (витрину магазина) на Next.js начните с добавления необходимых UI-компонентов. Таким образом провайдер будет отображаться на странице оформления заказа наряду с другими доступными методами оплаты.

Когда пользователь выбирает ЮKassa, витрина должна вызвать метод с нужными параметрами. Это создаст платёжную сессию через API ЮKassa и подготовит покупателя к перенаправлению. После этого кнопка Place Order должна отправить пользователя на страницу оплаты ЮKassa, где он сможет выбрать предпочтительный способ оплаты.

После завершения оплаты ЮKassa одновременно отправит вебхук и перенаправит покупателя обратно в витрину. То событие, которое придёт первым, завершит корзину и создаст новый заказ в Medusa.

Для витрины на Next.js необходимо внести следующие изменения:

Шаг 1: Настройте платёжный провайдер

Чтобы сделать ЮKassa доступным в качестве способа оплаты на странице оформления заказа витрины магазина, необходимо добавить её конфигурацию в маппинг платёжных провайдеров в файле с константами вашей витрины. Этот маппинг определяет как каждый провайдер отображается в интерфейсе.

Откройте и добавьте следующий код:

Структура проекта Medusa Storefront после обновления файла с константами

src/lib/constants.tsx
1export const paymentInfoMap: Record<
2 string,
3 { title: string; icon: React.ReactNode }
4> = {
5 // ... другие провайдеры
6 pp_yookassa_yookassa: {
7 title: "YooKassa",
8 icon: <CreditCard />,
9 }
10}
11
12// Вспомогательная функция для проверки, является ли провайдер ЮKassa
13export const isYookassa = (providerId?: string) => {
14 return providerId?.startsWith("pp_yookassa")
15}

Вы расширяете объект , добавляя в него запись . Эта запись определяет заголовок и иконку, которые будут отображаться для ЮKassa на странице оформления заказа.

Вспомогательная функция проверяет, принадлежит ли переданный к ЮKassa. Это используется при рендеринге UI-компонентов, специфичных для конкретного провайдера.


Шаг 2: Обновите настройки cookies

При подключении ЮKassa настройте политику cookie так, чтобы поддерживались междоменные редиректы. Это нужно для сохранения платёжной сессии при возврате пользователя в магазин.

Откройте и обновите конфигурацию файлов cookie следующим образом:

Структура проекта Medusa Storefront после обновления файлов cookie

src/lib/data/cookies.ts
1export const setCartId = async (cartId: string) => {
2 cookies.set("_medusa_cart_id", cartId, {
3 // ... другие настройки cookie
4 sameSite: "lax", // Переключено с режима «Strict» для междоменных редиректов
5 })
6}

Эта вспомогательная функция сохраняет идентификатор корзины в cookie с именем .

Опция установлена в значение вместо . Это изменение гарантирует, что cookie будет отправляться при кросс-доменных запросах во время процесса редиректа через ЮKassa, предотвращая потерю платёжной сессии.


Шаг 3: Инициализируйте платёжную сессию

Чтобы перенаправить покупателя в ЮKassa, платёжная сессия должна быть корректно инициализирована с необходимыми параметрами, включая return URL после оплаты и корзину для формирования онлайн-чеков.

Откройте и обновите логику инициализации платежа, включив в нее данные корзины и URL возврата для ЮKassa:

Структура проекта Medusa Storefront после обновления файла для компонента оплаты

src/modules/checkout/components/payment/index.tsx
1import { getBaseURL } from "@lib/util/env"
2
3const Payment = ({
4 // ...
5}) => {
6 // ...
7 const handleSubmit = async () => {
8 setIsLoading(true)
9 try {
10 // ...
11 const countryCode = cart?.shipping_address?.country_code
12 if (!checkActiveSession) {
13 await initiatePaymentSession(cart, {
14 provider_id: selectedPaymentMethod,
15 data: {
16 confirmation: {
17 type: "redirect",
18 return_url: `${getBaseURL()}/api/capture-payment/${cart?.id}?country_code=${countryCode}`
19 },
20 cart: cart
21 }
22 })
23 }
24 }
25 }
26}

При инициировании платёжной сессии для ЮKassa передайте объект с и . Позже ЮKassa предоставит , на которое клиент должен быть перенаправлен.

Параметр указывает на конечную точку захвата вашего магазина и используется как для успешных, так и для неудачных попыток оплаты.

Объект включается в данные инициализации для формирования чека в соответствии с Федеральным законом № 54.


Шаг 4: Создайте кнопку оплаты

В витрине для каждого платёжного провайдера необходим отдельный компонент кнопки оплаты. Он отвечает за обработку оформления заказа после подтверждения пользователем и, используя данные платёжного сеанса, перенаправляет его на страницу оплаты ЮKassa.

Откройте и добавьте следующий код:

Структура проекта Medusa Storefront после обновления файла для компонента кнопки оплаты

src/modules/checkout/components/payment-button/index.tsx
1import {
2 // ...
3 isYookassa
4} from "@lib/constants"
5import { useRouter } from "next/navigation"
6
7const PaymentButton: React.FC<PaymentButtonProps> = ({
8 cart,
9 "data-testid": dataTestId,
10}) => {
11 // ...
12 switch (true) {
13 // ... другие проверки
14 case isYookassa(paymentSession?.provider_id):
15 return (
16 <YookassaPaymentButton
17 notReady={notReady}
18 cart={cart}
19 data-testid={dataTestId}
20 />
21 )
22 default:
23 return <Button disabled>Select a payment method</Button>
24 }
25}
26
27// ... другие компоненты кнопок оплаты
28
29const YookassaPaymentButton = ({
30 cart,
31 notReady
32}: {
33 cart: HttpTypes.StoreCart
34 notReady: boolean
35 "data-testid"?: string
36}) => {
37 const [submitting, setSubmitting] = useState(false)
38 const [errorMessage, setErrorMessage] = useState<string | null>(null)
39 const router = useRouter()
40
41 const paymentSession = cart.payment_collection?.payment_sessions?.find(
42 session => session.provider_id === "pp_yookassa_yookassa"
43 )
44
45 const handlePayment = () => {
46 setSubmitting(true)
47
48 const confirmation = paymentSession?.data?.confirmation as any
49 if (confirmation?.confirmation_url) {
50 router.push(confirmation.confirmation_url)
51 }
52 }
53
54 return (
55 <>
56 <Button
57 disabled={notReady}
58 isLoading={submitting}
59 onClick={handlePayment}
60 size="large"
61 data-testid="submit-order-button"
62 >
63 Place an order and pay
64 </Button>
65 <ErrorMessage
66 error={errorMessage}
67 data-testid="manual-payment-error-message"
68 />
69 </>
70 )
71}

Этот компонент находит ЮKassa в активной корзине и извлекает значение . Когда пользователь нажимает кнопку Place an order, он перенаправляется на этот URL для завершения оплаты на странице ЮKassa.

Если отсутствует, компонент отображает сообщение об ошибке вместо продолжения процесса. Состояние обеспечивает визуальную обратную связь во время подготовки перенаправления.

Родительский компонент использует функцию , чтобы определить, нужно ли отобразить для текущей сессии; в противном случае показывается неактивная кнопка Select a payment method.


Шаг 5: Создайте API-роут для подтверждения платежа

После того как покупатель завершает оплату на странице ЮKassa, он перенаправляется обратно на витрину магазина. Необходимо создать API-роут, который обработает этот callback, проверит статус платежа и завершит корзину.

Создайте файл со следующим содержимым:

Структура проекта Medusa Storefront после создания файла для API route

src/app/api/capture-payment/[cartId]/route.ts
1import { NextRequest, NextResponse } from "next/server"
2import { revalidateTag } from "next/cache"
3import {
4 getCacheTag,
5 getAuthHeaders,
6 removeCartId
7} from "@lib/data/cookies"
8import { sdk } from "@lib/config"
9import { placeOrder } from "@lib/data/cart"
10
11type Params = Promise<{ cartId: string }>
12
13export async function GET(req: NextRequest, { params }: { params: Params }) {
14 const { cartId } = await params
15 const { origin, searchParams } = req.nextUrl
16
17 const countryCode = searchParams.get("country_code") || ""
18 const headers = { ...(await getAuthHeaders()) }
19
20 // Retrieve fresh cart values
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 headers
27 )
28 if (!cart) {
29 return NextResponse.redirect(`${origin}/${countryCode}`)
30 }
31
32 const orderId = (cart as unknown as Record<string, any>).order_link?.order_id
33 if (!orderId) {
34 await placeOrder(cartId)
35 // Fail when payment not authorized
36 return NextResponse.redirect(
37 `${origin}/${countryCode}/checkout?step=review&error=payment_failed`
38 )
39 }
40
41 const orderCacheTag = await getCacheTag("orders")
42 revalidateTag(orderCacheTag)
43 removeCartId()
44 return NextResponse.redirect(
45 `${origin}/${countryCode}/order/${orderId}/confirmed`
46 )
47}

Этот API-роут обрабатывает редирект от ЮKassa после попытки оплаты. Он получает актуальное состояние корзины, чтобы убедиться, что все изменения, внесённые во время оплаты, были отражены.

Если в корзине нет связанного идентификатора заказа, обработчик роута пытается оформить заказ. В случае успеха покупатель перенаправляется на страницу подтверждения заказа. Если же при обработке корзины возникла ошибка, покупатель возвращается на страницу оформления заказа с указанием ошибки и может повторить процесс оплаты заказа.

Когда оплата проходит успешно, роут повторно валидирует кэшированные данные корзины и заказа, удаляет cookie корзины и перенаправляет покупателя на страницу подтверждения заказа. Это гарантирует корректное завершение платёжного процесса и сохранение актуальных данных в витрине.


Полный пример кода интеграции в витрину

Вы можете ознакомиться с изменениями, внесенными в стартовый шаблон Medusa Next.js Starter Template в директории .

Полный код интеграции можно посмотреть в разделе comparison page, откройте вкладку и изучите различия в каталоге . Или запустите в терминале:

Terminal
1git clone https://github.com/gorgojs/medusa-plugins
2cd medusa-plugins
3git diff @gorgo/medusa-payment-yookassa@0.0.1...main -- examples/payment-yookassa/medusa-storefront
Изменено 5 июня 2026 г.·Редактировать страницу