Configuration

Secrets stored in browser localStorage. Не коммить этот файл в публичный git. Bearer-токен на этой странице === полный доступ к мерчанту.
Что такое proxy prefix и зачем он нужен
У OPP CORS обычно закрыт — браузер не пустит прямой fetch. Варианты:
1) Без прокси — кнопка Send упадёт, но curl-команда сгенерируется корректно, копируешь и шлёшь из терминала.
2) С прокси — поднимаешь локальный proxy-сервер, например node server.js на порту 8787. Минимальный код Node ниже (в раскрывашке внизу страницы).

Payment status & queries

GET

Get payment status

/v1/payments/{id}
Получить полный статус транзакции по её id. Это основной способ "проверить, что с платежом".

  
// response will appear here
GET

Query transactions

/v1/query
Поиск платежей по фильтрам. Ближайший аналог "посмотреть, что было за период" — то, что в API можно назвать "балансом".

  
// response will appear here
GET

Get checkout payment status

/v1/checkouts/{id}/payment
Узнать, чем закончилась checkout-сессия (после виджета Copy & Pay).

  
// response will appear here
GET

Get token info

/v1/registrations/{id}
Получить данные сохранённой карты (токена): бренд, last4, expiry.

  
// response will appear here

Checkout flow (hosted widget)

POST

Create checkout

/v1/checkouts
Создать checkout-сессию для Copy & Pay виджета. Возвращает id для подстановки в виджет.

  
// response will appear here
GET

Get registration from checkout

/v1/checkouts/{id}/registration
Если в checkout был createRegistration=true, получить созданный токен.

  
// response will appear here

Server-to-server payments (PCI scope)

Эти эндпоинты принимают сырые данные карты. Использовать можно ТОЛЬКО если у тебя PCI DSS SAQ D или ты в защищённой PCI-среде. Тестовые карты — см. блок внизу.
POST

Direct payment

/v1/payments
S2S платёж картой. PA = preauth (hold), DB = debit (полное списание), CD = credit (выплата на карту).

  
// response will appear here
POST

Capture / Refund / Reverse

/v1/payments/{id}
CP capture (после PA), RF refund (возврат после DB), RV reversal (отмена до клиринга).

  
// response will appear here
POST

Account verification (zero-amount auth)

/v1/payments
Проверить, что карта живая, не списывая ни цента. paymentType=PA, amount=0.

  
// response will appear here

Tokenization

POST

Register card (tokenize)

/v1/registrations
Сохранить карту как токен. Возвращает registrationId для последующих списаний.

  
// response will appear here
POST

Charge stored token

/v1/registrations/{id}/payments
Списать с ранее сохранённого токена (recurring / one-click).

  
// response will appear here
DELETE

Delete token

/v1/registrations/{id}
Удалить сохранённый токен.

  
// response will appear here

3-D Secure (standalone)

POST

Standalone 3DS authentication

/v1/threeDSecure
Отдельный 3DS-флоу, без привязки к платежу. Используется когда нужно сначала пройти challenge, а потом списать.

  
// response will appear here

Test cards (sandbox eu-test.oppwa.com)

Эти карты работают только на тестовом окружении. На eu-prod вернут ошибку.
VISA success:        4200 0000 0000 0000   05/34   CVV 123
MASTER success:      5454 5454 5454 5454   05/34   CVV 123
AMEX success:        3779 5715 7838 080    05/34   CVV 1234
VISA decline:        4444 4444 4444 4448
3DS challenge:       4711 1000 0000 0000
Insufficient funds:  4200 0000 0000 0042

Local proxy (если CORS блокирует)

Сохрани как server.js рядом, запусти node server.js. В поле Proxy prefix вверху укажи http://localhost:8787/proxy?url=.
// server.js — минимальный CORS-proxy
const http = require('http');
const https = require('https');
const { URL } = require('url');

http.createServer((req, res) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET,POST,DELETE,OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Authorization,Content-Type');
  if (req.method === 'OPTIONS') return res.end();

  const target = new URL(req.url, 'http://x').searchParams.get('url');
  if (!target) { res.statusCode = 400; return res.end('url required'); }

  const u = new URL(target);
  const opts = {
    method: req.method,
    headers: { ...req.headers, host: u.host },
  };
  delete opts.headers['origin']; delete opts.headers['referer'];

  const proxied = https.request(u, opts, (r) => {
    res.statusCode = r.statusCode;
    Object.entries(r.headers).forEach(([k,v]) => {
      if (k.toLowerCase() !== 'access-control-allow-origin') res.setHeader(k, v);
    });
    res.setHeader('Access-Control-Allow-Origin','*');
    r.pipe(res);
  });
  proxied.on('error', e => { res.statusCode=500; res.end(e.message); });
  req.pipe(proxied);
}).listen(8787, () => console.log('proxy on http://localhost:8787'));