Medusa plugins
R

Robokassa

Robokassa payment provider for Medusa

Need customizations for this plugin?

Get in touch
npm install @gorgo/medusa-payment-robokassa
Category
other
Built by
gorgo
Type
unknown
Last updated
3 weeks ago
Monthly downloads
367
Github stars
7

Возможности

  • 🔗  Бесшовная интеграция с платежной системой Robokassa
  • 🧾  Формирование онлайн-чеков в соответствии с 54-ФЗ
  • 1️⃣  Поддержка одностадийных (автосписание) и  2️⃣  двухстадийных (авторизация/холдирование) сценариев оплаты
  • 🔔  Вебхук-уведомления о статусах платежей в реальном времени
  • 🔄  Возвраты и отмена заказов
  • 🛡  Проверка подписи вебхуков для обеспечения безопасности
  • ⚙️  Поддержка тестового режима для имитация оплаты без реальных списаний
  • 🔍  Подробное логирование для отладки и аудита транзакций

💬  Чат поддержки плагина Robokassa

Есть вопросы или идеи по новым функциям плагина?
Присоединяйтесь к чату в Telegram – @medusajs_robokassa

👥  Чат сообщества Medusa.js

Общайтесь в Telegram с другими разработчиками Medusa – @medusajs_chat

Требования

Установка

yarn add @gorgo/medusa-payment-robokassa
# или
npm install @gorgo/medusa-payment-robokassa

Настройка

Добавьте конфигурацию провайдера в вашего приложения Medusa:

// ...
module.exports = defineConfig({
// ...
modules: [
{
resolve: "@medusajs/medusa/payment",
options: {
providers: [
{
resolve: "@gorgo/medusa-payment-robokassa/providers/payment-robokassa",
id: "robokassa",
options: {
merchantLogin: process.env.ROBOKASSA_MERCHANT_LOGIN,
hashAlgorithm: process.env.ROBOKASSA_HASH_ALGORITHM,
password1: process.env.ROBOKASSA_PASSWORD_1,
password2: process.env.ROBOKASSA_PASSWORD_2,
testPassword1: process.env.ROBOKASSA_TEST_PASSWORD_1,
testPassword2: process.env.ROBOKASSA_TEST_PASSWORD_2,
capture: false, // по умолчанию true
isTest: true, // по умолчанию false
},
}
]
}
}
]
})

Добавьте следующие переменные окружения: идентификатор магазина , алгоритм подписи , секретные пароли , , а также тестовые пароли , :

Важно! Значение должно совпадать с настройкой в аккаунте Robokassa и быть одним из: , , , , или (обратите внимание: для возможны ошибки на стороне провайдера).

В настройках магазина Robokassa укажите Метод отправки данных на Result URL или , и задайте Result URL в формате:

Параметры провайдера

ОпцияОписаниеОбязательныйПо умолчанию
Идентификатор магазина RobokassaДа-
Алгоритм подписи: , , , , Да-
Технический пароль №1 для расчёта подписи при инициализации платежаДа-
Технический пароль №2 для проверки подписи в уведомлениях об оплатеДа-
Тестовый пароль №1 для подписи в тестовом режиме (если )Нет-
Тестовый пароль №2 для уведомлений в тестовом режиме (если )Нет-
Автосписание: — 1‑стадийно; — предавторизация (двухстадийно)Нет
Включает тестовый режимНет
Включает формирование онлайн-чеков по 54-ФЗНет
Система налогообложения:- — общая СН- — упрощенная СН (доходы)- — упрощенная СН (доходы минус расходы)- — единый сельскохозяйственный налог- — патентная СНПрименимо только при Нет-
Ставка НДС по товарам:- — без НДС- — 0%- — 5%- — 7%- — 10%- — 20%- — 5/105- — 7/107- — 10/110- — 20/120Применимо только при Нет-
Ставка НДС для доставки (аналогично )Применимо только при Нет-

Интеграция с Storefront (витриной магазина)

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

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

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

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

1. Конфигурация платёжного провайдера

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

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

export const paymentInfoMap: Record<
string,
{ title: string; icon: React.ReactNode }
> = {
// ... другие провайдеры
pp_robokassa_robokassa: {
title: "Robokassa",
icon: <CreditCard />,
}
}
// Вспомогатьсяная функция для проверки, является ли провайдер Robokassa
export const isRobokassa = (providerId?: string) => {
return providerId?.startsWith("pp_robokassa")
}

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

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

2. Настройки Cookie

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

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

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

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

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

