# Создание подписочного платежа

Рекуррентные платежи по **банковской карте** позволяют списывать средства по **токену** сохранённой карты без повторного ввода реквизитов плательщиком. При активной привязке списание выполняется через GATE (`init_payment`).

Для включения функционала в проекте обратитесь к сопровождающему менеджеру 1Payment.

Формат запросов и подписи — в разделе [Формат запроса к API](/pages/init-request).

## Принцип работы

Процесс работы с подписками по картам состоит из четырёх этапов:

1. **Первый платёж (привязка карты)** — проведите **успешный** платёж с параметром `subscription=1` через [форму оплаты](/pages/card/init-form) или [GATE (host2host)](/pages/card/host2host). Плательщик вводит данные карты и проходит 3-D Secure при необходимости.
2. **Получение токена** — после успешной оплаты 1Payment передаёт уникальный `token` в колбеке на `notify_url` или в ответе [запроса статуса](/pages/card/status). Сохраните его для последующих списаний.
3. **Автосписание (рекуррент)** — для повторных оплат ваш сервер вызывает GATE (`init_payment`), передавая сохранённый `token` **вместо** полей `account`, `card_holder`, `year`, `month` и `cvc`.
4. **Уведомление** — после каждой попытки списания приходит колбек с финальным результатом транзакции.


## Техническая информация

| Этап | Эндпоинт | Методы |
|  --- | --- | --- |
| Регистрация подписки (форма) | `https://api.1payment.com/init_form` | `GET`, `POST` |
| Регистрация подписки (GATE) | `https://api.1payment.com/init_payment` | `GET`, `POST` |
| Рекуррентное списание (GATE) | `https://api.1payment.com/init_payment` | `POST` (рекомендуется), `GET` |


Формат ответа: **JSON**. Для карт в запросах GATE указывайте `payment_type=card`.

## 1. Получение токена (первый платёж)

Для привязки карты проведите **успешный** платёж с параметром `subscription=1`. Подробнее о параметрах формы и GATE — в разделах [Создание платежа по форме](/pages/card/init-form) и [Создание платежа по host2host (GATE)](/pages/card/host2host). Ниже — обязательный набор для привязки.

### Обязательные параметры (инициация через форму)

| Параметр | Тип | Описание |
|  --- | --- | --- |
| `partner_id` | Integer | Ваш уникальный ID в системе 1Payment. |
| `project_id` | Integer | Идентификатор вашего проекта. |
| `amount` | Number | Сумма платежа при привязке (например, `50.00`). |
| `subscription` | Integer | Флаг создания подписки. Всегда `1`. Без него токен в ответе не вернётся. |
| `user_data` | String | Ваш уникальный ID заказа для сопоставления. |
| `shop_url` | String | URL сайта, на котором совершается покупка. |
| `sign` | String | Контрольная подпись (см. ниже). |


### Обязательные параметры (инициация через GATE)

| Параметр | Тип | Описание |
|  --- | --- | --- |
| `partner_id` | Integer | Ваш уникальный ID в системе 1Payment. |
| `payment_type` | String | Всегда `card`. |
| `project_id` | Integer | Идентификатор вашего проекта. |
| `account` | String | Номер банковской карты. |
| `card_holder` | String | Имя держателя карты (как указано на карте). |
| `year` | String | Год окончания действия карты, две последние цифры (например, `22`). |
| `month` | String | Месяц окончания действия карты (например, `01`). |
| `cvc` | String | CVV/CVC код карты. |
| `amount` | Number | Сумма платежа при привязке (например, `50.00`). |
| `subscription` | Integer | Флаг создания подписки. Всегда `1`. Без него токен в ответе не вернётся. |
| `user_data` | String | Ваш уникальный ID заказа для сопоставления. |
| `shop_url` | String | URL сайта источника платежа. |
| `sign` | String | Контрольная подпись (см. ниже). |


