MulenPay для PHP и Python разработчиков: подключаем оплату в интернет-магазин
Студия из двух разработчиков делала интернет-магазин настольных игр. Витрина на PHP, а административная часть и скрипты склада написаны на Python. Владелец хотел, чтобы покупатель мог оплатить заказ сразу на сайте, а бухгалтерия не собирала чеки вручную. Срок поставили жёсткий: к старту сезонной распродажи.
Выбирали сервис, который не потребует месяцев на интеграцию. Остановились на MulenPay: платформа принимает оплату на сайте и в приложении, а подключение, по заявленным условиям, занимает один день. Ниже описано, как команда это сделала, какой код получился и что изменилось в работе магазина.
С какой задачей пришли разработчики
До подключения заказы оформлялись по телефону: менеджер выставлял счёт, покупатель переводил деньги, потом кто-то сверял поступления. Это отнимало время и приводило к ошибкам, например к оплаченным заказам со статусом «ожидает».
Требования сформулировали коротко:
- покупатель платит онлайн, не покидая сценария оформления заказа;
- статус заказа меняется автоматически после успешной оплаты;
- чек уходит клиенту без участия сотрудников;
- деньги можно вывести на расчётный счёт.
Для магазина это и есть приём платежей с онлайн-кассой: оплата и фискальный документ должны происходить как одна операция, а не как два отдельных процесса.
Почему выбрали REST API, а не готовый модуль
У сервиса есть несколько способов подключения: виджет, платёжные ссылки, готовые модули для популярных платформ и REST API. Своя витрина команды не работала на типовой CMS, поэтому модуль не подходил. Виджет рассматривали как запасной вариант, а основным выбрали API: так логика заказа остаётся в коде магазина, и разработчики полностью контролируют сценарий.
Если вы выбираете между вариантами, ориентир простой. Типовая CMS — берите модуль. Нужно быстро показать форму — виджет. Своя система заказов — API. Подробнее о том, как устроен приём платежей для интернет-магазина, можно прочитать в отдельном материале сервиса.
Общая схема интеграции
Схема для эквайринга интернет-магазина почти всегда одинакова, независимо от языка:
1. Магазин создаёт заказ у себя и сохраняет его номер.
2. Сервер отправляет в платёжный сервис запрос на создание платежа с суммой, описанием и номером заказа.
3. Сервис возвращает адрес страницы оплаты, на которую перенаправляют покупателя.
4. После оплаты сервис присылает на ваш адрес уведомление.
5. Магазин проверяет уведомление, меняет статус заказа и выдаёт товар.
Важная деталь: статус заказа меняется только по уведомлению сервера, а не по возврату пользователя на страницу «Спасибо». Пользователь может закрыть вкладку, а может и подделать адрес возврата.
Пример на PHP: создаём платёж
Ниже схематичный пример. Названия полей, адрес запроса и способ авторизации взяты для иллюстрации. Точные значения возьмите из документации API в личном кабинете или уточните у поддержки, иначе запрос не сработает.
<?php
$apiKey = getenv('MULENPAY_API_KEY');
$payload = [
'amount' => 2490.00,
'currency' => 'RUB',
'description' => 'Заказ №1024',
'order_id' => '1024',
'return_url' => 'https://shop.example/thanks',
'notify_url' => 'https://shop.example/payment/callback',
];
$ch = curl_init('https://API_ENDPOINT/payments'); // адрес из документации
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $apiKey,
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
header('Location: ' . $response['payment_url']);
Ключ API хранится в переменной окружения, а не в репозитории. Это базовое правило, которое сэкономит нервы при утечке кода.
Пример на Python: то же самое для приложения и скриптов
Склад и административная панель у команды написаны на Python, и там понадобилось создавать платёжные ссылки для заказов, оформленных менеджером вручную. Пример на библиотеке requests:
import os
import requests
API_KEY = os.environ["MULENPAY_API_KEY"]
payload = {
"amount": 2490.00,
"currency": "RUB",
"description": "Заказ №1024",
"order_id": "1024",
"return_url": "https://shop.example/thanks",
"notify_url": "https://shop.example/payment/callback",
}
resp = requests.post(
"https://API_ENDPOINT/payments", # адрес из документации
json=payload,
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=10,
)
resp.raise_for_status()
print(resp.json()["payment_url"])
Задайте таймаут и обрабатывайте ошибки: сетевой сбой не должен оставлять заказ в подвешенном состоянии. Если запрос не прошёл, покажите покупателю понятное сообщение и дайте повторить.
Обработка уведомления об оплате
Это самая ответственная часть. Приведём пример приёмника на Python с Flask. Способ подписи и названия полей также уточните в документации.
from flask import Flask, request, abort
app = Flask(__name__)
@app.post("/payment/callback")
def callback():
data = request.get_json(silent=True) or {}
if not verify_signature(request): # проверка по правилам сервиса
abort(400)
order = find_order(data.get("order_id"))
if order and data.get("status") == "paid" and not order.paid:
order.mark_paid()
return "ok", 200
Три правила, которые стоит заложить сразу:
- проверяйте подлинность уведомления, прежде чем менять заказ;
- делайте обработку идемпотентной: повторное уведомление не должно выдать товар дважды;
- сверяйте сумму в уведомлении с суммой заказа в вашей базе.
Чеки и тестирование перед запуском
Отдельно настраивать кассу в коде команде не пришлось: облачная онлайн-касса сервиса сама формирует чек при каждой оплате и отправляет его покупателю. Для разработчика это значит меньше кода и меньше рисков по требованиям к фискальным документам. Нужно лишь передавать корректные данные о заказе, а состав чека и налоговые параметры согласовать с бухгалтером.
Перед выходом в продакшен команда прошла короткий чек-лист:
- оплатила тестовый заказ банковской картой и через СБП;
- проверила, что статус заказа поменялся один раз, даже при повторном уведомлении;
- убедилась, что чек пришёл на почту покупателя;
- оформила возврат из личного кабинета и проверила, как это отражается в учёте.
Что изменилось в магазине после запуска
Интеграция заняла у разработчиков рабочий день плюс время на тесты: основная часть ушла не на запросы к API, а на аккуратную обработку уведомлений. Сказать, что это «просто», можно с оговоркой: сам обмен данными действительно несложный, а сложность в деталях, то есть в проверке подписи, повторных уведомлениях и статусах заказа.
Для бизнеса изменения заметны. Менеджеры больше не сверяют переводы вручную, покупатели получают чеки автоматически, а статус заказа отражает реальное состояние оплаты. Менеджеры по необходимости выставляют счета и платёжные ссылки в личном кабинете, где видны поступления и откуда можно выгрузить отчёты для бухгалтера. Средства выводятся на расчётный счёт или карту. Условия по комиссиям и срокам выплат команда уточняла у менеджера сервиса: в открытых данных их нет, и закладывать их в расчёты по догадке не стоит.
Чтобы подключиться, зайдите на mulenpay.com или напишите в поддержку: Telegram @mulen_support_bot, почта support@mulenpay.com. Поддержка работает круглосуточно, это удобно, если вопросы возникают во время ночного релиза.
Частые вопросы
Нужно ли хранить данные карт покупателей на своём сервере?
Нет. Покупатель вводит данные на стороне платёжного сервиса, а ваш сервер получает только результат операции. Это снижает риски и упрощает требования к безопасности вашей системы.
Как тестировать интеграцию без риска для заказов?
Разверните отдельное окружение с тестовой базой и проверяйте сценарии на небольших суммах. Порядок тестирования и наличие тестового режима уточните в документации или у поддержки.
Что делать, если уведомление не дошло?
Предусмотрите в админке кнопку ручной проверки статуса платежа и сверку с отчётом из личного кабинета. Также убедитесь, что ваш адрес для уведомлений доступен из интернета и не блокируется фаерволом.
Можно ли использовать один аккаунт и для сайта, и для приложения?
Сервис принимает платежи на сайте, в приложении и в соцсетях. Как лучше разделить проекты и ключи доступа, подскажет поддержка при подключении.