3. Инициализация платёжной сессии

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

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

await initiatePaymentSession(cart, {
provider_id: selectedPaymentMethod,
data: {
SuccessUrl2: `${getBaseURL()}/api/capture-payment/${cart?.id}?country_code=${countryCode}`,
SuccessUrl2Method: "GET",
FailUrl2: `${getBaseURL()}/api/capture-payment/${cart?.id}?country_code=${countryCode}`,
FailUrl2Method: "GET",
EMail: cart?.email,
Culture: countryCode === "ru" ? "ru" : "en",
cart: cart
}
})

При инициализации платёжной сессии для Robokassa параметры и определяют, куда будет перенаправлен покупатель после попытки оплаты. Оба URL динамически формируются на основе базового URL storefront, идентификатора корзины и выбранного кода страны.

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

4. Компонент кнопки оплаты

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

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

const PaymentButton: React.FC<PaymentButtonProps> = ({
cart,
"data-testid": dataTestId,
}) => {
// ...
switch (true) {
// ... другие проверки
case isRobokassa(paymentSession?.provider_id):
return (
<RobokassaPaymentButton
notReady={notReady}
cart={cart}
data-testid={dataTestId}
/>
)
default:
return <Button disabled>Select a payment method</Button>
}
}
// ... другие компоненты кнопок оплаты
type RobokassaPaymentProps = {
cart: HttpTypes.StoreCart
notReady: boolean
"data-testid"?: string
}
const RobokassaPaymentButton: React.FC<RobokassaPaymentProps> = ({
cart,
notReady,
"data-testid": dataTestId,
}) => {
const [submitting, setSubmitting] = useState(false)
const [errorMessage, setErrorMessage] = useState<string | null>(null)
const router = useRouter()
const paymentSession = cart.payment_collection?.payment_sessions?.find(
session =>
session.provider_id === "pp_robokassa_robokassa"
)
const handlePayment = () => {
setSubmitting(true)
const paymentUrl = (paymentSession?.data as any).paymentUrl
if (paymentUrl) {
router.push(paymentUrl)
} else {
setErrorMessage("Payment URL отсутствует")
setSubmitting(false)
}
}
return (
<>
<Button
disabled={notReady}
isLoading={submitting}
onClick={handlePayment}
data-testid={dataTestId}
size="large"
>
Place order
</Button>
<ErrorMessage
error={errorMessage}
data-testid="robokassa-payment-error-message"
/>
</>
)
}

Этот компонент находит в активной корзине платёжную сессию Robokassa и читает . При клике по Place order пользователь перенаправляется на этот URL, чтобы завершить оплату на странице Robokassa.

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

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

5. API-роут подтверждения платежа

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

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

import { NextRequest, NextResponse } from "next/server"
import { revalidateTag } from "next/cache"
import {
getCacheTag,
getAuthHeaders,
removeCartId
} from "@lib/data/cookies"
import { sdk } from "@lib/config"
import { placeOrder } from "@lib/data/cart"
type Params = Promise<{ cartId: string }>
export async function GET(req: NextRequest, { params }: { params: Params }) {
const { cartId } = await params
const { origin, searchParams } = req.nextUrl
const countryCode = searchParams.get("country_code") || ""
const headers = { ...(await getAuthHeaders()) }
// Получить актуальные значения корзины
const cartCacheTag = await getCacheTag("carts")
revalidateTag(cartCacheTag)
const { cart } = await sdk.store.cart.retrieve(cartId, {
fields: "id, order_link.order_id"
},
headers
)
if (!cart) {
return NextResponse.redirect(`${origin}/${countryCode}`)
}
const orderId = (cart as unknown as Record<string, any>).order_link?.order_id
if (!orderId) {
await placeOrder(cartId)
// Ошибка при неавторизованном платеже
return NextResponse.redirect(
`${origin}/${countryCode}/checkout?step=review&error=payment_failed`
)
}
const orderCacheTag = await getCacheTag("orders")
revalidateTag(orderCacheTag)
removeCartId()
return NextResponse.redirect(
`${origin}/${countryCode}/order/${orderId}/confirmed`
)
}

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

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

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

Пример

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

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

git clone https://github.com/gorgojs/medusa-plugins
cd medusa-plugins
git diff @gorgo/medusa-payment-robokassa@0.0.1...main -- examples/payment-robokassa/medusa-storefront

Разработка

Документация по запуску окружения для разработки можно найти здесь.

Лицензия

Распространяется на условиях лицензии MIT.