### Дополнительные параметры (инициация)

| Параметр | Тип | Описание |
|  --- | --- | --- |
| `description` | String | Описание платежа. |
| `success_url` | String | URL возврата клиента при успехе (форма). |
| `failure_url` | String | URL возврата клиента при ошибке (форма). |
| `return_url` | String | URL возврата плательщика после оплаты (GATE). |
| `ip` | String | IP-адрес плательщика (GATE). |
| `user_id` | String | ID плательщика в вашей системе. |


**Подпись при регистрации через форму** — префикс `init_form`. **Через GATE** — префикс `init_payment`.


```text
MD5(init_form + <параметры_без_sign_в_алфавитном_порядке_через_&> + <API_KEY>)
```


```text
MD5(init_payment + <параметры_без_sign_в_алфавитном_порядке_через_&> + <API_KEY>)
```

После финального статуса `SUCCESS` сохраните значение `token` из колбека или из ответа [Статусы платежей](/pages/card/status).

## 2. Рекуррентное списание (повторные платежи)

Для автоматического списания отправьте запрос на GATE с сохранённым `token`. **Не передавайте** `account`, `card_holder`, `year`, `month` и `cvc` — вместо них используется только `token`.

### Обязательные параметры (рекуррент)

| Параметр | Тип | Описание |
|  --- | --- | --- |
| `partner_id` | Integer | Ваш уникальный ID в системе 1Payment. |
| `payment_type` | String | Всегда `card`. |
| `project_id` | Integer | Идентификатор вашего проекта. |
| `amount` | Number | Сумма очередного списания (например, `500.00`). |
| `token` | String | Токен, полученный при первом успешном платеже (из колбека или запроса статуса). |
| `user_data` | String | Новый уникальный ID транзакции на вашей стороне. |
| `shop_url` | String | URL сайта источника платежа. |
| `sign` | String | Контрольная подпись (префикс `init_payment`). |


### Дополнительные параметры (рекуррент)

| Параметр | Тип | Описание |
|  --- | --- | --- |
| `description` | String | Описание платежа. |
| `ip` | String | IP-адрес плательщика. |
| `return_url` | String | URL возврата плательщика после оплаты. |
| `user_id` | String | ID плательщика в вашей системе. |


### Формирование подписи (`sign`)

Подпись: MD5, lowercase (hex). Общие правила — [Формат запроса к API](/pages/init-request#%D0%BF%D0%BE%D0%B4%D0%BF%D0%B8%D1%81%D1%8C-%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%B0-sign).

**Формула:**


```text
MD5(init_payment + <параметры_без_sign_в_алфавитном_порядке_через_&> + <API_KEY>)
```

**Пример строки до хеширования:**


```text
init_paymentamount=500.00&partner_id=1234&payment_type=card&project_id=5678&shop_url=https://test.com&token=card_t_1a2b3c&user_data=card_order_888secret_key
```

### Проверка подписи (рекуррентное списание)

## Примеры запроса

Node.js

```javascript
const crypto = require('crypto');
const axios = require('axios');

// 1. Инициация подписки через форму (первичная привязка)
async function setupCardSubscriptionForm(apiKey, params) {
  const sortedKeys = Object.keys(params).sort();
  const queryString = sortedKeys.map((k) => `${k}=${params[k]}`).join('&');

  const baseString = `init_form${queryString}${apiKey}`;
  params.sign = crypto.createHash('md5').update(baseString).digest('hex');

  const response = await axios.post('https://api.1payment.com/init_form', params);
  return response.data; // поле url — ссылка на форму
}

// 2. Инициация подписки через GATE
async function setupCardSubscriptionGate(apiKey, params) {
  const sortedKeys = Object.keys(params).sort();
  const queryString = sortedKeys.map((k) => `${k}=${params[k]}`).join('&');

  const baseString = `init_payment${queryString}${apiKey}`;
  params.sign = crypto.createHash('md5').update(baseString).digest('hex');

  const response = await axios.post('https://api.1payment.com/init_payment', params);
  return response.data; // redirect_url при необходимости 3-D Secure
}

// 3. Рекуррентное списание (по токену)
async function chargeCardByToken(apiKey, params) {
  const sortedKeys = Object.keys(params).sort();
  const queryString = sortedKeys.map((k) => `${k}=${params[k]}`).join('&');

  const baseString = `init_payment${queryString}${apiKey}`;
  params.sign = crypto.createHash('md5').update(baseString).digest('hex');

  const response = await axios.post('https://api.1payment.com/init_payment', params);
  return response.data;
}

const apiKey = process.env.ONEPAYMENT_API_KEY;

// Пример: привязка через форму
const setupFormData = {
  partner_id: 1234,
  project_id: 5678,
  amount: '50.00',
  subscription: 1,
  user_data: 'card_setup_777',
  shop_url: 'https://test.com',
};
// setupCardSubscriptionForm(apiKey, setupFormData).then(console.log);

// Пример: привязка через GATE
const setupGateData = {
  partner_id: 1234,
  payment_type: 'card',
  project_id: 5678,
  account: '4111111111111111',
  card_holder: 'TEST',
  year: '22',
  month: '01',
  cvc: '111',
  amount: '50.00',
  subscription: 1,
  user_data: 'card_setup_777',
  shop_url: 'https://test.com',
};
// setupCardSubscriptionGate(apiKey, setupGateData).then(console.log);

// Пример: списание
const recurringData = {
  partner_id: 1234,
  payment_type: 'card',
  project_id: 5678,
  token: 'card_t_1a2b3c',
  amount: '500.00',
  user_data: 'card_order_888',
  shop_url: 'https://test.com',
};
chargeCardByToken(apiKey, recurringData).then(console.log);
```

Python

```python
import hashlib
import os
import requests

def setup_card_subscription_form(api_key: str, params: dict) -> dict:
    sorted_keys = sorted(params.keys())
    query_string = "&".join(f"{k}={params[k]}" for k in sorted_keys)
    base_string = f"init_form{query_string}{api_key}"
    params["sign"] = hashlib.md5(base_string.encode("utf-8")).hexdigest()
    response = requests.post("https://api.1payment.com/init_form", data=params)
    response.raise_for_status()
    return response.json()

def setup_card_subscription_gate(api_key: str, params: dict) -> dict:
    sorted_keys = sorted(params.keys())
    query_string = "&".join(f"{k}={params[k]}" for k in sorted_keys)
    base_string = f"init_payment{query_string}{api_key}"
    params["sign"] = hashlib.md5(base_string.encode("utf-8")).hexdigest()
    response = requests.post("https://api.1payment.com/init_payment", data=params)
    response.raise_for_status()
    return response.json()

def charge_card_by_token(api_key: str, params: dict) -> dict:
    sorted_keys = sorted(params.keys())
    query_string = "&".join(f"{k}={params[k]}" for k in sorted_keys)
    base_string = f"init_payment{query_string}{api_key}"
    params["sign"] = hashlib.md5(base_string.encode("utf-8")).hexdigest()
    response = requests.post("https://api.1payment.com/init_payment", data=params)
    response.raise_for_status()
    return response.json()

api_key = os.environ["ONEPAYMENT_API_KEY"]

setup_form_data = {
    "partner_id": 1234,
    "project_id": 5678,
    "amount": "50.00",
    "subscription": 1,
    "user_data": "card_setup_777",
    "shop_url": "https://test.com",
}
# print(setup_card_subscription_form(api_key, setup_form_data))

setup_gate_data = {
    "partner_id": 1234,
    "payment_type": "card",
    "project_id": 5678,
    "account": "4111111111111111",
    "card_holder": "TEST",
    "year": "22",
    "month": "01",
    "cvc": "111",
    "amount": "50.00",
    "subscription": 1,
    "user_data": "card_setup_777",
    "shop_url": "https://test.com",
}
# print(setup_card_subscription_gate(api_key, setup_gate_data))

recurring_data = {
    "partner_id": 1234,
    "payment_type": "card",
    "project_id": 5678,
    "token": "card_t_1a2b3c",
    "amount": "500.00",
    "user_data": "card_order_888",
    "shop_url": "https://test.com",
}
print(charge_card_by_token(api_key, recurring_data))
```

PHP

```php
<?php

function signParams(string $prefix, string $apiKey, array $params): array
{
    ksort($params, SORT_LOCALE_STRING);
    $tail = '';
    foreach ($params as $k => $v) {
        if ($k === 'sign') {
            continue;
        }
        $tail .= "{$k}={$v}&";
    }
    $tail = substr($tail, 0, -1);
    $params['sign'] = md5($prefix . $tail . $apiKey);
    return $params;
}

function setupCardSubscriptionForm(string $apiKey, array $params): string
{
    $params = signParams('init_form', $apiKey, $params);
    $ch = curl_init('https://api.1payment.com/init_form');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    curl_close($ch);
    return $response;
}

function setupCardSubscriptionGate(string $apiKey, array $params): string
{
    $params = signParams('init_payment', $apiKey, $params);
    $ch = curl_init('https://api.1payment.com/init_payment');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    curl_close($ch);
    return $response;
}

function chargeCardByToken(string $apiKey, array $params): string
{
    $params = signParams('init_payment', $apiKey, $params);
    $ch = curl_init('https://api.1payment.com/init_payment');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    curl_close($ch);
    return $response;
}

$apiKey = getenv('ONEPAYMENT_API_KEY');

$setupFormData = [
    'partner_id' => 1234,
    'project_id' => 5678,
    'amount' => '50.00',
    'subscription' => 1,
    'user_data' => 'card_setup_777',
    'shop_url' => 'https://test.com',
];
// echo setupCardSubscriptionForm($apiKey, $setupFormData);

$setupGateData = [
    'partner_id' => 1234,
    'payment_type' => 'card',
    'project_id' => 5678,
    'account' => '4111111111111111',
    'card_holder' => 'TEST',
    'year' => '22',
    'month' => '01',
    'cvc' => '111',
    'amount' => '50.00',
    'subscription' => 1,
    'user_data' => 'card_setup_777',
    'shop_url' => 'https://test.com',
];
// echo setupCardSubscriptionGate($apiKey, $setupGateData);

$recurringData = [
    'partner_id' => 1234,
    'payment_type' => 'card',
    'project_id' => 5678,
    'token' => 'card_t_1a2b3c',
    'amount' => '500.00',
    'user_data' => 'card_order_888',
    'shop_url' => 'https://test.com',
];
echo chargeCardByToken($apiKey, $recurringData);
```

cURL

```bash
# Привязка через форму: sign = md5("init_form" + amount=50.00&partner_id=1234&project_id=5678&shop_url=...&subscription=1&user_data=... + API_KEY)
curl -sS -X POST "https://api.1payment.com/init_form" \
  -d "partner_id=1234&project_id=5678&amount=50.00&subscription=1&user_data=card_setup_777&shop_url=https%3A%2F%2Ftest.com&sign=PASTE_MD5_HEX"

# Рекуррент: sign = md5("init_payment" + amount=500.00&partner_id=1234&payment_type=card&project_id=5678&shop_url=...&token=...&user_data=... + API_KEY)
curl -sS -X POST "https://api.1payment.com/init_payment" \
  -d "partner_id=1234&payment_type=card&project_id=5678&token=card_t_1a2b3c&amount=500.00&user_data=card_order_888&shop_url=https%3A%2F%2Ftest.com&sign=PASTE_MD5_HEX"
```

## Ответ API

При успешном **рекуррентном** запросе (`200`):


```json
{
  "order_id": "8p3brmb19gfg0sg8gcwhws8kgc748s87",
  "status": 2,
  "status_description": "PENDING",
  "status_code": 0
}
```

| Поле | Описание |
|  --- | --- |
| `order_id` | ID платежа в системе 1Payment; используйте для [запроса статуса](/pages/card/status). |
| `status` | Числовой код состояния (`2` — ожидание). |
| `status_description` | Текстовое описание статуса (`PENDING` и др.). |
| `status_code` | Код ошибки при отказе (см. [коды отказов](/pages/decline-error-codes)). |
| `redirect_url` | URL для 3-D Secure, если требуется дополнительная аутентификация (может отсутствовать). |


При **первом платеже с `subscription=1` через форму** в ответе будет поле `url` (см. [Создание платежа по форме](/pages/card/init-form)); через GATE — `redirect_url` при необходимости 3-D Secure (см. [Создание платежа по host2host (GATE)](/pages/card/host2host)). После успешной оплаты поле `token` приходит в колбеке на `notify_url` или в ответе [запроса статуса](/pages/card/status) — без `subscription=1` токен не выдаётся.

Ошибка запроса (`400`):


```json
{
  "error_code": 2
}
```

## Уведомления о статусе (колбеки)

При успешной привязке карты или рекуррентном списании система отправляет **POST**-запрос в формате JSON на ваш `notify_url`.

| Параметр | Тип | Обяз. | Описание |
|  --- | --- | --- | --- |
| `payment_type` | String | Да | Тип платежа (`card`). |
| `order_id` | String | Да | ID платежа в системе 1Payment. |
| `project_id` | Integer | Да | ID вашего проекта. |
| `status` | Integer | Да | Состояние: `2` (ожидание), `3` (успех), `4` (отказ). |
| `status_description` | String | Да | `PENDING`, `SUCCESS` или `FAILURE`. |
| `init_time` | String | Да | Время создания платежа. |
| `status_time` | String | Да | Время получения финального статуса. |
| `merchant_price` | Number | Да | Стоимость для плательщика. |
| `init_price` | Number | Нет | Сумма при инициации. |
| `user_price` | Number | Да | Отчисления партнёра. |
| `currency` | String | Да | Валюта платежа (ISO 4217). |
| `account` | String | Да | Маска номера карты. |
| `user_data` | String | Да | ID транзакции, переданный при создании. |
| `sign` | String | Да | Контрольная подпись колбека. |
| `token` | String | Нет | Токен для последующих списаний — **сохраните** при первом успешном платеже. |
| `test` | Integer | Нет | `1` при тестовых платежах. |
| `status_code` | String | Нет | Код причины отказа. |


**Важно:** ваш сервер должен вернуть **HTTP 200 OK**. Иначе система повторяет отправку колбека **раз в минуту в течение 10 минут**.

**Проверка подписи колбека:** MD5 от всех параметров в алфавитном порядке через `&` + `API_KEY` (**без** префикса `init_payment`). Подробнее — в разделе [Формат запроса к API](/pages/init-request#%D0%BA%D0%BE%D0%BB%D0%B1%D0%B5%D0%BA%D0%B8-%D1%83%D0%B2%D0%B5%D0%B4%D0%BE%D0%BC%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F).

Токен также можно получить через [запрос статуса платежа](/pages/card/status) — поле `token` в ответе API.

## Связанные разделы

- [Создание платежа по форме](/pages/card/init-form)
- [Создание платежа по host2host (GATE)](/pages/card/host2host)
- [Статусы платежей](/pages/card/status)
- [Возвраты платежей](/pages/card/refund)
- [Статусы транзакций](/pages/transaction-statuses)
- [Коды ошибок (API)](/pages/error-codes-api)
- [Коды ошибок (отказы)](/pages/decline-error-codes)