Compare commits
13 Commits
2c19b9c29b
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| ad57a5679f | |||
| 2402d0e01f | |||
| 6d67e969e0 | |||
| 33da1338bb | |||
| 8c5ae99a6b | |||
| fc0c84f7f7 | |||
| ee6b510e41 | |||
| 0658854f47 | |||
| da823e7898 | |||
| 4659b0089e | |||
| bcf99b29a4 | |||
| dd2c360cf6 | |||
| 075a53f6ef |
7
.claude/settings.local.json
Normal file
7
.claude/settings.local.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(helm template:*)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
24
Dockerfile
Normal file
24
Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
FROM golang:1.26-alpine3.23 AS build
|
||||||
|
|
||||||
|
WORKDIR /payoutsbuild
|
||||||
|
|
||||||
|
RUN apk add --update --no-cache ca-certificates git
|
||||||
|
|
||||||
|
ENV GOBIN=/payoutsbuild/bin
|
||||||
|
|
||||||
|
ADD . /payoutsbuild
|
||||||
|
|
||||||
|
RUN cd /payoutsbuild && \
|
||||||
|
go mod download && \
|
||||||
|
go build ./cmd/...
|
||||||
|
|
||||||
|
FROM alpine:3.23
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=build /payoutsbuild/payouts /app/
|
||||||
|
COPY --from=build /payoutsbuild/config/payouts.properties /app/
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/app/payouts" ]
|
||||||
398
README.md
Normal file
398
README.md
Normal file
@@ -0,0 +1,398 @@
|
|||||||
|
# Payouts Service
|
||||||
|
|
||||||
|
A Go service for processing payouts via YooKassa, supporting SBP, YooMoney, bank card, and widget-based payout flows.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### `GET /health`
|
||||||
|
|
||||||
|
Health check endpoint. Verifies database connectivity.
|
||||||
|
|
||||||
|
**Request parameters:** None
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
| Status | Body |
|
||||||
|
|--------|------|
|
||||||
|
| `200 OK` | `{"OK": true}` |
|
||||||
|
| `503 Service Unavailable` | `{"OK": false, "Error": "error details"}` |
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
curl -s http://localhost:8080/health
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /version`
|
||||||
|
|
||||||
|
Returns the application version.
|
||||||
|
|
||||||
|
**Request parameters:** None
|
||||||
|
|
||||||
|
**Response:** Plain text version string (e.g. `v1.0.0`). If version is `unknown`, the git commit hash from build info is appended.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
curl -s http://localhost:8080/version
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /api/v1/user/register`
|
||||||
|
|
||||||
|
Register a new user.
|
||||||
|
|
||||||
|
**Request body (JSON):**
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `tin` | string | yes | Tax identification number |
|
||||||
|
| `phone` | string | yes | Phone number (must be unique) |
|
||||||
|
| `password` | string | yes | Password |
|
||||||
|
| `password_cfm` | string | yes | Password confirmation (must match `password`) |
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
| Status | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `201 Created` | User registered successfully (no body) |
|
||||||
|
| `400 Bad Request` | Validation error or phone already registered |
|
||||||
|
| `500 Internal Server Error` | Password hashing failure |
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
curl -s -X POST http://localhost:8080/api/v1/user/register \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"tin":"123456789","phone":"+79001234567","password":"secret","password_cfm":"secret"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /api/v1/user/login`
|
||||||
|
|
||||||
|
Authenticate a user and obtain a session token.
|
||||||
|
|
||||||
|
**Request body (JSON):**
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `phone` | string | yes | Registered phone number |
|
||||||
|
| `password` | string | yes | Password |
|
||||||
|
|
||||||
|
**Response (200 OK):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"token": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"token_ttl": 1712000000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `token` | string | UUID session token to use in subsequent requests |
|
||||||
|
| `token_ttl` | integer | Unix timestamp when the token expires |
|
||||||
|
|
||||||
|
**Error response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 401,
|
||||||
|
"message": "Unauthorized"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
curl -s -X POST http://localhost:8080/api/v1/user/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"phone":"+79001234567","password":"secret"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /api/v1/payout/sbp/banks`
|
||||||
|
|
||||||
|
Retrieve the list of banks available for SBP payouts.
|
||||||
|
|
||||||
|
**Request parameters:** None
|
||||||
|
|
||||||
|
**Authentication:** Not required
|
||||||
|
|
||||||
|
**Response (200 OK):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "sbp_banks",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"bank_id": "100000000111",
|
||||||
|
"name": "Тинькофф Банк",
|
||||||
|
"bic": "044525974"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
curl -s http://localhost:8080/api/v1/payout/sbp/banks
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /api/v1/payout/create`
|
||||||
|
|
||||||
|
Create a payout. The `payout_type` determines which additional fields are required.
|
||||||
|
|
||||||
|
**Request headers:**
|
||||||
|
|
||||||
|
| Header | Required | Description |
|
||||||
|
|--------|----------|-------------|
|
||||||
|
| `Authorization` | yes | `Bearer {token}` — session token from login |
|
||||||
|
| `Content-Type` | yes | `application/json` |
|
||||||
|
|
||||||
|
**Request body (JSON):**
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `payout_type` | string | yes | One of: `spb`, `yoo_money`, `bank_card`, `widget` |
|
||||||
|
| `amount` | float | yes | Payout amount in rubles |
|
||||||
|
| `payout_token` | string | for `widget` | Token received from the YooKassa widget `success_callback` |
|
||||||
|
| `account_number` | string | for `yoo_money` | YooMoney wallet number or phone |
|
||||||
|
| `bank_id` | string | for `spb` | Bank identifier from `/api/v1/payout/sbp/banks` |
|
||||||
|
| `card_number` | string | for `bank_card` | Card number |
|
||||||
|
|
||||||
|
> **Note:** For `spb`, the phone number is taken from the authenticated user's profile.
|
||||||
|
|
||||||
|
**Response (200 OK):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"payout_id": "po-285e5ee7-0022-5000-8000-01516a44b37d",
|
||||||
|
"payout_status": "succeeded"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `payout_id` | string | YooKassa payout identifier |
|
||||||
|
| `payout_status` | string | One of: `created`, `pending`, `succeeded`, `canceled`, `failed` |
|
||||||
|
|
||||||
|
**Error response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 401,
|
||||||
|
"message": "Unauthorized"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example (SBP payout):**
|
||||||
|
```bash
|
||||||
|
curl -s -X POST http://localhost:8080/api/v1/payout/create \
|
||||||
|
-H "Authorization: Bearer 550e8400-e29b-41d4-a716-446655440000" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"payout_type":"spb","amount":500.00,"bank_id":"100000000111"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example (widget payout):**
|
||||||
|
```bash
|
||||||
|
curl -s -X POST http://localhost:8080/api/v1/payout/create \
|
||||||
|
-H "Authorization: Bearer 550e8400-e29b-41d4-a716-446655440000" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"payout_type":"widget","amount":500.00,"payout_token":"pt-285e5ee7-0022-5000-8000-01516a44b37d"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /api/v1/payout/callback`
|
||||||
|
|
||||||
|
Webhook endpoint for YooKassa payout status notifications. Called by YooKassa when a payout status changes.
|
||||||
|
|
||||||
|
> **Note:** When `YooKassa.CheckAllowedCallbackAddress = true`, requests are validated against a CIDR whitelist of YooKassa IP ranges.
|
||||||
|
|
||||||
|
**Request body (JSON, sent by YooKassa):**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "po-285e5ee7-0022-5000-8000-01516a44b37d",
|
||||||
|
"status": "succeeded",
|
||||||
|
"amount": {
|
||||||
|
"value": "500.00",
|
||||||
|
"currency": "RUB"
|
||||||
|
},
|
||||||
|
"payout_destination": {
|
||||||
|
"type": "bank_card",
|
||||||
|
"card": {
|
||||||
|
"number": "220000******0001",
|
||||||
|
"first6": "220000",
|
||||||
|
"last4": "0001",
|
||||||
|
"card_type": "MIR",
|
||||||
|
"issuer_country": "RU",
|
||||||
|
"issuer_name": "Sberbank"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Payout description",
|
||||||
|
"created_at": "2024-01-01T12:00:00.000Z",
|
||||||
|
"succeeded_at": "2024-01-01T12:00:05.000Z",
|
||||||
|
"test": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:** `200 OK` (processing is asynchronous)
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
curl -s -X POST http://localhost:8080/api/v1/payout/callback \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"id":"po-285e5ee7-0022-5000-8000-01516a44b37d","status":"succeeded","amount":{"value":"500.00","currency":"RUB"}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Payout Widget: `/payout/widget`
|
||||||
|
|
||||||
|
`GET /payout/widget` serves an HTML page that embeds the [YooKassa Payout Widget](https://yookassa.ru/developers/payouts/making-payouts/bank-card/using-payout-widget/implementing-widget). The widget collects card details from the user and returns a one-time `payout_token` that must be passed to `/api/v1/payout/create`.
|
||||||
|
|
||||||
|
### Mobile App Integration
|
||||||
|
|
||||||
|
The widget page is designed to be loaded inside a **WebView** on Android or iOS. The widget communicates back to the native app via JavaScript bridge callbacks.
|
||||||
|
|
||||||
|
#### Widget Callbacks
|
||||||
|
|
||||||
|
The widget fires two callbacks:
|
||||||
|
|
||||||
|
**`success_callback(data)`** — called when the user successfully submits card details. The `data` object contains the `payout_token` and card metadata. See [YooKassa widget output parameters](https://yookassa.ru/developers/payouts/making-payouts/bank-card/using-payout-widget/implementing-widget#reference-output-parameters).
|
||||||
|
|
||||||
|
**`error_callback(error)`** — called when an error occurs in the widget. See [error output parameters](https://yookassa.ru/developers/payouts/making-payouts/bank-card/using-payout-widget/implementing-widget#reference-output-parameters-error).
|
||||||
|
|
||||||
|
#### Android
|
||||||
|
|
||||||
|
Expose a JavaScript interface named `AndroidCallback` on the WebView:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class AndroidBridge {
|
||||||
|
@JavascriptInterface
|
||||||
|
fun onWidgetData(dataJson: String) {
|
||||||
|
val data = JSONObject(dataJson)
|
||||||
|
val payoutToken = data.getString("payout_token")
|
||||||
|
// Call /api/v1/payout/create with payout_type "widget"
|
||||||
|
createPayout(payoutToken = payoutToken, amount = 500.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
fun onWidgetError(errorJson: String) {
|
||||||
|
// Handle widget error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webView.addJavascriptInterface(AndroidBridge(), "AndroidCallback")
|
||||||
|
```
|
||||||
|
|
||||||
|
#### iOS (WKWebView)
|
||||||
|
|
||||||
|
Add a `WKScriptMessageHandler` named `iosCallback`:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
class WidgetMessageHandler: NSObject, WKScriptMessageHandler {
|
||||||
|
func userContentController(
|
||||||
|
_ userContentController: WKUserContentController,
|
||||||
|
didReceive message: WKScriptMessage
|
||||||
|
) {
|
||||||
|
guard let body = message.body as? String,
|
||||||
|
let data = body.data(using: .utf8),
|
||||||
|
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
if message.name == "onWidgetData" {
|
||||||
|
let payoutToken = json["payout_token"] as? String ?? ""
|
||||||
|
// Call /api/v1/payout/create with payout_type "widget"
|
||||||
|
createPayout(payoutToken: payoutToken, amount: 500.0)
|
||||||
|
} else if message.name == "onWidgetError" {
|
||||||
|
// Handle widget error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let contentController = WKUserContentController()
|
||||||
|
let handler = WidgetMessageHandler()
|
||||||
|
contentController.add(handler, name: "onWidgetData")
|
||||||
|
contentController.add(handler, name: "onWidgetError")
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Payout Flow After Widget Callback
|
||||||
|
|
||||||
|
When `onWidgetData` fires, call `/api/v1/payout/create` with `payout_type = "widget"` and the received `payout_token`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -X POST https://your-service/api/v1/payout/create \
|
||||||
|
-H "Authorization: Bearer {session_token}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"payout_type": "widget",
|
||||||
|
"amount": 500.00,
|
||||||
|
"payout_token": "{payout_token_from_widget}"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Configuration is loaded from a `.properties` file (default: `config/payouts.properties`).
|
||||||
|
|
||||||
|
### YooKassa
|
||||||
|
|
||||||
|
| Property | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `YooKassa.BaseUrl` | `https://api.yookassa.ru/v3` | YooKassa API base URL |
|
||||||
|
| `YooKassa.Timeout` | `2s` | HTTP request timeout |
|
||||||
|
| `YooKassa.Test` | `false` | Enable test mode |
|
||||||
|
| `YooKassa.ApiBaseKey` | — | Base API key (used for SBP bank list) |
|
||||||
|
| `YooKassa.ApiBaseSecret` | — | Base API secret |
|
||||||
|
| `YooKassa.ApiPayoutKey` | — | Payouts API key (gateway account ID; also used as `account_id` in the widget) |
|
||||||
|
| `YooKassa.ApiPayoutSecret` | — | Payouts API secret |
|
||||||
|
| `YooKassa.Retry.Enabled` | `false` | Enable automatic request retries |
|
||||||
|
| `YooKassa.Retry.Count` | `3` | Total attempt count (including the initial request) |
|
||||||
|
| `YooKassa.Retry.WaitTime` | `200ms` | Initial delay between retries |
|
||||||
|
| `YooKassa.Retry.MaxWaitTime` | `5s` | Maximum delay (exponential backoff cap) |
|
||||||
|
| `YooKassa.CheckAllowedCallbackAddress` | `true` | Validate callback source IP against whitelist |
|
||||||
|
| `YooKassa.AllowedCallbackSubnets` | YooKassa IP ranges | Comma-separated CIDR subnets allowed to send callbacks |
|
||||||
|
| `YooKassa.CallbackProcessTimeout` | `1s` | Timeout for async callback processing |
|
||||||
|
| `YooKassa.WidgetVersion` | `3.1.0` | YooKassa widget JS version loaded on `/payout/widget` |
|
||||||
|
|
||||||
|
### Database
|
||||||
|
|
||||||
|
| Property | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `Database.Type` | — | Database driver: `sqlite` or `postgres` |
|
||||||
|
| `Database.Connection` | — | Connection string. SQLite: `payouts.db`. PostgreSQL: `host=127.0.0.1 user=gorm password=gorm dbname=gorm port=5432 sslmode=disable` |
|
||||||
|
| `Database.LogLevel` | `Info` | GORM log level: `Debug`, `Info`, `Warn`, `Error` |
|
||||||
|
| `Database.TraceRequests` | `false` | Log all SQL queries |
|
||||||
|
|
||||||
|
### Session Cache
|
||||||
|
|
||||||
|
| Property | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `Cache.TTL` | `24h` | Session token time-to-live (Go duration string) |
|
||||||
|
|
||||||
|
### Server
|
||||||
|
|
||||||
|
| Property | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `Server.Port` | `:8080` | Listening address and port |
|
||||||
|
| `Server.WriteTimeout` | `35s` | Response write timeout |
|
||||||
|
| `Server.ReadTimeout` | `35s` | Request read timeout |
|
||||||
|
| `Server.EnablePProfEndpoints` | `false` | Expose `/debug/pprof` endpoints |
|
||||||
|
| `Server.Tls.Enabled` | `false` | Enable TLS |
|
||||||
|
| `Server.Tls.CertFile` | — | Path to TLS certificate file |
|
||||||
|
| `Server.Tls.KeyFile` | — | Path to TLS private key file |
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
|
||||||
|
| Property | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `Log.Level` | `DEBUG` | Log level: `DEBUG`, `INFO`, `WARN`, `ERROR` |
|
||||||
|
| `Log.FilePath` | `./logs/payouts.log` | Log file path |
|
||||||
|
| `Log.TextOutput` | `false` | Use plain text output instead of JSON |
|
||||||
|
| `Log.StdoutEnabled` | `true` | Write logs to stdout |
|
||||||
|
| `Log.FileEnabled` | `false` | Write logs to file |
|
||||||
@@ -3,10 +3,9 @@ Server.WriteTimeout = 35s
|
|||||||
Server.ReadTimeout = 35s
|
Server.ReadTimeout = 35s
|
||||||
Server.EnablePProfEndpoints = false
|
Server.EnablePProfEndpoints = false
|
||||||
|
|
||||||
Socket.MaxHttpBufferSize = 2097152
|
Server.Tls.Enabled = false
|
||||||
Socket.PingInterval = 25s
|
Server.Tls.CertFile =
|
||||||
Socket.PingTimeout = 20s
|
Server.Tls.KeyFile =
|
||||||
Socket.Debug = false
|
|
||||||
|
|
||||||
# Prometheus settings
|
# Prometheus settings
|
||||||
Metrics.Endpoint = /metrics
|
Metrics.Endpoint = /metrics
|
||||||
@@ -38,12 +37,28 @@ Cache.TTL = 24h
|
|||||||
# Yookassa related props
|
# Yookassa related props
|
||||||
# Base API Url
|
# Base API Url
|
||||||
YooKassa.BaseUrl = https://api.yookassa.ru/v3
|
YooKassa.BaseUrl = https://api.yookassa.ru/v3
|
||||||
YooKassa.Timeout = 30s
|
# Timeout for requests
|
||||||
|
YooKassa.Timeout = 2s
|
||||||
|
|
||||||
|
YooKassa.Retry.Enabled = false
|
||||||
|
# Set retry count (including initial request)
|
||||||
|
YooKassa.Retry.Count = 3
|
||||||
|
# Set wait time between retries
|
||||||
|
YooKassa.Retry.WaitTime = 200ms
|
||||||
|
# Set maximum wait time (for exponential backoff)
|
||||||
|
YooKassa.Retry.MaxWaitTime = 5s
|
||||||
|
|
||||||
YooKassa.Test = false
|
YooKassa.Test = false
|
||||||
|
|
||||||
|
YooKassa.CheckAllowedCallbackAddress = true
|
||||||
YooKassa.AllowedCallbackSubnets = 185.71.76.0/27,185.71.77.0/27,77.75.153.0/25,77.75.156.11/32,77.75.156.35/32,77.75.154.128/25,2a02:5180::/32
|
YooKassa.AllowedCallbackSubnets = 185.71.76.0/27,185.71.77.0/27,77.75.153.0/25,77.75.156.11/32,77.75.156.35/32,77.75.154.128/25,2a02:5180::/32
|
||||||
# Base API key/secret
|
# Base API key/secret
|
||||||
YooKassa.ApiBaseKey =
|
YooKassa.ApiBaseKey =
|
||||||
YooKassa.ApiBaseSecret =
|
YooKassa.ApiBaseSecret =
|
||||||
# Payments API key/secret
|
# Payouts API key/secret
|
||||||
YooKassa.ApiPaymentKey =
|
YooKassa.ApiPayoutKey =
|
||||||
YooKassa.ApiPaymentSecret =
|
YooKassa.ApiPayoutSecret =
|
||||||
|
# Timeout to process yookassa callback
|
||||||
|
YooKassa.CallbackProcessTimeout = 1s
|
||||||
|
# Widget version
|
||||||
|
YooKassa.WidgetVersion = 3.1.0
|
||||||
|
|||||||
21
go.mod
21
go.mod
@@ -3,23 +3,18 @@ module payouts
|
|||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-faster/errors v0.7.1
|
github.com/go-resty/resty/v2 v2.17.2
|
||||||
github.com/go-faster/jx v1.2.0
|
|
||||||
github.com/go-viper/encoding/javaproperties v0.1.0
|
github.com/go-viper/encoding/javaproperties v0.1.0
|
||||||
github.com/go-viper/mapstructure/v2 v2.5.0
|
github.com/go-viper/mapstructure/v2 v2.5.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/jellydator/ttlcache/v3 v3.4.0
|
github.com/jellydator/ttlcache/v3 v3.4.0
|
||||||
github.com/jinzhu/copier v0.4.0
|
github.com/jinzhu/copier v0.4.0
|
||||||
github.com/ogen-go/ogen v1.20.1
|
|
||||||
github.com/ogier/pflag v0.0.1
|
github.com/ogier/pflag v0.0.1
|
||||||
github.com/orandin/slog-gorm v1.4.0
|
github.com/orandin/slog-gorm v1.4.0
|
||||||
github.com/prometheus/client_golang v1.23.2
|
github.com/prometheus/client_golang v1.23.2
|
||||||
github.com/samber/slog-multi v1.7.1
|
github.com/samber/slog-multi v1.7.1
|
||||||
github.com/spf13/viper v1.21.0
|
github.com/spf13/viper v1.21.0
|
||||||
go.opentelemetry.io/otel v1.42.0
|
|
||||||
go.opentelemetry.io/otel/metric v1.42.0
|
|
||||||
go.opentelemetry.io/otel/trace v1.42.0
|
|
||||||
go.uber.org/fx v1.24.0
|
go.uber.org/fx v1.24.0
|
||||||
golang.org/x/crypto v0.48.0
|
golang.org/x/crypto v0.48.0
|
||||||
gorm.io/driver/postgres v1.6.0
|
gorm.io/driver/postgres v1.6.0
|
||||||
@@ -30,15 +25,9 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
|
||||||
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
|
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
|
||||||
github.com/fatih/color v1.18.0 // indirect
|
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/getkin/kin-openapi v0.133.0 // indirect
|
github.com/getkin/kin-openapi v0.133.0 // indirect
|
||||||
github.com/ghodss/yaml v1.0.0 // indirect
|
|
||||||
github.com/go-faster/yaml v0.4.6 // indirect
|
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||||
github.com/go-openapi/swag v0.23.0 // indirect
|
github.com/go-openapi/swag v0.23.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
@@ -48,10 +37,9 @@ require (
|
|||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.18.2 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
@@ -63,11 +51,10 @@ require (
|
|||||||
github.com/prometheus/client_model v0.6.2 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
github.com/prometheus/common v0.66.1 // indirect
|
github.com/prometheus/common v0.66.1 // indirect
|
||||||
github.com/prometheus/procfs v0.16.1 // indirect
|
github.com/prometheus/procfs v0.16.1 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
github.com/samber/lo v1.52.0 // indirect
|
github.com/samber/lo v1.52.0 // indirect
|
||||||
github.com/samber/slog-common v0.20.0 // indirect
|
github.com/samber/slog-common v0.20.0 // indirect
|
||||||
github.com/segmentio/asm v1.2.1 // indirect
|
|
||||||
github.com/shopspring/decimal v1.4.0 // indirect
|
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
github.com/speakeasy-api/jsonpath v0.6.0 // indirect
|
github.com/speakeasy-api/jsonpath v0.6.0 // indirect
|
||||||
github.com/speakeasy-api/openapi-overlay v0.10.2 // indirect
|
github.com/speakeasy-api/openapi-overlay v0.10.2 // indirect
|
||||||
@@ -77,13 +64,11 @@ require (
|
|||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
|
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
|
||||||
github.com/woodsbury/decimal128 v1.3.0 // indirect
|
github.com/woodsbury/decimal128 v1.3.0 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
|
||||||
go.uber.org/dig v1.19.0 // indirect
|
go.uber.org/dig v1.19.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.uber.org/zap v1.27.1 // indirect
|
go.uber.org/zap v1.27.1 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect
|
|
||||||
golang.org/x/mod v0.33.0 // indirect
|
golang.org/x/mod v0.33.0 // indirect
|
||||||
golang.org/x/net v0.51.0 // indirect
|
golang.org/x/net v0.51.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
|
|||||||
44
go.sum
44
go.sum
@@ -8,13 +8,9 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
|
||||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
|
||||||
github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58=
|
github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58=
|
||||||
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w=
|
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w=
|
||||||
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q=
|
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q=
|
||||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
|
||||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
@@ -23,23 +19,12 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
|||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
|
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
|
||||||
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
|
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
|
||||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
|
||||||
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
|
|
||||||
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
|
|
||||||
github.com/go-faster/jx v1.2.0 h1:T2YHJPrFaYu21fJtUxC9GzmluKu8rVIFDwwGBKTDseI=
|
|
||||||
github.com/go-faster/jx v1.2.0/go.mod h1:UWLOVDmMG597a5tBFPLIWJdUxz5/2emOpfsj9Neg0PE=
|
|
||||||
github.com/go-faster/yaml v0.4.6 h1:lOK/EhI04gCpPgPhgt0bChS6bvw7G3WwI8xxVe0sw9I=
|
|
||||||
github.com/go-faster/yaml v0.4.6/go.mod h1:390dRIvV4zbnO7qC9FGo6YYutc+wyyUSHBgbXL52eXk=
|
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
|
||||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
|
||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||||
|
github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk=
|
||||||
|
github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||||
@@ -102,11 +87,6 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V
|
|||||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||||
@@ -122,8 +102,6 @@ github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//J
|
|||||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
|
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
|
||||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
|
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
|
||||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
||||||
github.com/ogen-go/ogen v1.20.1 h1:AFpIeI2rS37TNIMRQTHhAkThICQpa1p+Pceu7HP7xsA=
|
|
||||||
github.com/ogen-go/ogen v1.20.1/go.mod h1:eXQeqzIfw9qUjXdpqNtkX+XCvhlWNymqU1bm7S7y8iU=
|
|
||||||
github.com/ogier/pflag v0.0.1 h1:RW6JSWSu/RkSatfcLtogGfFgpim5p7ARQ10ECk5O750=
|
github.com/ogier/pflag v0.0.1 h1:RW6JSWSu/RkSatfcLtogGfFgpim5p7ARQ10ECk5O750=
|
||||||
github.com/ogier/pflag v0.0.1/go.mod h1:zkFki7tvTa0tafRvTBIZTvzYyAu6kQhPZFnshFFPE+g=
|
github.com/ogier/pflag v0.0.1/go.mod h1:zkFki7tvTa0tafRvTBIZTvzYyAu6kQhPZFnshFFPE+g=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
@@ -164,12 +142,8 @@ github.com/samber/slog-common v0.20.0 h1:WaLnm/aCvBJSk5nR5aXZTFBaV0B47A+AEaEOiZD
|
|||||||
github.com/samber/slog-common v0.20.0/go.mod h1:+Ozat1jgnnE59UAlmNX1IF3IByHsODnnwf9jUcBZ+m8=
|
github.com/samber/slog-common v0.20.0/go.mod h1:+Ozat1jgnnE59UAlmNX1IF3IByHsODnnwf9jUcBZ+m8=
|
||||||
github.com/samber/slog-multi v1.7.1 h1:aCLXHRxgU+2v0PVlEOh7phynzM7CRo89ZgFtOwaqVEE=
|
github.com/samber/slog-multi v1.7.1 h1:aCLXHRxgU+2v0PVlEOh7phynzM7CRo89ZgFtOwaqVEE=
|
||||||
github.com/samber/slog-multi v1.7.1/go.mod h1:A4KQC99deqfkCDJcL/cO3kX6McX7FffQAx/8QHink+c=
|
github.com/samber/slog-multi v1.7.1/go.mod h1:A4KQC99deqfkCDJcL/cO3kX6McX7FffQAx/8QHink+c=
|
||||||
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
|
|
||||||
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
|
||||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
|
||||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||||
github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8=
|
github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8=
|
||||||
@@ -200,14 +174,6 @@ github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN
|
|||||||
github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0=
|
github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0=
|
||||||
github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds=
|
github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
|
||||||
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
|
|
||||||
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
|
|
||||||
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
|
|
||||||
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
|
|
||||||
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
|
|
||||||
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
|
|
||||||
go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4=
|
go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4=
|
||||||
go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
|
go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
|
||||||
go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg=
|
go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg=
|
||||||
@@ -227,8 +193,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
|
|
||||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||||
@@ -260,8 +224,6 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
@@ -272,6 +234,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
|
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||||
|
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
|||||||
6
helm/payouts/Chart.yaml
Normal file
6
helm/payouts/Chart.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: payouts
|
||||||
|
description: Payouts service Helm chart
|
||||||
|
type: application
|
||||||
|
version: 0.1.0
|
||||||
|
appVersion: "1.0.0"
|
||||||
203
helm/payouts/README.md
Normal file
203
helm/payouts/README.md
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
# payouts Helm Chart
|
||||||
|
|
||||||
|
Helm chart for deploying the **payouts** service to Kubernetes.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Kubernetes 1.21+
|
||||||
|
- Helm 3.2+
|
||||||
|
|
||||||
|
## Installing the Chart
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install payouts ./helm
|
||||||
|
```
|
||||||
|
|
||||||
|
To install into a specific namespace:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install payouts ./helm --namespace payouts --create-namespace
|
||||||
|
```
|
||||||
|
|
||||||
|
## Uninstalling the Chart
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm uninstall payouts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
All parameters are set via `values.yaml` or `--set` flags.
|
||||||
|
|
||||||
|
### Image
|
||||||
|
|
||||||
|
| Parameter | Description | Default |
|
||||||
|
|-----------|-------------|---------|
|
||||||
|
| `image.repository` | Container image repository | `payouts` |
|
||||||
|
| `image.tag` | Container image tag | `latest` |
|
||||||
|
| `image.pullPolicy` | Image pull policy | `IfNotPresent` |
|
||||||
|
|
||||||
|
### Service
|
||||||
|
|
||||||
|
| Parameter | Description | Default |
|
||||||
|
|-----------|-------------|---------|
|
||||||
|
| `service.type` | Kubernetes service type | `ClusterIP` |
|
||||||
|
| `service.port` | Service port | `8080` |
|
||||||
|
|
||||||
|
### Ingress
|
||||||
|
|
||||||
|
| Parameter | Description | Default |
|
||||||
|
|-----------|-------------|---------|
|
||||||
|
| `ingress.enabled` | Enable ingress | `false` |
|
||||||
|
| `ingress.className` | IngressClass name | `""` |
|
||||||
|
| `ingress.annotations` | Ingress annotations | `{}` |
|
||||||
|
| `ingress.host` | Ingress hostname | `payouts.example.com` |
|
||||||
|
| `ingress.path` | Ingress path | `/` |
|
||||||
|
| `ingress.pathType` | Ingress path type | `Prefix` |
|
||||||
|
| `ingress.tls` | TLS configuration | `[]` |
|
||||||
|
|
||||||
|
### TLS (application-level)
|
||||||
|
|
||||||
|
When `config.Server.Tls.Enabled` is `true` the chart mounts a TLS secret as
|
||||||
|
files into the pod at the paths defined by `config.Server.Tls.CertFile` and
|
||||||
|
`config.Server.Tls.KeyFile`.
|
||||||
|
|
||||||
|
Two modes are supported:
|
||||||
|
|
||||||
|
**Option A — use an existing secret** (type `kubernetes.io/tls`):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
config:
|
||||||
|
Server:
|
||||||
|
Tls:
|
||||||
|
Enabled: true
|
||||||
|
tls:
|
||||||
|
existingSecret: "my-tls-secret"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option B — let the chart create the secret** (supply PEM values, do not commit to VCS):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
config:
|
||||||
|
Server:
|
||||||
|
Tls:
|
||||||
|
Enabled: true
|
||||||
|
tls:
|
||||||
|
cert: |
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
...
|
||||||
|
key: |
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Description | Default |
|
||||||
|
|-----------|-------------|---------|
|
||||||
|
| `config.Server.Tls.Enabled` | Enable TLS on the HTTP server | `false` |
|
||||||
|
| `config.Server.Tls.CertFile` | Path to the certificate file inside the pod | `/etc/payouts/tls/tls.crt` |
|
||||||
|
| `config.Server.Tls.KeyFile` | Path to the private key file inside the pod | `/etc/payouts/tls/tls.key` |
|
||||||
|
| `tls.existingSecret` | Name of an existing `kubernetes.io/tls` secret to use | `""` |
|
||||||
|
| `tls.cert` | PEM-encoded certificate (used when `existingSecret` is empty) | `""` |
|
||||||
|
| `tls.key` | PEM-encoded private key (used when `existingSecret` is empty) | `""` |
|
||||||
|
|
||||||
|
> When TLS is enabled, either `tls.existingSecret` or both `tls.cert` and `tls.key` must be provided — the chart will fail with a descriptive error otherwise.
|
||||||
|
|
||||||
|
### Application Config
|
||||||
|
|
||||||
|
Non-secret application parameters are stored in a ConfigMap and mounted as
|
||||||
|
`/etc/payouts/config.yaml` inside the pod. The path is passed to the application
|
||||||
|
via the `CONFIG_PATH` environment variable.
|
||||||
|
|
||||||
|
All keys preserve the exact casing from `config/payouts.properties`.
|
||||||
|
|
||||||
|
| Parameter | Description | Default |
|
||||||
|
|-----------|-------------|---------|
|
||||||
|
| `config.Server.Port` | HTTP listen address | `:8080` |
|
||||||
|
| `config.Server.WriteTimeout` | HTTP write timeout | `35s` |
|
||||||
|
| `config.Server.ReadTimeout` | HTTP read timeout | `35s` |
|
||||||
|
| `config.Server.EnablePProfEndpoints` | Expose pprof endpoints | `false` |
|
||||||
|
| `config.Socket.MaxHttpBufferSize` | Max HTTP buffer size for socket | `2097152` |
|
||||||
|
| `config.Socket.PingInterval` | Socket ping interval | `25s` |
|
||||||
|
| `config.Socket.PingTimeout` | Socket ping timeout | `20s` |
|
||||||
|
| `config.Socket.Debug` | Enable socket debug logging | `false` |
|
||||||
|
| `config.Metrics.Endpoint` | Prometheus metrics endpoint | `/metrics` |
|
||||||
|
| `config.Metrics.HistogramBuckets` | Global histogram buckets | `0.001,...,10` |
|
||||||
|
| `config.Metrics.Http.HistogramEnabled` | Enable HTTP latency histogram | `true` |
|
||||||
|
| `config.Metrics.Http.Buckets` | HTTP histogram buckets | `0.001,...,10` |
|
||||||
|
| `config.Log.Level` | Log level | `DEBUG` |
|
||||||
|
| `config.Log.FilePath` | Log file path | `./logs/payouts.log` |
|
||||||
|
| `config.Log.TextOutput` | Use plain-text log format | `false` |
|
||||||
|
| `config.Log.StdoutEnabled` | Log to stdout | `true` |
|
||||||
|
| `config.Log.FileEnabled` | Log to file | `false` |
|
||||||
|
| `config.Database.Type` | Database type (`sqlite` or `postgres`) | `""` |
|
||||||
|
| `config.Database.LogLevel` | Database query log level | `Info` |
|
||||||
|
| `config.Database.TraceRequests` | Trace all DB requests | `false` |
|
||||||
|
| `config.Cache.TTL` | Session cache TTL | `24h` |
|
||||||
|
| `config.YooKassa.BaseUrl` | YooKassa API base URL | `https://api.yookassa.ru/v3` |
|
||||||
|
| `config.YooKassa.Timeout` | YooKassa request timeout | `2s` |
|
||||||
|
| `config.YooKassa.Retry.Enabled` | Enable request retries | `false` |
|
||||||
|
| `config.YooKassa.Retry.Count` | Retry count (incl. initial) | `3` |
|
||||||
|
| `config.YooKassa.Retry.WaitTime` | Wait time between retries | `200ms` |
|
||||||
|
| `config.YooKassa.Retry.MaxWaitTime` | Max wait time (exponential backoff) | `5s` |
|
||||||
|
| `config.YooKassa.Test` | Enable YooKassa test mode | `false` |
|
||||||
|
| `config.YooKassa.CheckAllowedCallbackAddress` | Validate callback source IP | `true` |
|
||||||
|
| `config.YooKassa.AllowedCallbackSubnets` | Allowed callback CIDR list | YooKassa subnets |
|
||||||
|
| `config.YooKassa.CallbackProcessTimeout` | Timeout to process callback | `1s` |
|
||||||
|
|
||||||
|
### Secrets
|
||||||
|
|
||||||
|
Secret values are stored in a Kubernetes Secret and injected as environment
|
||||||
|
variables. Variable names are the uppercased property key with `.` replaced by `_`.
|
||||||
|
|
||||||
|
> **These values are empty by default and must be supplied before deploying to production.**
|
||||||
|
|
||||||
|
| Env variable | Original property |
|
||||||
|
|---|---|
|
||||||
|
| `DATABASE_CONNECTION` | `Database.Connection` |
|
||||||
|
| `YOOKASSA_APIBASEKEY` | `YooKassa.ApiBaseKey` |
|
||||||
|
| `YOOKASSA_APIBASESECRET` | `YooKassa.ApiBaseSecret` |
|
||||||
|
| `YOOKASSA_APIPAYOUTKEY` | `YooKassa.ApiPayoutKey` |
|
||||||
|
| `YOOKASSA_APIPAYOUTSECRET` | `YooKassa.ApiPayoutSecret` |
|
||||||
|
|
||||||
|
Provide secrets at install/upgrade time:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install payouts ./helm \
|
||||||
|
--set secrets.DATABASE_CONNECTION="host=127.0.0.1 user=app password=s3cr3t dbname=payouts port=5432 sslmode=disable" \
|
||||||
|
--set secrets.YOOKASSA_APIBASEKEY="<key>" \
|
||||||
|
--set secrets.YOOKASSA_APIBASESECRET="<secret>" \
|
||||||
|
--set secrets.YOOKASSA_APIPAYOUTKEY="<key>" \
|
||||||
|
--set secrets.YOOKASSA_APIPAYOUTSECRET="<secret>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Or keep them in a separate values file that is **not committed to version control**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install payouts ./helm -f secrets.values.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Example `secrets.values.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
secrets:
|
||||||
|
DATABASE_CONNECTION: "host=127.0.0.1 user=payouts password=password dbname=payouts port=5432 sslmode=disable"
|
||||||
|
YOOKASSA_APIBASEKEY: "<key>"
|
||||||
|
YOOKASSA_APIBASESECRET: "<secret>"
|
||||||
|
YOOKASSA_APIPAYOUTKEY: "<key>"
|
||||||
|
YOOKASSA_APIPAYOUTSECRET: "<secret>"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ingress example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: nginx
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
host: payouts.example.com
|
||||||
|
tls:
|
||||||
|
- secretName: payouts-tls
|
||||||
|
hosts:
|
||||||
|
- payouts.example.com
|
||||||
|
```
|
||||||
45
helm/payouts/templates/NOTES.txt
Normal file
45
helm/payouts/templates/NOTES.txt
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
1. Get the application URL:
|
||||||
|
{{- if .Values.ingress.enabled }}
|
||||||
|
http{{ if .Values.ingress.tls }}s{{ end }}://{{ .Values.ingress.host }}{{ .Values.ingress.path }}
|
||||||
|
{{- else if eq .Values.service.type "NodePort" }}
|
||||||
|
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "payouts.fullname" . }})
|
||||||
|
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||||
|
echo "http://$NODE_IP:$NODE_PORT"
|
||||||
|
{{- else if eq .Values.service.type "LoadBalancer" }}
|
||||||
|
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "payouts.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||||
|
echo "http://$SERVICE_IP:{{ .Values.service.port }}"
|
||||||
|
{{- else }}
|
||||||
|
kubectl --namespace {{ .Release.Namespace }} port-forward svc/{{ include "payouts.fullname" . }} {{ .Values.service.port }}:{{ .Values.service.port }}
|
||||||
|
echo "http://127.0.0.1:{{ .Values.service.port }}"
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
2. Configuration is mounted at $CONFIG_PATH inside the pod:
|
||||||
|
/etc/payouts/config.yaml (from ConfigMap {{ include "payouts.fullname" . }})
|
||||||
|
|
||||||
|
{{- if .Values.config.Server.Tls.Enabled }}
|
||||||
|
3. TLS is ENABLED. Certificate and key are mounted from Secret {{ include "payouts.tlsSecretName" . }}:
|
||||||
|
{{ .Values.config.Server.Tls.CertFile }}
|
||||||
|
{{ .Values.config.Server.Tls.KeyFile }}
|
||||||
|
{{- if not .Values.tls.existingSecret }}
|
||||||
|
The chart created the TLS secret. To rotate the certificate, update tls.cert / tls.key and run helm upgrade.
|
||||||
|
{{- else }}
|
||||||
|
Using existing secret: {{ .Values.tls.existingSecret }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
4. Secret environment variables are injected from Secret {{ include "payouts.fullname" . }}:
|
||||||
|
{{- else }}
|
||||||
|
3. Secret environment variables are injected from Secret {{ include "payouts.fullname" . }}:
|
||||||
|
{{- end }}
|
||||||
|
DATABASE_CONNECTION, YOOKASSA_APIBASEKEY, YOOKASSA_APIBASESECRET,
|
||||||
|
YOOKASSA_APIPAYOUTKEY, YOOKASSA_APIPAYOUTSECRET
|
||||||
|
|
||||||
|
Before deploying to production, populate these values:
|
||||||
|
helm upgrade {{ .Release.Name }} ./helm \
|
||||||
|
--set secrets.DATABASE_CONNECTION="host=... dbname=..." \
|
||||||
|
--set secrets.YOOKASSA_APIBASEKEY="<key>" \
|
||||||
|
--set secrets.YOOKASSA_APIBASESECRET="<secret>" \
|
||||||
|
--set secrets.YOOKASSA_APIPAYOUTKEY="<key>" \
|
||||||
|
--set secrets.YOOKASSA_APIPAYOUTSECRET="<secret>"
|
||||||
|
|
||||||
|
Or use a separate values file that is not committed to version control:
|
||||||
|
helm upgrade {{ .Release.Name }} ./helm -f secrets.values.yaml
|
||||||
62
helm/payouts/templates/_helpers.tpl
Normal file
62
helm/payouts/templates/_helpers.tpl
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
{{/*
|
||||||
|
Expand the name of the chart.
|
||||||
|
*/}}
|
||||||
|
{{- define "payouts.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create a default fully qualified app name.
|
||||||
|
*/}}
|
||||||
|
{{- define "payouts.fullname" -}}
|
||||||
|
{{- if .Values.fullnameOverride }}
|
||||||
|
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||||
|
{{- if contains $name .Release.Name }}
|
||||||
|
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Common labels
|
||||||
|
*/}}
|
||||||
|
{{- define "payouts.labels" -}}
|
||||||
|
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{ include "payouts.selectorLabels" . }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Selector labels
|
||||||
|
*/}}
|
||||||
|
{{- define "payouts.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: {{ include "payouts.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Name of the TLS secret to mount.
|
||||||
|
Returns tls.existingSecret when set, otherwise the chart-managed secret name.
|
||||||
|
*/}}
|
||||||
|
{{- define "payouts.tlsSecretName" -}}
|
||||||
|
{{- if .Values.tls.existingSecret }}
|
||||||
|
{{- .Values.tls.existingSecret }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "%s-tls" (include "payouts.fullname" .) }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Validate TLS configuration.
|
||||||
|
*/}}
|
||||||
|
{{- define "payouts.validateTls" -}}
|
||||||
|
{{- if .Values.config.Server.Tls.Enabled }}
|
||||||
|
{{- if and (not .Values.tls.existingSecret) (or (not .Values.tls.cert) (not .Values.tls.key)) }}
|
||||||
|
{{- fail "TLS is enabled: either set tls.existingSecret or provide both tls.cert and tls.key" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
9
helm/payouts/templates/configmap.yaml
Normal file
9
helm/payouts/templates/configmap.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: {{ include "payouts.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "payouts.labels" . | nindent 4 }}
|
||||||
|
data:
|
||||||
|
config.yaml: |
|
||||||
|
{{- .Values.config | toYaml | nindent 4 }}
|
||||||
85
helm/payouts/templates/deployment.yaml
Normal file
85
helm/payouts/templates/deployment.yaml
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "payouts.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "payouts.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
replicas: {{ .Values.replicaCount }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "payouts.selectorLabels" . | nindent 6 }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
{{- include "payouts.selectorLabels" . | nindent 8 }}
|
||||||
|
annotations:
|
||||||
|
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
|
||||||
|
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
|
||||||
|
{{- if .Values.config.Server.Tls.Enabled }}
|
||||||
|
checksum/tls: {{ include (print $.Template.BasePath "/tls-secret.yaml") . | sha256sum }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: {{ .Chart.Name }}
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
env:
|
||||||
|
- name: CONFIG_PATH
|
||||||
|
value: /etc/payouts/config.yaml
|
||||||
|
{{- range $key, $value := .Values.secrets }}
|
||||||
|
- name: {{ $key }}
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ include "payouts.fullname" $ }}
|
||||||
|
key: {{ $key }}
|
||||||
|
{{- end }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: {{ trimPrefix ":" .Values.config.Server.Port | int }}
|
||||||
|
protocol: TCP
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: http
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: http
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /etc/payouts
|
||||||
|
readOnly: true
|
||||||
|
{{- if .Values.config.Server.Tls.Enabled }}
|
||||||
|
- name: tls
|
||||||
|
mountPath: /etc/payouts/tls
|
||||||
|
readOnly: true
|
||||||
|
{{- end }}
|
||||||
|
resources:
|
||||||
|
{{- toYaml .Values.resources | nindent 12 }}
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
configMap:
|
||||||
|
name: {{ include "payouts.fullname" . }}
|
||||||
|
{{- if .Values.config.Server.Tls.Enabled }}
|
||||||
|
- name: tls
|
||||||
|
secret:
|
||||||
|
secretName: {{ include "payouts.tlsSecretName" . }}
|
||||||
|
items:
|
||||||
|
- key: tls.crt
|
||||||
|
path: tls.crt
|
||||||
|
- key: tls.key
|
||||||
|
path: tls.key
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.nodeSelector }}
|
||||||
|
nodeSelector:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
31
helm/payouts/templates/ingress.yaml
Normal file
31
helm/payouts/templates/ingress.yaml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{{- if .Values.ingress.enabled }}
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: {{ include "payouts.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "payouts.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.ingress.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
{{- if .Values.ingress.className }}
|
||||||
|
ingressClassName: {{ .Values.ingress.className }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.ingress.tls }}
|
||||||
|
tls:
|
||||||
|
{{- toYaml .Values.ingress.tls | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
rules:
|
||||||
|
- host: {{ .Values.ingress.host }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: {{ .Values.ingress.path }}
|
||||||
|
pathType: {{ .Values.ingress.pathType }}
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: {{ include "payouts.fullname" . }}
|
||||||
|
port:
|
||||||
|
name: http
|
||||||
|
{{- end }}
|
||||||
11
helm/payouts/templates/secret.yaml
Normal file
11
helm/payouts/templates/secret.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: {{ include "payouts.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "payouts.labels" . | nindent 4 }}
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
{{- range $key, $value := .Values.secrets }}
|
||||||
|
{{ $key }}: {{ $value | toString | b64enc | quote }}
|
||||||
|
{{- end }}
|
||||||
15
helm/payouts/templates/service.yaml
Normal file
15
helm/payouts/templates/service.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "payouts.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "payouts.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.service.type }}
|
||||||
|
ports:
|
||||||
|
- port: {{ .Values.service.port }}
|
||||||
|
targetPort: http
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
{{- include "payouts.selectorLabels" . | nindent 4 }}
|
||||||
13
helm/payouts/templates/tls-secret.yaml
Normal file
13
helm/payouts/templates/tls-secret.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{{- if and .Values.config.Server.Tls.Enabled (not .Values.tls.existingSecret) }}
|
||||||
|
{{- include "payouts.validateTls" . }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: {{ printf "%s-tls" (include "payouts.fullname" .) }}
|
||||||
|
labels:
|
||||||
|
{{- include "payouts.labels" . | nindent 4 }}
|
||||||
|
type: kubernetes.io/tls
|
||||||
|
data:
|
||||||
|
tls.crt: {{ .Values.tls.cert | toString | b64enc }}
|
||||||
|
tls.key: {{ .Values.tls.key | toString | b64enc }}
|
||||||
|
{{- end }}
|
||||||
105
helm/payouts/values.yaml
Normal file
105
helm/payouts/values.yaml
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
replicaCount: 1
|
||||||
|
|
||||||
|
image:
|
||||||
|
repository: payouts
|
||||||
|
tag: latest
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
port: 8080
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: false
|
||||||
|
className: ""
|
||||||
|
annotations: {}
|
||||||
|
host: payouts.example.com
|
||||||
|
path: /
|
||||||
|
pathType: Prefix
|
||||||
|
tls: []
|
||||||
|
|
||||||
|
resources: {}
|
||||||
|
|
||||||
|
nodeSelector: {}
|
||||||
|
tolerations: []
|
||||||
|
affinity: {}
|
||||||
|
|
||||||
|
# Non-secret application config — rendered as /etc/payouts/config.yaml inside the pod
|
||||||
|
config:
|
||||||
|
Server:
|
||||||
|
Port: ":8080"
|
||||||
|
WriteTimeout: 35s
|
||||||
|
ReadTimeout: 35s
|
||||||
|
EnablePProfEndpoints: false
|
||||||
|
Tls:
|
||||||
|
Enabled: false
|
||||||
|
CertFile: /etc/payouts/tls/tls.crt
|
||||||
|
KeyFile: /etc/payouts/tls/tls.key
|
||||||
|
|
||||||
|
Socket:
|
||||||
|
MaxHttpBufferSize: 2097152
|
||||||
|
PingInterval: 25s
|
||||||
|
PingTimeout: 20s
|
||||||
|
Debug: false
|
||||||
|
|
||||||
|
Metrics:
|
||||||
|
Endpoint: /metrics
|
||||||
|
HistogramBuckets: "0.001,0.002,0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10"
|
||||||
|
Http:
|
||||||
|
HistogramEnabled: true
|
||||||
|
Buckets: "0.001,0.002,0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10"
|
||||||
|
|
||||||
|
Log:
|
||||||
|
Level: DEBUG
|
||||||
|
FilePath: ./logs/payouts.log
|
||||||
|
TextOutput: false
|
||||||
|
StdoutEnabled: true
|
||||||
|
FileEnabled: false
|
||||||
|
|
||||||
|
Database:
|
||||||
|
Type: ""
|
||||||
|
LogLevel: Info
|
||||||
|
TraceRequests: false
|
||||||
|
|
||||||
|
Cache:
|
||||||
|
TTL: 24h
|
||||||
|
|
||||||
|
YooKassa:
|
||||||
|
BaseUrl: https://api.yookassa.ru/v3
|
||||||
|
Timeout: 2s
|
||||||
|
Retry:
|
||||||
|
Enabled: false
|
||||||
|
Count: 3
|
||||||
|
WaitTime: 200ms
|
||||||
|
MaxWaitTime: 5s
|
||||||
|
Test: false
|
||||||
|
CheckAllowedCallbackAddress: true
|
||||||
|
AllowedCallbackSubnets: "185.71.76.0/27,185.71.77.0/27,77.75.153.0/25,77.75.156.11/32,77.75.156.35/32,77.75.154.128/25,2a02:5180::/32"
|
||||||
|
CallbackProcessTimeout: 1s
|
||||||
|
|
||||||
|
# TLS for the application server
|
||||||
|
# When config.Server.Tls.Enabled is true, a volume with the cert and key is mounted
|
||||||
|
# into the pod at the paths defined by config.Server.Tls.CertFile / KeyFile.
|
||||||
|
#
|
||||||
|
# Option A — bring your own secret (must be type kubernetes.io/tls):
|
||||||
|
# tls.existingSecret: "my-tls-secret"
|
||||||
|
#
|
||||||
|
# Option B — supply PEM values and let the chart create the secret:
|
||||||
|
# tls.cert: |
|
||||||
|
# -----BEGIN CERTIFICATE-----
|
||||||
|
# ...
|
||||||
|
# tls.key: |
|
||||||
|
# -----BEGIN PRIVATE KEY-----
|
||||||
|
# ...
|
||||||
|
tls:
|
||||||
|
existingSecret: ""
|
||||||
|
cert: ""
|
||||||
|
key: ""
|
||||||
|
|
||||||
|
# Secret values — injected as env vars (uppercase, dots → underscores)
|
||||||
|
secrets:
|
||||||
|
DATABASE_CONNECTION: ""
|
||||||
|
YOOKASSA_APIBASEKEY: ""
|
||||||
|
YOOKASSA_APIBASESECRET: ""
|
||||||
|
YOOKASSA_APIPAYOUTKEY: ""
|
||||||
|
YOOKASSA_APIPAYOUTSECRET: ""
|
||||||
36
internal/api/common/error.go
Normal file
36
internal/api/common/error.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"payouts/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Reason(reason string, params ...any) slog.Attr {
|
||||||
|
return slog.String("reason", fmt.Sprintf(reason, params...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrorResponse(w http.ResponseWriter, message string, err error, status int, logOpts ...any) {
|
||||||
|
r, size := utf8.DecodeRuneInString(message)
|
||||||
|
errorMsg := string(unicode.ToUpper(r)) + strings.ToLower(message[size:])
|
||||||
|
|
||||||
|
logFields := logOpts
|
||||||
|
if err != nil {
|
||||||
|
logFields = append([]any{slog.String("error", err.Error())}, logOpts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Error(errorMsg, logFields...)
|
||||||
|
w.Header().Set("Content-type", "application/json")
|
||||||
|
w.WriteHeader(status)
|
||||||
|
|
||||||
|
json.NewEncoder(w).Encode(&models.ErrorResp{
|
||||||
|
Message: errorMsg,
|
||||||
|
Status: status,
|
||||||
|
})
|
||||||
|
}
|
||||||
46
internal/api/health/health_handler.go
Normal file
46
internal/api/health/health_handler.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package health
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"payouts/internal/service/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Route health route
|
||||||
|
const Route = "/health"
|
||||||
|
|
||||||
|
// New constructs a new health Handler.
|
||||||
|
func New(dbService database.Service) (Handler, error) {
|
||||||
|
return &handler{
|
||||||
|
dbService: dbService,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type handler struct {
|
||||||
|
dbService database.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthHandler handles the health check requests
|
||||||
|
func (h *handler) Health(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
status := map[string]any{}
|
||||||
|
|
||||||
|
// Check database connection
|
||||||
|
err := h.dbService.HealthCheck()
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
|
status["Error"] = fmt.Sprintf("%v", err)
|
||||||
|
slog.Error("Health check failed", slog.String("error", status["Error"].(string)))
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
status["OK"] = (err == nil)
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(w)
|
||||||
|
encoder.Encode(status)
|
||||||
|
}
|
||||||
16
internal/api/health/module.go
Normal file
16
internal/api/health/module.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package health
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"go.uber.org/fx"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Module = fx.Options(
|
||||||
|
fx.Provide(New),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler health handler interface
|
||||||
|
type Handler interface {
|
||||||
|
Health(http.ResponseWriter, *http.Request)
|
||||||
|
}
|
||||||
@@ -11,9 +11,11 @@ import (
|
|||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
|
|
||||||
|
"payouts/internal/api/health"
|
||||||
"payouts/internal/api/payout"
|
"payouts/internal/api/payout"
|
||||||
"payouts/internal/api/user"
|
"payouts/internal/api/user"
|
||||||
"payouts/internal/api/version"
|
"payouts/internal/api/version"
|
||||||
|
"payouts/internal/api/widget"
|
||||||
appConfig "payouts/internal/config"
|
appConfig "payouts/internal/config"
|
||||||
"payouts/internal/service/monitoring"
|
"payouts/internal/service/monitoring"
|
||||||
)
|
)
|
||||||
@@ -21,8 +23,10 @@ import (
|
|||||||
// Module is a fx module
|
// Module is a fx module
|
||||||
var Module = fx.Options(
|
var Module = fx.Options(
|
||||||
user.Module,
|
user.Module,
|
||||||
|
health.Module,
|
||||||
payout.Module,
|
payout.Module,
|
||||||
version.Module,
|
version.Module,
|
||||||
|
widget.Module,
|
||||||
monitoring.Module,
|
monitoring.Module,
|
||||||
|
|
||||||
fx.Invoke(RegisterRoutes),
|
fx.Invoke(RegisterRoutes),
|
||||||
@@ -39,7 +43,9 @@ type Params struct {
|
|||||||
|
|
||||||
PayoutHandler payout.Handler
|
PayoutHandler payout.Handler
|
||||||
UserHandler user.Handler
|
UserHandler user.Handler
|
||||||
|
HealthHandler health.Handler
|
||||||
Version version.Handler
|
Version version.Handler
|
||||||
|
Widget widget.Handler
|
||||||
|
|
||||||
Metrics monitoring.Metrics
|
Metrics monitoring.Metrics
|
||||||
}
|
}
|
||||||
@@ -50,7 +56,10 @@ func RegisterRoutes(p Params, lc fx.Lifecycle) {
|
|||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
router.StrictSlash(true)
|
router.StrictSlash(true)
|
||||||
|
|
||||||
|
// Version endpoint
|
||||||
router.HandleFunc(version.Route, p.Version.VersionHandler).Methods(http.MethodGet)
|
router.HandleFunc(version.Route, p.Version.VersionHandler).Methods(http.MethodGet)
|
||||||
|
// Health check endpoint
|
||||||
|
router.HandleFunc(health.Route, p.HealthHandler.Health).Methods(http.MethodGet)
|
||||||
|
|
||||||
if p.AppConfig.Server.EnablePProfEndpoints {
|
if p.AppConfig.Server.EnablePProfEndpoints {
|
||||||
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||||
@@ -67,9 +76,13 @@ func RegisterRoutes(p Params, lc fx.Lifecycle) {
|
|||||||
userRouter.HandleFunc(user.LoginRoute, p.UserHandler.UserLogin).Methods(http.MethodPost)
|
userRouter.HandleFunc(user.LoginRoute, p.UserHandler.UserLogin).Methods(http.MethodPost)
|
||||||
|
|
||||||
payoutRouter := apiRouter.PathPrefix(payout.BaseRoute).Subrouter()
|
payoutRouter := apiRouter.PathPrefix(payout.BaseRoute).Subrouter()
|
||||||
|
payoutRouter.HandleFunc(payout.BanksRoute, p.PayoutHandler.GetSbpBanks).Methods(http.MethodGet)
|
||||||
payoutRouter.HandleFunc(payout.CreateRoute, p.PayoutHandler.PayoutCreate).Methods(http.MethodPost)
|
payoutRouter.HandleFunc(payout.CreateRoute, p.PayoutHandler.PayoutCreate).Methods(http.MethodPost)
|
||||||
payoutRouter.HandleFunc(payout.CallbackRoute, p.PayoutHandler.PayoutCallback).Methods(http.MethodPost)
|
payoutRouter.HandleFunc(payout.CallbackRoute, p.PayoutHandler.PayoutCallback).Methods(http.MethodPost)
|
||||||
|
|
||||||
|
// Widget endpoint
|
||||||
|
router.HandleFunc(widget.WidgetPage, p.Widget.WidgetHandler).Methods(http.MethodGet)
|
||||||
|
|
||||||
// collect api metrics
|
// collect api metrics
|
||||||
apiRouter.Use(p.Metrics.GetMiddleware())
|
apiRouter.Use(p.Metrics.GetMiddleware())
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package payout
|
package payout
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -12,6 +13,7 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
|
|
||||||
|
"payouts/internal/api/common"
|
||||||
"payouts/internal/config"
|
"payouts/internal/config"
|
||||||
"payouts/internal/models"
|
"payouts/internal/models"
|
||||||
"payouts/internal/service/cache"
|
"payouts/internal/service/cache"
|
||||||
@@ -99,18 +101,36 @@ func (p *payoutHandler) checkAllowedIpCallback(ipStr string) bool {
|
|||||||
|
|
||||||
// GetSbpBanks implements [Handler].
|
// GetSbpBanks implements [Handler].
|
||||||
func (p *payoutHandler) GetSbpBanks(w http.ResponseWriter, r *http.Request) {
|
func (p *payoutHandler) GetSbpBanks(w http.ResponseWriter, r *http.Request) {
|
||||||
panic("unimplemented")
|
w.Header().Set("Content-type", "application/json")
|
||||||
|
|
||||||
|
banksResp, err := p.yooKassa.GetSbpBanks(yookassa.WithContext(r.Context()))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
status := http.StatusBadRequest
|
||||||
|
var yError *yookassa.Error
|
||||||
|
if errors.As(err, &yError) {
|
||||||
|
status = yError.Status
|
||||||
|
}
|
||||||
|
common.ErrorResponse(w, "failed to retrieve sbp banks", err, status)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(w)
|
||||||
|
err = encoder.Encode(banksResp)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponse(w, "failed to encode response", err, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PaymentCreate implements [Handler].
|
// PayoutCreate implements [Handler].
|
||||||
func (p *payoutHandler) PayoutCreate(w http.ResponseWriter, r *http.Request) {
|
func (p *payoutHandler) PayoutCreate(w http.ResponseWriter, r *http.Request) {
|
||||||
errResponse := func(message string, err error, status int) {
|
defer r.Body.Close()
|
||||||
http.Error(w, errors.Join(errors.New(message), err).Error(), status)
|
|
||||||
}
|
w.Header().Set("Content-type", "application/json")
|
||||||
|
|
||||||
userSession, err := p.getSession(r)
|
userSession, err := p.getSession(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errResponse("unauthorized", err, http.StatusUnauthorized)
|
common.ErrorResponse(w, "unauthorized", err, http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,8 +140,7 @@ func (p *payoutHandler) PayoutCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
decoder := json.NewDecoder(r.Body)
|
decoder := json.NewDecoder(r.Body)
|
||||||
err = decoder.Decode(&payoutReq)
|
err = decoder.Decode(&payoutReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to decode request body", slog.String("error", err.Error()))
|
common.ErrorResponse(w, "failed to decode request body", err, http.StatusBadRequest)
|
||||||
errResponse("failed to decode request body", err, http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,34 +150,78 @@ func (p *payoutHandler) PayoutCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
IdempotenceKey: idempotenceKey,
|
IdempotenceKey: idempotenceKey,
|
||||||
Type: payoutReq.PayoutType.String(),
|
Type: payoutReq.PayoutType.String(),
|
||||||
Amount: payoutReq.Amount,
|
Amount: payoutReq.Amount,
|
||||||
Status: orm.StatusCreated,
|
Status: models.StatusCreated.String(),
|
||||||
}
|
}
|
||||||
err = p.dbService.CreatePayout(payoutModel, database.WithContext(r.Context()))
|
err = p.dbService.CreatePayout(payoutModel, database.WithContext(r.Context()))
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponse(w, "failed to create payout data", err, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
slog.Debug(fmt.Sprintf("Received create payload request: %v from user %v", payoutReq, userSession))
|
slog.Debug(fmt.Sprintf("Received create payload request: %v from user %v", payoutReq, userSession))
|
||||||
|
|
||||||
payoutResp, err := p.yooKassa.CreatePayout(payoutReq, userSession, idempotenceKey, yookassa.WithContext(r.Context()))
|
payoutResp, err := p.yooKassa.CreatePayout(payoutReq, userSession, idempotenceKey, yookassa.WithContext(r.Context()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to create payout request", slog.String("error", err.Error()))
|
status := http.StatusBadRequest
|
||||||
errResponse("failed to create payout request", err, http.StatusBadRequest)
|
var yError *yookassa.Error
|
||||||
|
if errors.As(err, &yError) {
|
||||||
|
status = yError.Status
|
||||||
|
}
|
||||||
|
common.ErrorResponse(w, "failed to create payout request", err, status)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedRows, err := p.dbService.UpdatePayoutById(payoutModel.ID, orm.Payout{
|
||||||
|
PayoutID: payoutResp.ID,
|
||||||
|
Status: payoutResp.Status.String(),
|
||||||
|
}, database.WithContext(r.Context()))
|
||||||
|
|
||||||
|
if err != nil || updatedRows == 0 {
|
||||||
|
common.ErrorResponse(w, "failed to update payout data", err, http.StatusInternalServerError, slog.String("id", fmt.Sprintf("%d", payoutModel.ID)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
encoder := json.NewEncoder(w)
|
encoder := json.NewEncoder(w)
|
||||||
encoder.Encode(payoutResp)
|
err = encoder.Encode(payoutResp)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponse(w, "failed to encode response", err, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PaymentCallback implements [Handler].
|
func (p *payoutHandler) delayedPayoutUpdate(ctx context.Context, payoutData *yookassa.PayoutResponse) {
|
||||||
func (p *payoutHandler) PayoutCallback(w http.ResponseWriter, r *http.Request) {
|
<-ctx.Done()
|
||||||
inData := map[string]any{}
|
slog.Info("Updating payout data received from callback")
|
||||||
decoder := json.NewDecoder(r.Body)
|
|
||||||
decoder.Decode(&inData)
|
|
||||||
|
|
||||||
|
updatedRows, err := p.dbService.UpdatePayoutByPayoutID(payoutData.ID, orm.Payout{
|
||||||
|
Status: payoutData.Status.String(),
|
||||||
|
}, database.WithContext(context.Background()))
|
||||||
|
|
||||||
|
if err != nil || updatedRows == 0 {
|
||||||
|
slog.Error("Failed to update paylout data", slog.String("error", fmt.Sprintf("%v", err)), slog.Int("rows_updated", updatedRows))
|
||||||
|
} else {
|
||||||
|
slog.Info("Successfully updated payout data", slog.String("payout_id", payoutData.ID), slog.String("new_status", payoutData.Status.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PayoutCallback implements [Handler].
|
||||||
|
func (p *payoutHandler) PayoutCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
// todo: check also the X-real-ip and/or X-Forwarded-For
|
// todo: check also the X-real-ip and/or X-Forwarded-For
|
||||||
if !p.checkAllowedIpCallback(r.RemoteAddr) {
|
if p.yookassaConf.CheckAllowedCallbackAddress && !p.checkAllowedIpCallback(r.RemoteAddr) {
|
||||||
slog.Error(fmt.Sprintf("Callback came from unallowed ip: %s", r.RemoteAddr))
|
common.ErrorResponse(w, "unallowed", nil, http.StatusForbidden, common.Reason("Callback came from unallowed ip: %s", r.RemoteAddr))
|
||||||
http.Error(w, "unallowed", http.StatusForbidden)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info(fmt.Sprintf("Received callback from %s with object %v with headers %v", r.RemoteAddr, inData, r.Header))
|
payoutData := &yookassa.PayoutResponse{}
|
||||||
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
err := decoder.Decode(payoutData)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponse(w, "bad request", nil, http.StatusBadRequest, common.Reason("Failed to decode payload data: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), p.yookassaConf.CallbackProcessTimeout)
|
||||||
|
go p.delayedPayoutUpdate(ctx, payoutData)
|
||||||
|
|
||||||
|
slog.Debug(fmt.Sprintf("Received callback from %s with object %v with headers %v", r.RemoteAddr, payoutData, r.Header))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package user
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -12,6 +10,7 @@ import (
|
|||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
|
"payouts/internal/api/common"
|
||||||
"payouts/internal/config"
|
"payouts/internal/config"
|
||||||
"payouts/internal/models"
|
"payouts/internal/models"
|
||||||
"payouts/internal/service/cache"
|
"payouts/internal/service/cache"
|
||||||
@@ -52,28 +51,21 @@ func NewUserHandler(p Params) (Handler, error) {
|
|||||||
func (u *userHandler) UserRegister(w http.ResponseWriter, r *http.Request) {
|
func (u *userHandler) UserRegister(w http.ResponseWriter, r *http.Request) {
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
|
|
||||||
errResponse := func(message string, err error, status int) {
|
|
||||||
http.Error(w, errors.Join(errors.New(message), err).Error(), status)
|
|
||||||
}
|
|
||||||
|
|
||||||
user := models.UserRegister{}
|
user := models.UserRegister{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&user)
|
err := json.NewDecoder(r.Body).Decode(&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to get password hash", slog.String("error", err.Error()))
|
common.ErrorResponse(w, "failed to decode request body", err, http.StatusBadRequest)
|
||||||
errResponse("failed to decode request body", err, http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Passwd != user.PasswdCfm || len(user.Passwd) == 0 || len(user.Phone) == 0 || len(user.TIN) == 0 {
|
if user.Passwd != user.PasswdCfm || len(user.Passwd) == 0 || len(user.Phone) == 0 || len(user.TIN) == 0 {
|
||||||
slog.Error("No required parameters passed")
|
common.ErrorResponse(w, "invalid parameters", nil, http.StatusBadRequest)
|
||||||
errResponse("invalid parameters", nil, http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Passwd), bcrypt.DefaultCost)
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Passwd), bcrypt.DefaultCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to get password hash", slog.String("error", err.Error()))
|
common.ErrorResponse(w, "internal error", nil, http.StatusInternalServerError, common.Reason("failed to get password hash: %v", err))
|
||||||
errResponse("internal error", nil, http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user.PasswdHash = string(hashedPassword)
|
user.PasswdHash = string(hashedPassword)
|
||||||
@@ -84,8 +76,7 @@ func (u *userHandler) UserRegister(w http.ResponseWriter, r *http.Request) {
|
|||||||
// todo: add data validation
|
// todo: add data validation
|
||||||
err = u.dbService.CreateUser(&ormUser, database.WithContext(r.Context()))
|
err = u.dbService.CreateUser(&ormUser, database.WithContext(r.Context()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to create user", slog.String("error", err.Error()))
|
common.ErrorResponse(w, "failed to create user", err, http.StatusBadRequest)
|
||||||
errResponse("failed to create user", err, http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,33 +87,27 @@ func (u *userHandler) UserRegister(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (u *userHandler) UserLogin(w http.ResponseWriter, r *http.Request) {
|
func (u *userHandler) UserLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
|
|
||||||
errResponse := func(message string, err error, status int) {
|
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
|
||||||
http.Error(w, errors.Join(errors.New(message), err).Error(), status)
|
|
||||||
}
|
|
||||||
|
|
||||||
user := models.UserLoginReq{}
|
user := models.UserLoginReq{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&user)
|
err := json.NewDecoder(r.Body).Decode(&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errResponse("failed to decode request body", err, http.StatusBadRequest)
|
common.ErrorResponse(w, "failed to decode request body", err, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(user.Phone) == 0 || len(user.Passwd) == 0 {
|
if len(user.Phone) == 0 || len(user.Passwd) == 0 {
|
||||||
slog.Error("No required parameters passed")
|
common.ErrorResponse(w, "invalid parameters", nil, http.StatusBadRequest, common.Reason("no user or password passed"))
|
||||||
errResponse("invalid parameters", nil, http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ormUser, err := u.dbService.GetUser(&orm.User{Phone: user.Phone}, database.WithContext(r.Context()))
|
ormUser, err := u.dbService.GetUser(&orm.User{Phone: user.Phone}, database.WithContext(r.Context()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errResponse("invalid credentials", nil, http.StatusUnauthorized)
|
common.ErrorResponse(w, "invalid credentials", nil, http.StatusUnauthorized, common.Reason("no user found by number %s", user.Phone))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = bcrypt.CompareHashAndPassword([]byte(ormUser.PasswdHash), []byte(user.Passwd))
|
err = bcrypt.CompareHashAndPassword([]byte(ormUser.PasswdHash), []byte(user.Passwd))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errResponse("invalid credentials", nil, http.StatusUnauthorized)
|
common.ErrorResponse(w, "invalid credentials", nil, http.StatusUnauthorized, common.Reason("password does not match"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +123,7 @@ func (u *userHandler) UserLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
err = json.NewEncoder(w).Encode(resp)
|
err = json.NewEncoder(w).Encode(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errResponse("failed to encode response", err, http.StatusInternalServerError)
|
common.ErrorResponse(w, "failed to encode response", err, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package version
|
|||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"runtime/debug"
|
||||||
|
|
||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
|
|
||||||
@@ -39,7 +40,20 @@ type handler struct {
|
|||||||
func (h *handler) VersionHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) VersionHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
ver := h.version
|
||||||
|
if ver == "unknown" {
|
||||||
|
buildInfo, ok := debug.ReadBuildInfo()
|
||||||
|
if ok {
|
||||||
|
for _, setting := range buildInfo.Settings {
|
||||||
|
if setting.Key == "vcs.revision" {
|
||||||
|
ver = ver + "-" + setting.Value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
io.WriteString(w, h.version)
|
io.WriteString(w, ver+"\n")
|
||||||
}
|
}
|
||||||
9
internal/api/widget/module.go
Normal file
9
internal/api/widget/module.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package widget
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/fx"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Module = fx.Options(
|
||||||
|
fx.Provide(NewWidgetHandler),
|
||||||
|
)
|
||||||
55
internal/api/widget/widget_handler.go
Normal file
55
internal/api/widget/widget_handler.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package widget
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"go.uber.org/fx"
|
||||||
|
|
||||||
|
"payouts/internal/service/yookassa"
|
||||||
|
yookassaConf "payouts/internal/service/yookassa/config"
|
||||||
|
"payouts/internal/templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
const WidgetPage = "/payout/widget"
|
||||||
|
|
||||||
|
type Handler interface {
|
||||||
|
WidgetHandler(http.ResponseWriter, *http.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
type widgetHandler struct {
|
||||||
|
template *template.Template
|
||||||
|
config yookassaConf.YooKassa
|
||||||
|
}
|
||||||
|
|
||||||
|
// Params represents the module input params
|
||||||
|
type Params struct {
|
||||||
|
fx.In
|
||||||
|
|
||||||
|
YookassaService yookassa.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWidgetHandler(p Params) (Handler, error) {
|
||||||
|
return &widgetHandler{
|
||||||
|
template: templates.Templates,
|
||||||
|
config: p.YookassaService.GetConfig(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WidgetHandler renders the payouts widget page
|
||||||
|
func (h *widgetHandler) WidgetHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := struct {
|
||||||
|
ApiPayoutKey string
|
||||||
|
WidgetVersion string
|
||||||
|
}{
|
||||||
|
ApiPayoutKey: h.config.ApiPayoutKey,
|
||||||
|
WidgetVersion: h.config.WidgetVersion,
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
err := h.template.ExecuteTemplate(w, "payouts-widget.html", data)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,6 +66,9 @@ func NewAppConfig() (*App, error) {
|
|||||||
tempConf.SetConfigName(confName)
|
tempConf.SetConfigName(confName)
|
||||||
tempConf.SetConfigType(confType)
|
tempConf.SetConfigType(confType)
|
||||||
|
|
||||||
|
tempConf.AutomaticEnv()
|
||||||
|
tempConf.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
|
|
||||||
err := tempConf.ReadInConfig()
|
err := tempConf.ReadInConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// complain on missed non-default config
|
// complain on missed non-default config
|
||||||
|
|||||||
6
internal/models/error.go
Normal file
6
internal/models/error.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type ErrorResp struct {
|
||||||
|
Status int `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@ type PayoutType int64
|
|||||||
const (
|
const (
|
||||||
TypeSBP PayoutType = iota
|
TypeSBP PayoutType = iota
|
||||||
TypeYooMoney
|
TypeYooMoney
|
||||||
|
TypeCard
|
||||||
|
TypeCardWidget
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r PayoutType) String() string {
|
func (r PayoutType) String() string {
|
||||||
@@ -18,6 +20,10 @@ func (r PayoutType) String() string {
|
|||||||
return "spb"
|
return "spb"
|
||||||
case TypeYooMoney:
|
case TypeYooMoney:
|
||||||
return "yoo_money"
|
return "yoo_money"
|
||||||
|
case TypeCard:
|
||||||
|
return "bank_card"
|
||||||
|
case TypeCardWidget:
|
||||||
|
return "widget"
|
||||||
}
|
}
|
||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
@@ -33,20 +39,81 @@ func (r *PayoutType) UnmarshalText(text []byte) (err error) {
|
|||||||
*r = TypeSBP
|
*r = TypeSBP
|
||||||
case "yoo_money":
|
case "yoo_money":
|
||||||
*r = TypeYooMoney
|
*r = TypeYooMoney
|
||||||
|
case "bank_card":
|
||||||
|
*r = TypeCard
|
||||||
|
case "widget":
|
||||||
|
*r = TypeCardWidget
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("invalid payment type: %s", s)
|
err = fmt.Errorf("invalid payout type: %s", s)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PayoutStatus int64
|
||||||
|
|
||||||
|
const (
|
||||||
|
StatusCreated PayoutStatus = iota
|
||||||
|
StatusCanceled
|
||||||
|
StatusPending
|
||||||
|
StatusSucceeded
|
||||||
|
StatusFailed
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r PayoutStatus) String() string {
|
||||||
|
switch r {
|
||||||
|
case StatusCreated:
|
||||||
|
return "created"
|
||||||
|
case StatusCanceled:
|
||||||
|
return "canceled"
|
||||||
|
case StatusPending:
|
||||||
|
return "pending"
|
||||||
|
case StatusSucceeded:
|
||||||
|
return "succeeded"
|
||||||
|
case StatusFailed:
|
||||||
|
return "failed"
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r PayoutStatus) MarshalText() (text []byte, err error) {
|
||||||
|
return []byte(r.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PayoutStatus) UnmarshalText(text []byte) (err error) {
|
||||||
|
s := strings.ToLower(string(text))
|
||||||
|
switch s {
|
||||||
|
case "canceled":
|
||||||
|
*r = StatusCanceled
|
||||||
|
case "created":
|
||||||
|
*r = StatusCreated
|
||||||
|
case "pending":
|
||||||
|
*r = StatusPending
|
||||||
|
case "succeeded":
|
||||||
|
*r = StatusSucceeded
|
||||||
|
case "failed":
|
||||||
|
*r = StatusFailed
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("invalid payout type: %s", s)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type SBPBank struct {
|
||||||
|
BankID string `json:"bank_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Bic string `json:"bic"`
|
||||||
|
}
|
||||||
|
|
||||||
type PayoutReq struct {
|
type PayoutReq struct {
|
||||||
PayoutType PayoutType `json:"payout_type"`
|
PayoutType PayoutType `json:"payout_type"`
|
||||||
|
PayoutToken string `json:"payout_token"`
|
||||||
AccountNumber string `json:"account_number"`
|
AccountNumber string `json:"account_number"`
|
||||||
|
BankID string `json:"bank_id"`
|
||||||
|
CardNumber string `json:"card_number"`
|
||||||
Amount float32 `json:"amount"`
|
Amount float32 `json:"amount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PayoutResp struct {
|
type PayoutResp struct {
|
||||||
Result string `json:"result"`
|
ID string `json:"payout_id,omitempty"`
|
||||||
PayoutID string `json:"payout_id,omitempty"`
|
Status PayoutStatus `json:"payout_status,omitempty"`
|
||||||
ErrorReason string `json:"error_reason,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,8 +98,28 @@ func (d *dbService) CreatePayout(payoutModel *orm.Payout, opts ...Optional) erro
|
|||||||
return gorm.G[orm.Payout](d.db).Create(p.ctx, payoutModel)
|
return gorm.G[orm.Payout](d.db).Create(p.ctx, payoutModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePayout implements [Service].
|
// UpdatePayoutById implements [Service].
|
||||||
func (d *dbService) UpdatePayout(payoutModel *orm.Payout, opts ...Optional) error {
|
func (d *dbService) UpdatePayoutById(id uint, updateModel orm.Payout, opts ...Optional) (int, error) {
|
||||||
// p := d.getParams(opts...)
|
p := d.getParams(opts...)
|
||||||
panic("unimplemented")
|
return gorm.G[orm.Payout](d.db).Where("id = ?", id).Updates(p.ctx, updateModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePayoutByPayoutID implements [Service].
|
||||||
|
func (d *dbService) UpdatePayoutByPayoutID(payoutId string, updateModel orm.Payout, opts ...Optional) (int, error) {
|
||||||
|
p := d.getParams(opts...)
|
||||||
|
return gorm.G[orm.Payout](d.db).Where("payout_id = ?", payoutId).Updates(p.ctx, updateModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthCheck implements [Service].
|
||||||
|
func (d *dbService) HealthCheck() error {
|
||||||
|
if d.db == nil {
|
||||||
|
return errors.New("database connection is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := d.db.DB()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.Ping()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ type Service interface {
|
|||||||
GetUser(user *orm.User, opts ...Optional) (orm.User, error)
|
GetUser(user *orm.User, opts ...Optional) (orm.User, error)
|
||||||
GetPayout(payoutModel *orm.Payout, opts ...Optional) (orm.Payout, error)
|
GetPayout(payoutModel *orm.Payout, opts ...Optional) (orm.Payout, error)
|
||||||
CreatePayout(payoutModel *orm.Payout, opts ...Optional) error
|
CreatePayout(payoutModel *orm.Payout, opts ...Optional) error
|
||||||
UpdatePayout(payoutModel *orm.Payout, opts ...Optional) error
|
UpdatePayoutById(id uint, updateModel orm.Payout, opts ...Optional) (int, error)
|
||||||
|
UpdatePayoutByPayoutID(payoutId string, updateModel orm.Payout, opts ...Optional) (int, error)
|
||||||
|
HealthCheck() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Params represents the module input params
|
// Params represents the module input params
|
||||||
|
|||||||
@@ -1,61 +1,9 @@
|
|||||||
package orm
|
package orm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PayoutStatus int64
|
|
||||||
|
|
||||||
const (
|
|
||||||
StatusCreated PayoutStatus = iota
|
|
||||||
StatusCanceled
|
|
||||||
StatusPending
|
|
||||||
StatusSucceeded
|
|
||||||
StatusFailed
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r PayoutStatus) String() string {
|
|
||||||
switch r {
|
|
||||||
case StatusCreated:
|
|
||||||
return "created"
|
|
||||||
case StatusCanceled:
|
|
||||||
return "canceled"
|
|
||||||
case StatusPending:
|
|
||||||
return "pending"
|
|
||||||
case StatusSucceeded:
|
|
||||||
return "succeeded"
|
|
||||||
case StatusFailed:
|
|
||||||
return "failed"
|
|
||||||
}
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r PayoutStatus) MarshalText() (text []byte, err error) {
|
|
||||||
return []byte(r.String()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *PayoutStatus) UnmarshalText(text []byte) (err error) {
|
|
||||||
s := strings.ToLower(string(text))
|
|
||||||
switch s {
|
|
||||||
case "canceled":
|
|
||||||
*r = StatusCanceled
|
|
||||||
case "created":
|
|
||||||
*r = StatusCreated
|
|
||||||
case "pending":
|
|
||||||
*r = StatusPending
|
|
||||||
case "succeeded":
|
|
||||||
*r = StatusSucceeded
|
|
||||||
case "failed":
|
|
||||||
*r = StatusFailed
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("invalid payment type: %s", s)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type Payout struct {
|
type Payout struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
|
|
||||||
@@ -69,6 +17,6 @@ type Payout struct {
|
|||||||
AccountNumber string
|
AccountNumber string
|
||||||
Amount float32
|
Amount float32
|
||||||
Currency string
|
Currency string
|
||||||
Status PayoutStatus
|
Status string
|
||||||
Test bool
|
Test bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,3 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) New Cloud Technologies, Ltd., 2013-2026
|
|
||||||
*
|
|
||||||
* You can not use the contents of the file in any way without New Cloud Technologies Ltd. written permission.
|
|
||||||
* To obtain such a permit, you should contact New Cloud Technologies, Ltd. at https://myoffice.ru/contacts/
|
|
||||||
*/
|
|
||||||
|
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -5,12 +5,24 @@ import "time"
|
|||||||
type YooKassa struct {
|
type YooKassa struct {
|
||||||
BaseUrl string
|
BaseUrl string
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
|
Retry Retry
|
||||||
Test bool
|
Test bool
|
||||||
|
|
||||||
AllowedCallbackSubnets []string
|
CheckAllowedCallbackAddress bool
|
||||||
|
AllowedCallbackSubnets []string
|
||||||
|
|
||||||
ApiBaseKey string
|
ApiBaseKey string
|
||||||
ApiBaseSecret string
|
ApiBaseSecret string
|
||||||
ApiPaymentKey string
|
ApiPayoutKey string
|
||||||
ApiPaymentSecret string
|
ApiPayoutSecret string
|
||||||
|
WidgetVersion string
|
||||||
|
|
||||||
|
CallbackProcessTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type Retry struct {
|
||||||
|
Enabled bool
|
||||||
|
Count int
|
||||||
|
WaitTime time.Duration
|
||||||
|
MaxWaitTime time.Duration
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,347 +0,0 @@
|
|||||||
// Code generated by ogen, DO NOT EDIT.
|
|
||||||
|
|
||||||
package gen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
ht "github.com/ogen-go/ogen/http"
|
|
||||||
"github.com/ogen-go/ogen/middleware"
|
|
||||||
"github.com/ogen-go/ogen/ogenerrors"
|
|
||||||
"github.com/ogen-go/ogen/ogenregex"
|
|
||||||
"github.com/ogen-go/ogen/otelogen"
|
|
||||||
"go.opentelemetry.io/otel"
|
|
||||||
"go.opentelemetry.io/otel/attribute"
|
|
||||||
"go.opentelemetry.io/otel/metric"
|
|
||||||
"go.opentelemetry.io/otel/trace"
|
|
||||||
)
|
|
||||||
|
|
||||||
var regexMap = map[string]ogenregex.Regexp{
|
|
||||||
"(.)([0-9а-яА-Яa-zA-Z]+)(.)": ogenregex.MustCompile("(.)([0-9а-яА-Яa-zA-Z]+)(.)"),
|
|
||||||
"(?s).{1,210}": ogenregex.MustCompile("(?s).{1,210}"),
|
|
||||||
"([\\d]{20})|(0)": ogenregex.MustCompile("([\\d]{20})|(0)"),
|
|
||||||
"([\\d]{8})|(0)": ogenregex.MustCompile("([\\d]{8})|(0)"),
|
|
||||||
"(^00[1-9]{1}$)|(^0[1-6]{1}[0-9]{1}$)|(^07[0-3]{1}$)": ogenregex.MustCompile("(^00[1-9]{1}$)|(^0[1-6]{1}[0-9]{1}$)|(^07[0-3]{1}$)"),
|
|
||||||
"[0-9]{1,150}": ogenregex.MustCompile("[0-9]{1,150}"),
|
|
||||||
"[0-9]{10}": ogenregex.MustCompile("[0-9]{10}"),
|
|
||||||
"[0-9]{11,33}": ogenregex.MustCompile("[0-9]{11,33}"),
|
|
||||||
"[0-9]{14,19}": ogenregex.MustCompile("[0-9]{14,19}"),
|
|
||||||
"[0-9]{16,19}": ogenregex.MustCompile("[0-9]{16,19}"),
|
|
||||||
"[0-9]{24}": ogenregex.MustCompile("[0-9]{24}"),
|
|
||||||
"[0-9]{2}": ogenregex.MustCompile("[0-9]{2}"),
|
|
||||||
"[0-9]{4,15}": ogenregex.MustCompile("[0-9]{4,15}"),
|
|
||||||
"[0-9]{4}": ogenregex.MustCompile("[0-9]{4}"),
|
|
||||||
"[0-9]{6}": ogenregex.MustCompile("[0-9]{6}"),
|
|
||||||
"[0-9]{9}": ogenregex.MustCompile("[0-9]{9}"),
|
|
||||||
"[0-9a-zA-Z+-_.]{1,64}": ogenregex.MustCompile("[0-9a-zA-Z+-_.]{1,64}"),
|
|
||||||
"[A-Z]{3}": ogenregex.MustCompile("[A-Z]{3}"),
|
|
||||||
"[a-zA-Z '-]{0,26}": ogenregex.MustCompile("[a-zA-Z '-]{0,26}"),
|
|
||||||
"[a-zA-Z0-9]{12}": ogenregex.MustCompile("[a-zA-Z0-9]{12}"),
|
|
||||||
"\\d{1,20}": ogenregex.MustCompile("\\d{1,20}"),
|
|
||||||
"\\d{10}|\\d{12}": ogenregex.MustCompile("\\d{10}|\\d{12}"),
|
|
||||||
"\\d{20}": ogenregex.MustCompile("\\d{20}"),
|
|
||||||
"\\d{9}": ogenregex.MustCompile("\\d{9}"),
|
|
||||||
"^[0]{1}$": ogenregex.MustCompile("^[0]{1}$"),
|
|
||||||
"^[\\-a-zA-Zа-яёА-ЯЁ ]]*$": ogenregex.MustCompile("^[\\-a-zA-Zа-яёА-ЯЁ ]]*$"),
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
// Allocate option closure once.
|
|
||||||
clientSpanKind = trace.WithSpanKind(trace.SpanKindClient)
|
|
||||||
// Allocate option closure once.
|
|
||||||
serverSpanKind = trace.WithSpanKind(trace.SpanKindServer)
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
optionFunc[C any] func(*C)
|
|
||||||
otelOptionFunc func(*otelConfig)
|
|
||||||
)
|
|
||||||
|
|
||||||
type otelConfig struct {
|
|
||||||
TracerProvider trace.TracerProvider
|
|
||||||
Tracer trace.Tracer
|
|
||||||
MeterProvider metric.MeterProvider
|
|
||||||
Meter metric.Meter
|
|
||||||
Attributes []attribute.KeyValue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *otelConfig) initOTEL() {
|
|
||||||
if cfg.TracerProvider == nil {
|
|
||||||
cfg.TracerProvider = otel.GetTracerProvider()
|
|
||||||
}
|
|
||||||
if cfg.MeterProvider == nil {
|
|
||||||
cfg.MeterProvider = otel.GetMeterProvider()
|
|
||||||
}
|
|
||||||
cfg.Tracer = cfg.TracerProvider.Tracer(otelogen.Name,
|
|
||||||
trace.WithInstrumentationVersion(otelogen.SemVersion()),
|
|
||||||
)
|
|
||||||
cfg.Meter = cfg.MeterProvider.Meter(otelogen.Name,
|
|
||||||
metric.WithInstrumentationVersion(otelogen.SemVersion()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorHandler is error handler.
|
|
||||||
type ErrorHandler = ogenerrors.ErrorHandler
|
|
||||||
|
|
||||||
type serverConfig struct {
|
|
||||||
otelConfig
|
|
||||||
NotFound http.HandlerFunc
|
|
||||||
MethodNotAllowed func(w http.ResponseWriter, r *http.Request, allowed string)
|
|
||||||
ErrorHandler ErrorHandler
|
|
||||||
Prefix string
|
|
||||||
Middleware Middleware
|
|
||||||
MaxMultipartMemory int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServerOption is server config option.
|
|
||||||
type ServerOption interface {
|
|
||||||
applyServer(*serverConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ ServerOption = (optionFunc[serverConfig])(nil)
|
|
||||||
|
|
||||||
func (o optionFunc[C]) applyServer(c *C) {
|
|
||||||
o(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ ServerOption = (otelOptionFunc)(nil)
|
|
||||||
|
|
||||||
func (o otelOptionFunc) applyServer(c *serverConfig) {
|
|
||||||
o(&c.otelConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newServerConfig(opts ...ServerOption) serverConfig {
|
|
||||||
cfg := serverConfig{
|
|
||||||
NotFound: http.NotFound,
|
|
||||||
MethodNotAllowed: nil,
|
|
||||||
ErrorHandler: ogenerrors.DefaultErrorHandler,
|
|
||||||
Middleware: nil,
|
|
||||||
MaxMultipartMemory: 32 << 20, // 32 MB
|
|
||||||
}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt.applyServer(&cfg)
|
|
||||||
}
|
|
||||||
cfg.initOTEL()
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
type baseServer struct {
|
|
||||||
cfg serverConfig
|
|
||||||
requests metric.Int64Counter
|
|
||||||
errors metric.Int64Counter
|
|
||||||
duration metric.Float64Histogram
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s baseServer) notFound(w http.ResponseWriter, r *http.Request) {
|
|
||||||
s.cfg.NotFound(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
type notAllowedParams struct {
|
|
||||||
allowedMethods string
|
|
||||||
allowedHeaders map[string]string
|
|
||||||
acceptPost string
|
|
||||||
acceptPatch string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s baseServer) notAllowed(w http.ResponseWriter, r *http.Request, params notAllowedParams) {
|
|
||||||
h := w.Header()
|
|
||||||
isOptions := r.Method == "OPTIONS"
|
|
||||||
if isOptions {
|
|
||||||
h.Set("Access-Control-Allow-Methods", params.allowedMethods)
|
|
||||||
if params.allowedHeaders != nil {
|
|
||||||
m := r.Header.Get("Access-Control-Request-Method")
|
|
||||||
if m != "" {
|
|
||||||
allowedHeaders, ok := params.allowedHeaders[strings.ToUpper(m)]
|
|
||||||
if ok {
|
|
||||||
h.Set("Access-Control-Allow-Headers", allowedHeaders)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if params.acceptPost != "" {
|
|
||||||
h.Set("Accept-Post", params.acceptPost)
|
|
||||||
}
|
|
||||||
if params.acceptPatch != "" {
|
|
||||||
h.Set("Accept-Patch", params.acceptPatch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if s.cfg.MethodNotAllowed != nil {
|
|
||||||
s.cfg.MethodNotAllowed(w, r, params.allowedMethods)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
status := http.StatusNoContent
|
|
||||||
if !isOptions {
|
|
||||||
h.Set("Allow", params.allowedMethods)
|
|
||||||
status = http.StatusMethodNotAllowed
|
|
||||||
}
|
|
||||||
w.WriteHeader(status)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg serverConfig) baseServer() (s baseServer, err error) {
|
|
||||||
s = baseServer{cfg: cfg}
|
|
||||||
if s.requests, err = otelogen.ServerRequestCountCounter(s.cfg.Meter); err != nil {
|
|
||||||
return s, err
|
|
||||||
}
|
|
||||||
if s.errors, err = otelogen.ServerErrorsCountCounter(s.cfg.Meter); err != nil {
|
|
||||||
return s, err
|
|
||||||
}
|
|
||||||
if s.duration, err = otelogen.ServerDurationHistogram(s.cfg.Meter); err != nil {
|
|
||||||
return s, err
|
|
||||||
}
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type clientConfig struct {
|
|
||||||
otelConfig
|
|
||||||
Client ht.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClientOption is client config option.
|
|
||||||
type ClientOption interface {
|
|
||||||
applyClient(*clientConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ ClientOption = (optionFunc[clientConfig])(nil)
|
|
||||||
|
|
||||||
func (o optionFunc[C]) applyClient(c *C) {
|
|
||||||
o(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ ClientOption = (otelOptionFunc)(nil)
|
|
||||||
|
|
||||||
func (o otelOptionFunc) applyClient(c *clientConfig) {
|
|
||||||
o(&c.otelConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newClientConfig(opts ...ClientOption) clientConfig {
|
|
||||||
cfg := clientConfig{
|
|
||||||
Client: http.DefaultClient,
|
|
||||||
}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt.applyClient(&cfg)
|
|
||||||
}
|
|
||||||
cfg.initOTEL()
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
type baseClient struct {
|
|
||||||
cfg clientConfig
|
|
||||||
requests metric.Int64Counter
|
|
||||||
errors metric.Int64Counter
|
|
||||||
duration metric.Float64Histogram
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg clientConfig) baseClient() (c baseClient, err error) {
|
|
||||||
c = baseClient{cfg: cfg}
|
|
||||||
if c.requests, err = otelogen.ClientRequestCountCounter(c.cfg.Meter); err != nil {
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
if c.errors, err = otelogen.ClientErrorsCountCounter(c.cfg.Meter); err != nil {
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
if c.duration, err = otelogen.ClientDurationHistogram(c.cfg.Meter); err != nil {
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Option is config option.
|
|
||||||
type Option interface {
|
|
||||||
ServerOption
|
|
||||||
ClientOption
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTracerProvider specifies a tracer provider to use for creating a tracer.
|
|
||||||
//
|
|
||||||
// If none is specified, the global provider is used.
|
|
||||||
func WithTracerProvider(provider trace.TracerProvider) Option {
|
|
||||||
return otelOptionFunc(func(cfg *otelConfig) {
|
|
||||||
if provider != nil {
|
|
||||||
cfg.TracerProvider = provider
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMeterProvider specifies a meter provider to use for creating a meter.
|
|
||||||
//
|
|
||||||
// If none is specified, the otel.GetMeterProvider() is used.
|
|
||||||
func WithMeterProvider(provider metric.MeterProvider) Option {
|
|
||||||
return otelOptionFunc(func(cfg *otelConfig) {
|
|
||||||
if provider != nil {
|
|
||||||
cfg.MeterProvider = provider
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithAttributes specifies default otel attributes.
|
|
||||||
func WithAttributes(attributes ...attribute.KeyValue) Option {
|
|
||||||
return otelOptionFunc(func(cfg *otelConfig) {
|
|
||||||
cfg.Attributes = attributes
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithClient specifies http client to use.
|
|
||||||
func WithClient(client ht.Client) ClientOption {
|
|
||||||
return optionFunc[clientConfig](func(cfg *clientConfig) {
|
|
||||||
if client != nil {
|
|
||||||
cfg.Client = client
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithNotFound specifies Not Found handler to use.
|
|
||||||
func WithNotFound(notFound http.HandlerFunc) ServerOption {
|
|
||||||
return optionFunc[serverConfig](func(cfg *serverConfig) {
|
|
||||||
if notFound != nil {
|
|
||||||
cfg.NotFound = notFound
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMethodNotAllowed specifies Method Not Allowed handler to use.
|
|
||||||
func WithMethodNotAllowed(methodNotAllowed func(w http.ResponseWriter, r *http.Request, allowed string)) ServerOption {
|
|
||||||
return optionFunc[serverConfig](func(cfg *serverConfig) {
|
|
||||||
if methodNotAllowed != nil {
|
|
||||||
cfg.MethodNotAllowed = methodNotAllowed
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithErrorHandler specifies error handler to use.
|
|
||||||
func WithErrorHandler(h ErrorHandler) ServerOption {
|
|
||||||
return optionFunc[serverConfig](func(cfg *serverConfig) {
|
|
||||||
if h != nil {
|
|
||||||
cfg.ErrorHandler = h
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithPathPrefix specifies server path prefix.
|
|
||||||
func WithPathPrefix(prefix string) ServerOption {
|
|
||||||
return optionFunc[serverConfig](func(cfg *serverConfig) {
|
|
||||||
cfg.Prefix = prefix
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMiddleware specifies middlewares to use.
|
|
||||||
func WithMiddleware(m ...Middleware) ServerOption {
|
|
||||||
return optionFunc[serverConfig](func(cfg *serverConfig) {
|
|
||||||
switch len(m) {
|
|
||||||
case 0:
|
|
||||||
cfg.Middleware = nil
|
|
||||||
case 1:
|
|
||||||
cfg.Middleware = m[0]
|
|
||||||
default:
|
|
||||||
cfg.Middleware = middleware.ChainMiddlewares(m...)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMaxMultipartMemory specifies limit of memory for storing file parts.
|
|
||||||
// File parts which can't be stored in memory will be stored on disk in temporary files.
|
|
||||||
func WithMaxMultipartMemory(max int64) ServerOption {
|
|
||||||
return optionFunc[serverConfig](func(cfg *serverConfig) {
|
|
||||||
if max > 0 {
|
|
||||||
cfg.MaxMultipartMemory = max
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,47 +0,0 @@
|
|||||||
// Code generated by ogen, DO NOT EDIT.
|
|
||||||
|
|
||||||
package gen
|
|
||||||
|
|
||||||
// setDefaults set default value of fields.
|
|
||||||
func (s *ConfirmationDataRedirect) setDefaults() {
|
|
||||||
{
|
|
||||||
val := bool(false)
|
|
||||||
s.Enforce.SetTo(Enforce(val))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setDefaults set default value of fields.
|
|
||||||
func (s *ConfirmationRedirect) setDefaults() {
|
|
||||||
{
|
|
||||||
val := bool(false)
|
|
||||||
s.Enforce.SetTo(Enforce(val))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setDefaults set default value of fields.
|
|
||||||
func (s *PaymentsPostReq) setDefaults() {
|
|
||||||
{
|
|
||||||
val := bool(false)
|
|
||||||
s.SavePaymentMethod.SetTo(SavePaymentMethodAttribute(val))
|
|
||||||
}
|
|
||||||
{
|
|
||||||
val := bool(false)
|
|
||||||
s.Capture.SetTo(Capture(val))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setDefaults set default value of fields.
|
|
||||||
func (s *SafeDeal) setDefaults() {
|
|
||||||
{
|
|
||||||
val := FeeMoment("payment_succeeded")
|
|
||||||
s.FeeMoment = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setDefaults set default value of fields.
|
|
||||||
func (s *SafeDealRequest) setDefaults() {
|
|
||||||
{
|
|
||||||
val := FeeMoment("payment_succeeded")
|
|
||||||
s.FeeMoment = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,110 +0,0 @@
|
|||||||
// Code generated by ogen, DO NOT EDIT.
|
|
||||||
package gen
|
|
||||||
|
|
||||||
type DealsDealIDGetRes interface {
|
|
||||||
dealsDealIDGetRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type DealsGetRes interface {
|
|
||||||
dealsGetRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type DealsPostRes interface {
|
|
||||||
dealsPostRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type MeGetRes interface {
|
|
||||||
meGetRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type PaymentMethodsPaymentMethodIDGetRes interface {
|
|
||||||
paymentMethodsPaymentMethodIDGetRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type PaymentMethodsPostRes interface {
|
|
||||||
paymentMethodsPostRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type PaymentsGetRes interface {
|
|
||||||
paymentsGetRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type PaymentsPaymentIDCancelPostRes interface {
|
|
||||||
paymentsPaymentIDCancelPostRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type PaymentsPaymentIDCapturePostRes interface {
|
|
||||||
paymentsPaymentIDCapturePostRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type PaymentsPaymentIDGetRes interface {
|
|
||||||
paymentsPaymentIDGetRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type PaymentsPostRes interface {
|
|
||||||
paymentsPostRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type PayoutsGetRes interface {
|
|
||||||
payoutsGetRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type PayoutsPayoutIDGetRes interface {
|
|
||||||
payoutsPayoutIDGetRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type PayoutsPostRes interface {
|
|
||||||
payoutsPostRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type PayoutsSearchGetRes interface {
|
|
||||||
payoutsSearchGetRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type PersonalDataPersonalDataIDGetRes interface {
|
|
||||||
personalDataPersonalDataIDGetRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type PersonalDataPostRes interface {
|
|
||||||
personalDataPostRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReceiptsGetRes interface {
|
|
||||||
receiptsGetRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReceiptsPostRes interface {
|
|
||||||
receiptsPostRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReceiptsReceiptIDGetRes interface {
|
|
||||||
receiptsReceiptIDGetRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type RefundsGetRes interface {
|
|
||||||
refundsGetRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type RefundsPostRes interface {
|
|
||||||
refundsPostRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type RefundsRefundIDGetRes interface {
|
|
||||||
refundsRefundIDGetRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type SbpBanksGetRes interface {
|
|
||||||
sbpBanksGetRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type WebhooksGetRes interface {
|
|
||||||
webhooksGetRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type WebhooksPostRes interface {
|
|
||||||
webhooksPostRes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type WebhooksWebhookIDDeleteRes interface {
|
|
||||||
webhooksWebhookIDDeleteRes()
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,42 +0,0 @@
|
|||||||
// Code generated by ogen, DO NOT EDIT.
|
|
||||||
|
|
||||||
package gen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"go.opentelemetry.io/otel/attribute"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Labeler is used to allow adding custom attributes to the server request metrics.
|
|
||||||
type Labeler struct {
|
|
||||||
attrs []attribute.KeyValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add attributes to the Labeler.
|
|
||||||
func (l *Labeler) Add(attrs ...attribute.KeyValue) {
|
|
||||||
l.attrs = append(l.attrs, attrs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AttributeSet returns the attributes added to the Labeler as an attribute.Set.
|
|
||||||
func (l *Labeler) AttributeSet() attribute.Set {
|
|
||||||
return attribute.NewSet(l.attrs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
type labelerContextKey struct{}
|
|
||||||
|
|
||||||
// LabelerFromContext retrieves the Labeler from the provided context, if present.
|
|
||||||
//
|
|
||||||
// If no Labeler was found in the provided context a new, empty Labeler is returned and the second
|
|
||||||
// return value is false. In this case it is safe to use the Labeler but any attributes added to
|
|
||||||
// it will not be used.
|
|
||||||
func LabelerFromContext(ctx context.Context) (*Labeler, bool) {
|
|
||||||
if l, ok := ctx.Value(labelerContextKey{}).(*Labeler); ok {
|
|
||||||
return l, true
|
|
||||||
}
|
|
||||||
return &Labeler{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func contextWithLabeler(ctx context.Context, l *Labeler) context.Context {
|
|
||||||
return context.WithValue(ctx, labelerContextKey{}, l)
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
// Code generated by ogen, DO NOT EDIT.
|
|
||||||
|
|
||||||
package gen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/ogen-go/ogen/middleware"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Middleware is middleware type.
|
|
||||||
type Middleware = middleware.Middleware
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
// Code generated by ogen, DO NOT EDIT.
|
|
||||||
|
|
||||||
package gen
|
|
||||||
|
|
||||||
// OperationName is the ogen operation name
|
|
||||||
type OperationName = string
|
|
||||||
|
|
||||||
const (
|
|
||||||
DealsDealIDGetOperation OperationName = "DealsDealIDGet"
|
|
||||||
DealsGetOperation OperationName = "DealsGet"
|
|
||||||
DealsPostOperation OperationName = "DealsPost"
|
|
||||||
MeGetOperation OperationName = "MeGet"
|
|
||||||
PaymentMethodsPaymentMethodIDGetOperation OperationName = "PaymentMethodsPaymentMethodIDGet"
|
|
||||||
PaymentMethodsPostOperation OperationName = "PaymentMethodsPost"
|
|
||||||
PaymentsGetOperation OperationName = "PaymentsGet"
|
|
||||||
PaymentsPaymentIDCancelPostOperation OperationName = "PaymentsPaymentIDCancelPost"
|
|
||||||
PaymentsPaymentIDCapturePostOperation OperationName = "PaymentsPaymentIDCapturePost"
|
|
||||||
PaymentsPaymentIDGetOperation OperationName = "PaymentsPaymentIDGet"
|
|
||||||
PaymentsPostOperation OperationName = "PaymentsPost"
|
|
||||||
PayoutsGetOperation OperationName = "PayoutsGet"
|
|
||||||
PayoutsPayoutIDGetOperation OperationName = "PayoutsPayoutIDGet"
|
|
||||||
PayoutsPostOperation OperationName = "PayoutsPost"
|
|
||||||
PayoutsSearchGetOperation OperationName = "PayoutsSearchGet"
|
|
||||||
PersonalDataPersonalDataIDGetOperation OperationName = "PersonalDataPersonalDataIDGet"
|
|
||||||
PersonalDataPostOperation OperationName = "PersonalDataPost"
|
|
||||||
ReceiptsGetOperation OperationName = "ReceiptsGet"
|
|
||||||
ReceiptsPostOperation OperationName = "ReceiptsPost"
|
|
||||||
ReceiptsReceiptIDGetOperation OperationName = "ReceiptsReceiptIDGet"
|
|
||||||
RefundsGetOperation OperationName = "RefundsGet"
|
|
||||||
RefundsPostOperation OperationName = "RefundsPost"
|
|
||||||
RefundsRefundIDGetOperation OperationName = "RefundsRefundIDGet"
|
|
||||||
SbpBanksGetOperation OperationName = "SbpBanksGet"
|
|
||||||
WebhooksGetOperation OperationName = "WebhooksGet"
|
|
||||||
WebhooksPostOperation OperationName = "WebhooksPost"
|
|
||||||
WebhooksWebhookIDDeleteOperation OperationName = "WebhooksWebhookIDDelete"
|
|
||||||
)
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,718 +0,0 @@
|
|||||||
// Code generated by ogen, DO NOT EDIT.
|
|
||||||
|
|
||||||
package gen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"mime"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/go-faster/errors"
|
|
||||||
"github.com/go-faster/jx"
|
|
||||||
"github.com/ogen-go/ogen/ogenerrors"
|
|
||||||
"github.com/ogen-go/ogen/validate"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Server) decodeDealsPostRequest(r *http.Request) (
|
|
||||||
req *SafeDealRequest,
|
|
||||||
rawBody []byte,
|
|
||||||
close func() error,
|
|
||||||
rerr error,
|
|
||||||
) {
|
|
||||||
var closers []func() error
|
|
||||||
close = func() error {
|
|
||||||
var merr error
|
|
||||||
// Close in reverse order, to match defer behavior.
|
|
||||||
for i := len(closers) - 1; i >= 0; i-- {
|
|
||||||
c := closers[i]
|
|
||||||
merr = errors.Join(merr, c())
|
|
||||||
}
|
|
||||||
return merr
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if rerr != nil {
|
|
||||||
rerr = errors.Join(rerr, close())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
|
||||||
if err != nil {
|
|
||||||
return req, rawBody, close, errors.Wrap(err, "parse media type")
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case ct == "application/json":
|
|
||||||
if r.ContentLength == 0 {
|
|
||||||
return req, rawBody, close, validate.ErrBodyRequired
|
|
||||||
}
|
|
||||||
buf, err := io.ReadAll(r.Body)
|
|
||||||
defer func() {
|
|
||||||
_ = r.Body.Close()
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
return req, rawBody, close, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the body to allow for downstream reading.
|
|
||||||
r.Body = io.NopCloser(bytes.NewBuffer(buf))
|
|
||||||
|
|
||||||
if len(buf) == 0 {
|
|
||||||
return req, rawBody, close, validate.ErrBodyRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
rawBody = append(rawBody, buf...)
|
|
||||||
d := jx.DecodeBytes(buf)
|
|
||||||
|
|
||||||
var request SafeDealRequest
|
|
||||||
if err := func() error {
|
|
||||||
if err := request.Decode(d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := d.Skip(); err != io.EOF {
|
|
||||||
return errors.New("unexpected trailing data")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}(); err != nil {
|
|
||||||
err = &ogenerrors.DecodeBodyError{
|
|
||||||
ContentType: ct,
|
|
||||||
Body: buf,
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
return req, rawBody, close, err
|
|
||||||
}
|
|
||||||
if err := func() error {
|
|
||||||
if err := request.Validate(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}(); err != nil {
|
|
||||||
return req, rawBody, close, errors.Wrap(err, "validate")
|
|
||||||
}
|
|
||||||
return &request, rawBody, close, nil
|
|
||||||
default:
|
|
||||||
return req, rawBody, close, validate.InvalidContentType(ct)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) decodePaymentMethodsPostRequest(r *http.Request) (
|
|
||||||
req PaymentMethodsPostReq,
|
|
||||||
rawBody []byte,
|
|
||||||
close func() error,
|
|
||||||
rerr error,
|
|
||||||
) {
|
|
||||||
var closers []func() error
|
|
||||||
close = func() error {
|
|
||||||
var merr error
|
|
||||||
// Close in reverse order, to match defer behavior.
|
|
||||||
for i := len(closers) - 1; i >= 0; i-- {
|
|
||||||
c := closers[i]
|
|
||||||
merr = errors.Join(merr, c())
|
|
||||||
}
|
|
||||||
return merr
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if rerr != nil {
|
|
||||||
rerr = errors.Join(rerr, close())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
|
||||||
if err != nil {
|
|
||||||
return req, rawBody, close, errors.Wrap(err, "parse media type")
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case ct == "application/json":
|
|
||||||
if r.ContentLength == 0 {
|
|
||||||
return req, rawBody, close, validate.ErrBodyRequired
|
|
||||||
}
|
|
||||||
buf, err := io.ReadAll(r.Body)
|
|
||||||
defer func() {
|
|
||||||
_ = r.Body.Close()
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
return req, rawBody, close, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the body to allow for downstream reading.
|
|
||||||
r.Body = io.NopCloser(bytes.NewBuffer(buf))
|
|
||||||
|
|
||||||
if len(buf) == 0 {
|
|
||||||
return req, rawBody, close, validate.ErrBodyRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
rawBody = append(rawBody, buf...)
|
|
||||||
d := jx.DecodeBytes(buf)
|
|
||||||
|
|
||||||
var request PaymentMethodsPostReq
|
|
||||||
if err := func() error {
|
|
||||||
if err := request.Decode(d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := d.Skip(); err != io.EOF {
|
|
||||||
return errors.New("unexpected trailing data")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}(); err != nil {
|
|
||||||
err = &ogenerrors.DecodeBodyError{
|
|
||||||
ContentType: ct,
|
|
||||||
Body: buf,
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
return req, rawBody, close, err
|
|
||||||
}
|
|
||||||
if err := func() error {
|
|
||||||
if err := request.Validate(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}(); err != nil {
|
|
||||||
return req, rawBody, close, errors.Wrap(err, "validate")
|
|
||||||
}
|
|
||||||
return request, rawBody, close, nil
|
|
||||||
default:
|
|
||||||
return req, rawBody, close, validate.InvalidContentType(ct)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) decodePaymentsPaymentIDCapturePostRequest(r *http.Request) (
|
|
||||||
req *PaymentsPaymentIDCapturePostReq,
|
|
||||||
rawBody []byte,
|
|
||||||
close func() error,
|
|
||||||
rerr error,
|
|
||||||
) {
|
|
||||||
var closers []func() error
|
|
||||||
close = func() error {
|
|
||||||
var merr error
|
|
||||||
// Close in reverse order, to match defer behavior.
|
|
||||||
for i := len(closers) - 1; i >= 0; i-- {
|
|
||||||
c := closers[i]
|
|
||||||
merr = errors.Join(merr, c())
|
|
||||||
}
|
|
||||||
return merr
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if rerr != nil {
|
|
||||||
rerr = errors.Join(rerr, close())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
|
||||||
if err != nil {
|
|
||||||
return req, rawBody, close, errors.Wrap(err, "parse media type")
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case ct == "application/json":
|
|
||||||
if r.ContentLength == 0 {
|
|
||||||
return req, rawBody, close, validate.ErrBodyRequired
|
|
||||||
}
|
|
||||||
buf, err := io.ReadAll(r.Body)
|
|
||||||
defer func() {
|
|
||||||
_ = r.Body.Close()
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
return req, rawBody, close, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the body to allow for downstream reading.
|
|
||||||
r.Body = io.NopCloser(bytes.NewBuffer(buf))
|
|
||||||
|
|
||||||
if len(buf) == 0 {
|
|
||||||
return req, rawBody, close, validate.ErrBodyRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
rawBody = append(rawBody, buf...)
|
|
||||||
d := jx.DecodeBytes(buf)
|
|
||||||
|
|
||||||
var request PaymentsPaymentIDCapturePostReq
|
|
||||||
if err := func() error {
|
|
||||||
if err := request.Decode(d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := d.Skip(); err != io.EOF {
|
|
||||||
return errors.New("unexpected trailing data")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}(); err != nil {
|
|
||||||
err = &ogenerrors.DecodeBodyError{
|
|
||||||
ContentType: ct,
|
|
||||||
Body: buf,
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
return req, rawBody, close, err
|
|
||||||
}
|
|
||||||
if err := func() error {
|
|
||||||
if err := request.Validate(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}(); err != nil {
|
|
||||||
return req, rawBody, close, errors.Wrap(err, "validate")
|
|
||||||
}
|
|
||||||
return &request, rawBody, close, nil
|
|
||||||
default:
|
|
||||||
return req, rawBody, close, validate.InvalidContentType(ct)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) decodePaymentsPostRequest(r *http.Request) (
|
|
||||||
req *PaymentsPostReq,
|
|
||||||
rawBody []byte,
|
|
||||||
close func() error,
|
|
||||||
rerr error,
|
|
||||||
) {
|
|
||||||
var closers []func() error
|
|
||||||
close = func() error {
|
|
||||||
var merr error
|
|
||||||
// Close in reverse order, to match defer behavior.
|
|
||||||
for i := len(closers) - 1; i >= 0; i-- {
|
|
||||||
c := closers[i]
|
|
||||||
merr = errors.Join(merr, c())
|
|
||||||
}
|
|
||||||
return merr
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if rerr != nil {
|
|
||||||
rerr = errors.Join(rerr, close())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
|
||||||
if err != nil {
|
|
||||||
return req, rawBody, close, errors.Wrap(err, "parse media type")
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case ct == "application/json":
|
|
||||||
if r.ContentLength == 0 {
|
|
||||||
return req, rawBody, close, validate.ErrBodyRequired
|
|
||||||
}
|
|
||||||
buf, err := io.ReadAll(r.Body)
|
|
||||||
defer func() {
|
|
||||||
_ = r.Body.Close()
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
return req, rawBody, close, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the body to allow for downstream reading.
|
|
||||||
r.Body = io.NopCloser(bytes.NewBuffer(buf))
|
|
||||||
|
|
||||||
if len(buf) == 0 {
|
|
||||||
return req, rawBody, close, validate.ErrBodyRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
rawBody = append(rawBody, buf...)
|
|
||||||
d := jx.DecodeBytes(buf)
|
|
||||||
|
|
||||||
var request PaymentsPostReq
|
|
||||||
if err := func() error {
|
|
||||||
if err := request.Decode(d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := d.Skip(); err != io.EOF {
|
|
||||||
return errors.New("unexpected trailing data")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}(); err != nil {
|
|
||||||
err = &ogenerrors.DecodeBodyError{
|
|
||||||
ContentType: ct,
|
|
||||||
Body: buf,
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
return req, rawBody, close, err
|
|
||||||
}
|
|
||||||
if err := func() error {
|
|
||||||
if err := request.Validate(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}(); err != nil {
|
|
||||||
return req, rawBody, close, errors.Wrap(err, "validate")
|
|
||||||
}
|
|
||||||
return &request, rawBody, close, nil
|
|
||||||
default:
|
|
||||||
return req, rawBody, close, validate.InvalidContentType(ct)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) decodePayoutsPostRequest(r *http.Request) (
|
|
||||||
req *PayoutRequest,
|
|
||||||
rawBody []byte,
|
|
||||||
close func() error,
|
|
||||||
rerr error,
|
|
||||||
) {
|
|
||||||
var closers []func() error
|
|
||||||
close = func() error {
|
|
||||||
var merr error
|
|
||||||
// Close in reverse order, to match defer behavior.
|
|
||||||
for i := len(closers) - 1; i >= 0; i-- {
|
|
||||||
c := closers[i]
|
|
||||||
merr = errors.Join(merr, c())
|
|
||||||
}
|
|
||||||
return merr
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if rerr != nil {
|
|
||||||
rerr = errors.Join(rerr, close())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
|
||||||
if err != nil {
|
|
||||||
return req, rawBody, close, errors.Wrap(err, "parse media type")
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case ct == "application/json":
|
|
||||||
if r.ContentLength == 0 {
|
|
||||||
return req, rawBody, close, validate.ErrBodyRequired
|
|
||||||
}
|
|
||||||
buf, err := io.ReadAll(r.Body)
|
|
||||||
defer func() {
|
|
||||||
_ = r.Body.Close()
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
return req, rawBody, close, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the body to allow for downstream reading.
|
|
||||||
r.Body = io.NopCloser(bytes.NewBuffer(buf))
|
|
||||||
|
|
||||||
if len(buf) == 0 {
|
|
||||||
return req, rawBody, close, validate.ErrBodyRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
rawBody = append(rawBody, buf...)
|
|
||||||
d := jx.DecodeBytes(buf)
|
|
||||||
|
|
||||||
var request PayoutRequest
|
|
||||||
if err := func() error {
|
|
||||||
if err := request.Decode(d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := d.Skip(); err != io.EOF {
|
|
||||||
return errors.New("unexpected trailing data")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}(); err != nil {
|
|
||||||
err = &ogenerrors.DecodeBodyError{
|
|
||||||
ContentType: ct,
|
|
||||||
Body: buf,
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
return req, rawBody, close, err
|
|
||||||
}
|
|
||||||
if err := func() error {
|
|
||||||
if err := request.Validate(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}(); err != nil {
|
|
||||||
return req, rawBody, close, errors.Wrap(err, "validate")
|
|
||||||
}
|
|
||||||
return &request, rawBody, close, nil
|
|
||||||
default:
|
|
||||||
return req, rawBody, close, validate.InvalidContentType(ct)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) decodePersonalDataPostRequest(r *http.Request) (
|
|
||||||
req PersonalDataPostReq,
|
|
||||||
rawBody []byte,
|
|
||||||
close func() error,
|
|
||||||
rerr error,
|
|
||||||
) {
|
|
||||||
var closers []func() error
|
|
||||||
close = func() error {
|
|
||||||
var merr error
|
|
||||||
// Close in reverse order, to match defer behavior.
|
|
||||||
for i := len(closers) - 1; i >= 0; i-- {
|
|
||||||
c := closers[i]
|
|
||||||
merr = errors.Join(merr, c())
|
|
||||||
}
|
|
||||||
return merr
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if rerr != nil {
|
|
||||||
rerr = errors.Join(rerr, close())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
|
||||||
if err != nil {
|
|
||||||
return req, rawBody, close, errors.Wrap(err, "parse media type")
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case ct == "application/json":
|
|
||||||
if r.ContentLength == 0 {
|
|
||||||
return req, rawBody, close, validate.ErrBodyRequired
|
|
||||||
}
|
|
||||||
buf, err := io.ReadAll(r.Body)
|
|
||||||
defer func() {
|
|
||||||
_ = r.Body.Close()
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
return req, rawBody, close, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the body to allow for downstream reading.
|
|
||||||
r.Body = io.NopCloser(bytes.NewBuffer(buf))
|
|
||||||
|
|
||||||
if len(buf) == 0 {
|
|
||||||
return req, rawBody, close, validate.ErrBodyRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
rawBody = append(rawBody, buf...)
|
|
||||||
d := jx.DecodeBytes(buf)
|
|
||||||
|
|
||||||
var request PersonalDataPostReq
|
|
||||||
if err := func() error {
|
|
||||||
if err := request.Decode(d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := d.Skip(); err != io.EOF {
|
|
||||||
return errors.New("unexpected trailing data")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}(); err != nil {
|
|
||||||
err = &ogenerrors.DecodeBodyError{
|
|
||||||
ContentType: ct,
|
|
||||||
Body: buf,
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
return req, rawBody, close, err
|
|
||||||
}
|
|
||||||
if err := func() error {
|
|
||||||
if err := request.Validate(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}(); err != nil {
|
|
||||||
return req, rawBody, close, errors.Wrap(err, "validate")
|
|
||||||
}
|
|
||||||
return request, rawBody, close, nil
|
|
||||||
default:
|
|
||||||
return req, rawBody, close, validate.InvalidContentType(ct)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) decodeReceiptsPostRequest(r *http.Request) (
|
|
||||||
req PostReceiptData,
|
|
||||||
rawBody []byte,
|
|
||||||
close func() error,
|
|
||||||
rerr error,
|
|
||||||
) {
|
|
||||||
var closers []func() error
|
|
||||||
close = func() error {
|
|
||||||
var merr error
|
|
||||||
// Close in reverse order, to match defer behavior.
|
|
||||||
for i := len(closers) - 1; i >= 0; i-- {
|
|
||||||
c := closers[i]
|
|
||||||
merr = errors.Join(merr, c())
|
|
||||||
}
|
|
||||||
return merr
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if rerr != nil {
|
|
||||||
rerr = errors.Join(rerr, close())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
|
||||||
if err != nil {
|
|
||||||
return req, rawBody, close, errors.Wrap(err, "parse media type")
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case ct == "application/json":
|
|
||||||
if r.ContentLength == 0 {
|
|
||||||
return req, rawBody, close, validate.ErrBodyRequired
|
|
||||||
}
|
|
||||||
buf, err := io.ReadAll(r.Body)
|
|
||||||
defer func() {
|
|
||||||
_ = r.Body.Close()
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
return req, rawBody, close, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the body to allow for downstream reading.
|
|
||||||
r.Body = io.NopCloser(bytes.NewBuffer(buf))
|
|
||||||
|
|
||||||
if len(buf) == 0 {
|
|
||||||
return req, rawBody, close, validate.ErrBodyRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
rawBody = append(rawBody, buf...)
|
|
||||||
d := jx.DecodeBytes(buf)
|
|
||||||
|
|
||||||
var request PostReceiptData
|
|
||||||
if err := func() error {
|
|
||||||
if err := request.Decode(d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := d.Skip(); err != io.EOF {
|
|
||||||
return errors.New("unexpected trailing data")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}(); err != nil {
|
|
||||||
err = &ogenerrors.DecodeBodyError{
|
|
||||||
ContentType: ct,
|
|
||||||
Body: buf,
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
return req, rawBody, close, err
|
|
||||||
}
|
|
||||||
return request, rawBody, close, nil
|
|
||||||
default:
|
|
||||||
return req, rawBody, close, validate.InvalidContentType(ct)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) decodeRefundsPostRequest(r *http.Request) (
|
|
||||||
req *RefundsPostReq,
|
|
||||||
rawBody []byte,
|
|
||||||
close func() error,
|
|
||||||
rerr error,
|
|
||||||
) {
|
|
||||||
var closers []func() error
|
|
||||||
close = func() error {
|
|
||||||
var merr error
|
|
||||||
// Close in reverse order, to match defer behavior.
|
|
||||||
for i := len(closers) - 1; i >= 0; i-- {
|
|
||||||
c := closers[i]
|
|
||||||
merr = errors.Join(merr, c())
|
|
||||||
}
|
|
||||||
return merr
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if rerr != nil {
|
|
||||||
rerr = errors.Join(rerr, close())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
|
||||||
if err != nil {
|
|
||||||
return req, rawBody, close, errors.Wrap(err, "parse media type")
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case ct == "application/json":
|
|
||||||
if r.ContentLength == 0 {
|
|
||||||
return req, rawBody, close, validate.ErrBodyRequired
|
|
||||||
}
|
|
||||||
buf, err := io.ReadAll(r.Body)
|
|
||||||
defer func() {
|
|
||||||
_ = r.Body.Close()
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
return req, rawBody, close, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the body to allow for downstream reading.
|
|
||||||
r.Body = io.NopCloser(bytes.NewBuffer(buf))
|
|
||||||
|
|
||||||
if len(buf) == 0 {
|
|
||||||
return req, rawBody, close, validate.ErrBodyRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
rawBody = append(rawBody, buf...)
|
|
||||||
d := jx.DecodeBytes(buf)
|
|
||||||
|
|
||||||
var request RefundsPostReq
|
|
||||||
if err := func() error {
|
|
||||||
if err := request.Decode(d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := d.Skip(); err != io.EOF {
|
|
||||||
return errors.New("unexpected trailing data")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}(); err != nil {
|
|
||||||
err = &ogenerrors.DecodeBodyError{
|
|
||||||
ContentType: ct,
|
|
||||||
Body: buf,
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
return req, rawBody, close, err
|
|
||||||
}
|
|
||||||
if err := func() error {
|
|
||||||
if err := request.Validate(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}(); err != nil {
|
|
||||||
return req, rawBody, close, errors.Wrap(err, "validate")
|
|
||||||
}
|
|
||||||
return &request, rawBody, close, nil
|
|
||||||
default:
|
|
||||||
return req, rawBody, close, validate.InvalidContentType(ct)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) decodeWebhooksPostRequest(r *http.Request) (
|
|
||||||
req *WebhooksPostReq,
|
|
||||||
rawBody []byte,
|
|
||||||
close func() error,
|
|
||||||
rerr error,
|
|
||||||
) {
|
|
||||||
var closers []func() error
|
|
||||||
close = func() error {
|
|
||||||
var merr error
|
|
||||||
// Close in reverse order, to match defer behavior.
|
|
||||||
for i := len(closers) - 1; i >= 0; i-- {
|
|
||||||
c := closers[i]
|
|
||||||
merr = errors.Join(merr, c())
|
|
||||||
}
|
|
||||||
return merr
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if rerr != nil {
|
|
||||||
rerr = errors.Join(rerr, close())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
|
||||||
if err != nil {
|
|
||||||
return req, rawBody, close, errors.Wrap(err, "parse media type")
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case ct == "application/json":
|
|
||||||
if r.ContentLength == 0 {
|
|
||||||
return req, rawBody, close, validate.ErrBodyRequired
|
|
||||||
}
|
|
||||||
buf, err := io.ReadAll(r.Body)
|
|
||||||
defer func() {
|
|
||||||
_ = r.Body.Close()
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
return req, rawBody, close, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the body to allow for downstream reading.
|
|
||||||
r.Body = io.NopCloser(bytes.NewBuffer(buf))
|
|
||||||
|
|
||||||
if len(buf) == 0 {
|
|
||||||
return req, rawBody, close, validate.ErrBodyRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
rawBody = append(rawBody, buf...)
|
|
||||||
d := jx.DecodeBytes(buf)
|
|
||||||
|
|
||||||
var request WebhooksPostReq
|
|
||||||
if err := func() error {
|
|
||||||
if err := request.Decode(d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := d.Skip(); err != io.EOF {
|
|
||||||
return errors.New("unexpected trailing data")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}(); err != nil {
|
|
||||||
err = &ogenerrors.DecodeBodyError{
|
|
||||||
ContentType: ct,
|
|
||||||
Body: buf,
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
return req, rawBody, close, err
|
|
||||||
}
|
|
||||||
if err := func() error {
|
|
||||||
if err := request.Validate(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}(); err != nil {
|
|
||||||
return req, rawBody, close, errors.Wrap(err, "validate")
|
|
||||||
}
|
|
||||||
return &request, rawBody, close, nil
|
|
||||||
default:
|
|
||||||
return req, rawBody, close, validate.InvalidContentType(ct)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
// Code generated by ogen, DO NOT EDIT.
|
|
||||||
|
|
||||||
package gen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/go-faster/jx"
|
|
||||||
ht "github.com/ogen-go/ogen/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func encodeDealsPostRequest(
|
|
||||||
req *SafeDealRequest,
|
|
||||||
r *http.Request,
|
|
||||||
) error {
|
|
||||||
const contentType = "application/json"
|
|
||||||
e := new(jx.Encoder)
|
|
||||||
{
|
|
||||||
req.Encode(e)
|
|
||||||
}
|
|
||||||
encoded := e.Bytes()
|
|
||||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodePaymentMethodsPostRequest(
|
|
||||||
req PaymentMethodsPostReq,
|
|
||||||
r *http.Request,
|
|
||||||
) error {
|
|
||||||
const contentType = "application/json"
|
|
||||||
e := new(jx.Encoder)
|
|
||||||
{
|
|
||||||
req.Encode(e)
|
|
||||||
}
|
|
||||||
encoded := e.Bytes()
|
|
||||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodePaymentsPaymentIDCapturePostRequest(
|
|
||||||
req *PaymentsPaymentIDCapturePostReq,
|
|
||||||
r *http.Request,
|
|
||||||
) error {
|
|
||||||
const contentType = "application/json"
|
|
||||||
e := new(jx.Encoder)
|
|
||||||
{
|
|
||||||
req.Encode(e)
|
|
||||||
}
|
|
||||||
encoded := e.Bytes()
|
|
||||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodePaymentsPostRequest(
|
|
||||||
req *PaymentsPostReq,
|
|
||||||
r *http.Request,
|
|
||||||
) error {
|
|
||||||
const contentType = "application/json"
|
|
||||||
e := new(jx.Encoder)
|
|
||||||
{
|
|
||||||
req.Encode(e)
|
|
||||||
}
|
|
||||||
encoded := e.Bytes()
|
|
||||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodePayoutsPostRequest(
|
|
||||||
req *PayoutRequest,
|
|
||||||
r *http.Request,
|
|
||||||
) error {
|
|
||||||
const contentType = "application/json"
|
|
||||||
e := new(jx.Encoder)
|
|
||||||
{
|
|
||||||
req.Encode(e)
|
|
||||||
}
|
|
||||||
encoded := e.Bytes()
|
|
||||||
slog.Info(string(encoded))
|
|
||||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodePersonalDataPostRequest(
|
|
||||||
req PersonalDataPostReq,
|
|
||||||
r *http.Request,
|
|
||||||
) error {
|
|
||||||
const contentType = "application/json"
|
|
||||||
e := new(jx.Encoder)
|
|
||||||
{
|
|
||||||
req.Encode(e)
|
|
||||||
}
|
|
||||||
encoded := e.Bytes()
|
|
||||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeReceiptsPostRequest(
|
|
||||||
req PostReceiptData,
|
|
||||||
r *http.Request,
|
|
||||||
) error {
|
|
||||||
const contentType = "application/json"
|
|
||||||
e := new(jx.Encoder)
|
|
||||||
{
|
|
||||||
req.Encode(e)
|
|
||||||
}
|
|
||||||
encoded := e.Bytes()
|
|
||||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeRefundsPostRequest(
|
|
||||||
req *RefundsPostReq,
|
|
||||||
r *http.Request,
|
|
||||||
) error {
|
|
||||||
const contentType = "application/json"
|
|
||||||
e := new(jx.Encoder)
|
|
||||||
{
|
|
||||||
req.Encode(e)
|
|
||||||
}
|
|
||||||
encoded := e.Bytes()
|
|
||||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeWebhooksPostRequest(
|
|
||||||
req *WebhooksPostReq,
|
|
||||||
r *http.Request,
|
|
||||||
) error {
|
|
||||||
const contentType = "application/json"
|
|
||||||
e := new(jx.Encoder)
|
|
||||||
{
|
|
||||||
req.Encode(e)
|
|
||||||
}
|
|
||||||
encoded := e.Bytes()
|
|
||||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,207 +0,0 @@
|
|||||||
// Code generated by ogen, DO NOT EDIT.
|
|
||||||
|
|
||||||
package gen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-faster/errors"
|
|
||||||
"github.com/ogen-go/ogen/ogenerrors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SecurityHandler is handler for security parameters.
|
|
||||||
type SecurityHandler interface {
|
|
||||||
// HandleBasicAuth handles BasicAuth security.
|
|
||||||
// HTTP Basic аутентификация клиента ЮKassa.
|
|
||||||
HandleBasicAuth(ctx context.Context, operationName OperationName, t BasicAuth) (context.Context, error)
|
|
||||||
// HandleOAuth2 handles OAuth2 security.
|
|
||||||
// Авторизация клиента ЮKassa с использованием OAuth-токена.
|
|
||||||
HandleOAuth2(ctx context.Context, operationName OperationName, t OAuth2) (context.Context, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func findAuthorization(h http.Header, prefix string) (string, bool) {
|
|
||||||
v, ok := h["Authorization"]
|
|
||||||
if !ok {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
for _, vv := range v {
|
|
||||||
scheme, value, ok := strings.Cut(vv, " ")
|
|
||||||
if !ok || !strings.EqualFold(scheme, prefix) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return value, true
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
// operationRolesBasicAuth is a private map storing roles per operation.
|
|
||||||
var operationRolesBasicAuth = map[string][]string{
|
|
||||||
DealsDealIDGetOperation: []string{},
|
|
||||||
DealsGetOperation: []string{},
|
|
||||||
DealsPostOperation: []string{},
|
|
||||||
MeGetOperation: []string{},
|
|
||||||
PaymentMethodsPaymentMethodIDGetOperation: []string{},
|
|
||||||
PaymentMethodsPostOperation: []string{},
|
|
||||||
PaymentsGetOperation: []string{},
|
|
||||||
PaymentsPaymentIDCancelPostOperation: []string{},
|
|
||||||
PaymentsPaymentIDCapturePostOperation: []string{},
|
|
||||||
PaymentsPaymentIDGetOperation: []string{},
|
|
||||||
PaymentsPostOperation: []string{},
|
|
||||||
PayoutsGetOperation: []string{},
|
|
||||||
PayoutsPayoutIDGetOperation: []string{},
|
|
||||||
PayoutsPostOperation: []string{},
|
|
||||||
PayoutsSearchGetOperation: []string{},
|
|
||||||
PersonalDataPersonalDataIDGetOperation: []string{},
|
|
||||||
PersonalDataPostOperation: []string{},
|
|
||||||
ReceiptsGetOperation: []string{},
|
|
||||||
ReceiptsPostOperation: []string{},
|
|
||||||
ReceiptsReceiptIDGetOperation: []string{},
|
|
||||||
RefundsGetOperation: []string{},
|
|
||||||
RefundsPostOperation: []string{},
|
|
||||||
RefundsRefundIDGetOperation: []string{},
|
|
||||||
SbpBanksGetOperation: []string{},
|
|
||||||
WebhooksGetOperation: []string{},
|
|
||||||
WebhooksPostOperation: []string{},
|
|
||||||
WebhooksWebhookIDDeleteOperation: []string{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRolesForBasicAuth returns the required roles for the given operation.
|
|
||||||
//
|
|
||||||
// This is useful for authorization scenarios where you need to know which roles
|
|
||||||
// are required for an operation.
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
//
|
|
||||||
// requiredRoles := GetRolesForBasicAuth(AddPetOperation)
|
|
||||||
//
|
|
||||||
// Returns nil if the operation has no role requirements or if the operation is unknown.
|
|
||||||
func GetRolesForBasicAuth(operation string) []string {
|
|
||||||
roles, ok := operationRolesBasicAuth[operation]
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Return a copy to prevent external modification
|
|
||||||
result := make([]string, len(roles))
|
|
||||||
copy(result, roles)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// oauth2ScopesOAuth2 is a private map storing OAuth2 scopes per operation.
|
|
||||||
var oauth2ScopesOAuth2 = map[string][]string{
|
|
||||||
DealsDealIDGetOperation: []string{},
|
|
||||||
DealsGetOperation: []string{},
|
|
||||||
DealsPostOperation: []string{},
|
|
||||||
MeGetOperation: []string{},
|
|
||||||
PaymentMethodsPaymentMethodIDGetOperation: []string{},
|
|
||||||
PaymentMethodsPostOperation: []string{},
|
|
||||||
PaymentsGetOperation: []string{},
|
|
||||||
PaymentsPaymentIDCancelPostOperation: []string{},
|
|
||||||
PaymentsPaymentIDCapturePostOperation: []string{},
|
|
||||||
PaymentsPaymentIDGetOperation: []string{},
|
|
||||||
PaymentsPostOperation: []string{},
|
|
||||||
PayoutsGetOperation: []string{},
|
|
||||||
PayoutsPayoutIDGetOperation: []string{},
|
|
||||||
PayoutsPostOperation: []string{},
|
|
||||||
PayoutsSearchGetOperation: []string{},
|
|
||||||
PersonalDataPersonalDataIDGetOperation: []string{},
|
|
||||||
PersonalDataPostOperation: []string{},
|
|
||||||
ReceiptsGetOperation: []string{},
|
|
||||||
ReceiptsPostOperation: []string{},
|
|
||||||
ReceiptsReceiptIDGetOperation: []string{},
|
|
||||||
RefundsGetOperation: []string{},
|
|
||||||
RefundsPostOperation: []string{},
|
|
||||||
RefundsRefundIDGetOperation: []string{},
|
|
||||||
SbpBanksGetOperation: []string{},
|
|
||||||
WebhooksGetOperation: []string{},
|
|
||||||
WebhooksPostOperation: []string{},
|
|
||||||
WebhooksWebhookIDDeleteOperation: []string{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOAuth2ScopesForOAuth2 returns the required OAuth2 scopes for the given operation.
|
|
||||||
//
|
|
||||||
// This is useful for token exchange scenarios where you need to know which scopes
|
|
||||||
// to request when obtaining a token for a downstream API call.
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
//
|
|
||||||
// requiredScopes := GetOAuth2ScopesForOAuth2(AddPetOperation)
|
|
||||||
// token := exchangeTokenWithScopes(requiredScopes, "https://api.example.com")
|
|
||||||
//
|
|
||||||
// Returns nil if the operation has no scope requirements or if the operation is unknown.
|
|
||||||
func GetOAuth2ScopesForOAuth2(operation string) []string {
|
|
||||||
scopes, ok := oauth2ScopesOAuth2[operation]
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Return a copy to prevent external modification
|
|
||||||
result := make([]string, len(scopes))
|
|
||||||
copy(result, scopes)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) securityBasicAuth(ctx context.Context, operationName OperationName, req *http.Request) (context.Context, bool, error) {
|
|
||||||
var t BasicAuth
|
|
||||||
if _, ok := findAuthorization(req.Header, "Basic"); !ok {
|
|
||||||
return ctx, false, nil
|
|
||||||
}
|
|
||||||
username, password, ok := req.BasicAuth()
|
|
||||||
if !ok {
|
|
||||||
return nil, false, errors.New("invalid basic auth")
|
|
||||||
}
|
|
||||||
t.Username = username
|
|
||||||
t.Password = password
|
|
||||||
t.Roles = operationRolesBasicAuth[operationName]
|
|
||||||
rctx, err := s.sec.HandleBasicAuth(ctx, operationName, t)
|
|
||||||
if errors.Is(err, ogenerrors.ErrSkipServerSecurity) {
|
|
||||||
return nil, false, nil
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
return rctx, true, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) securityOAuth2(ctx context.Context, operationName OperationName, req *http.Request) (context.Context, bool, error) {
|
|
||||||
var t OAuth2
|
|
||||||
token, ok := findAuthorization(req.Header, "Bearer")
|
|
||||||
if !ok {
|
|
||||||
return ctx, false, nil
|
|
||||||
}
|
|
||||||
t.Token = token
|
|
||||||
t.Scopes = oauth2ScopesOAuth2[operationName]
|
|
||||||
rctx, err := s.sec.HandleOAuth2(ctx, operationName, t)
|
|
||||||
if errors.Is(err, ogenerrors.ErrSkipServerSecurity) {
|
|
||||||
return nil, false, nil
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
return rctx, true, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SecuritySource is provider of security values (tokens, passwords, etc.).
|
|
||||||
type SecuritySource interface {
|
|
||||||
// BasicAuth provides BasicAuth security value.
|
|
||||||
// HTTP Basic аутентификация клиента ЮKassa.
|
|
||||||
BasicAuth(ctx context.Context, operationName OperationName) (BasicAuth, error)
|
|
||||||
// OAuth2 provides OAuth2 security value.
|
|
||||||
// Авторизация клиента ЮKassa с использованием OAuth-токена.
|
|
||||||
OAuth2(ctx context.Context, operationName OperationName) (OAuth2, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Client) securityBasicAuth(ctx context.Context, operationName OperationName, req *http.Request) error {
|
|
||||||
t, err := s.sec.BasicAuth(ctx, operationName)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "security source \"BasicAuth\"")
|
|
||||||
}
|
|
||||||
req.SetBasicAuth(t.Username, t.Password)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (s *Client) securityOAuth2(ctx context.Context, operationName OperationName, req *http.Request) error {
|
|
||||||
t, err := s.sec.OAuth2(ctx, operationName)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "security source \"OAuth2\"")
|
|
||||||
}
|
|
||||||
req.Header.Set("Authorization", "Bearer "+t.Token)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,316 +0,0 @@
|
|||||||
// Code generated by ogen, DO NOT EDIT.
|
|
||||||
|
|
||||||
package gen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handler handles operations described by OpenAPI v3 specification.
|
|
||||||
type Handler interface {
|
|
||||||
// DealsDealIDGet implements GET /deals/{deal_id} operation.
|
|
||||||
//
|
|
||||||
// Запрос позволяет получить информацию о текущем
|
|
||||||
// состоянии сделки по ее уникальному идентификатору.
|
|
||||||
//
|
|
||||||
// GET /deals/{deal_id}
|
|
||||||
DealsDealIDGet(ctx context.Context, params DealsDealIDGetParams) (DealsDealIDGetRes, error)
|
|
||||||
// DealsGet implements GET /deals operation.
|
|
||||||
//
|
|
||||||
// Запрос позволяет получить список сделок,
|
|
||||||
// отфильтрованный по заданным критериям. Подробнее о
|
|
||||||
// работе со списками: https://yookassa.ru/developers/using-api/lists.
|
|
||||||
//
|
|
||||||
// GET /deals
|
|
||||||
DealsGet(ctx context.Context, params DealsGetParams) (DealsGetRes, error)
|
|
||||||
// DealsPost implements POST /deals operation.
|
|
||||||
//
|
|
||||||
// Запрос позволяет создать сделку, в рамках которой
|
|
||||||
// необходимо принять оплату от покупателя и
|
|
||||||
// перечислить ее продавцу.
|
|
||||||
//
|
|
||||||
// POST /deals
|
|
||||||
DealsPost(ctx context.Context, req *SafeDealRequest, params DealsPostParams) (DealsPostRes, error)
|
|
||||||
// MeGet implements GET /me operation.
|
|
||||||
//
|
|
||||||
// С помощью этого запроса вы можете получить
|
|
||||||
// информацию о магазине или шлюзе: * Для Сплитования
|
|
||||||
// платежей: https://yookassa.ru/developers/solutions-for-platforms/split-payments/basics: в
|
|
||||||
// запросе необходимо передать параметр on_behalf_of с
|
|
||||||
// идентификатором магазина продавца и ваши данные для
|
|
||||||
// аутентификации: https://yookassa.ru/developers/using-api/interaction-format#auth
|
|
||||||
// (идентификатор и секретный ключ вашей платформы). *
|
|
||||||
// Для партнеров: https://yookassa.
|
|
||||||
// ru/developers/solutions-for-platforms/partners-api/basics: в запросе необходимо
|
|
||||||
// передать OAuth-токен магазина. * Для выплат: https://yookassa.
|
|
||||||
// ru/developers/payouts/overview: в запросе необходимо передать ваши
|
|
||||||
// данные для аутентификации: https://yookassa.
|
|
||||||
// ru/developers/using-api/interaction-format#auth (идентификатор и секретный
|
|
||||||
// ключ вашего шлюза).
|
|
||||||
//
|
|
||||||
// GET /me
|
|
||||||
MeGet(ctx context.Context, params MeGetParams) (MeGetRes, error)
|
|
||||||
// PaymentMethodsPaymentMethodIDGet implements GET /payment_methods/{payment_method_id} operation.
|
|
||||||
//
|
|
||||||
// Используйте этот запрос, чтобы получить информацию о
|
|
||||||
// текущем состоянии способа оплаты по его уникальному
|
|
||||||
// идентификатору.
|
|
||||||
//
|
|
||||||
// GET /payment_methods/{payment_method_id}
|
|
||||||
PaymentMethodsPaymentMethodIDGet(ctx context.Context, params PaymentMethodsPaymentMethodIDGetParams) (PaymentMethodsPaymentMethodIDGetRes, error)
|
|
||||||
// PaymentMethodsPost implements POST /payment_methods operation.
|
|
||||||
//
|
|
||||||
// Используйте этот запрос, чтобы создать в ЮKassa объект
|
|
||||||
// способа оплаты: https://yookassa.ru/developers/api#payment_method_object. В
|
|
||||||
// запросе необходимо передать код способа оплаты,
|
|
||||||
// который вы хотите сохранить, и при необходимости
|
|
||||||
// дополнительные параметры, связанные с той
|
|
||||||
// функциональностью, которую вы хотите использовать.
|
|
||||||
// Идентификатор созданного способа оплаты вы можете
|
|
||||||
// использовать при проведении автоплатежей: https://yookassa.
|
|
||||||
// ru/developers/payment-acceptance/scenario-extensions/recurring-payments/create-recurring или
|
|
||||||
// выплат: https://yookassa.ru/developers/payouts/scenario-extensions/multipurpose-token.
|
|
||||||
//
|
|
||||||
// POST /payment_methods
|
|
||||||
PaymentMethodsPost(ctx context.Context, req PaymentMethodsPostReq, params PaymentMethodsPostParams) (PaymentMethodsPostRes, error)
|
|
||||||
// PaymentsGet implements GET /payments operation.
|
|
||||||
//
|
|
||||||
// Use this request to get a list of payments. You can download payments created over the last 3
|
|
||||||
// years. You can filter the list by specified criteria. More about working with lists:
|
|
||||||
// https://yookassa.ru/developers/using-api/lists.
|
|
||||||
//
|
|
||||||
// GET /payments
|
|
||||||
PaymentsGet(ctx context.Context, params PaymentsGetParams) (PaymentsGetRes, error)
|
|
||||||
// PaymentsPaymentIDCancelPost implements POST /payments/{payment_id}/cancel operation.
|
|
||||||
//
|
|
||||||
// Cancel payments with the waiting_for_capture status. Payment cancelation means you are not ready
|
|
||||||
// to dispatch a product or to provide a service to the user. Once you cancel the payment, we will
|
|
||||||
// start returning the money to the payer’s account. If the payment was made from a bank card, a
|
|
||||||
// YooMoney wallet, or via SberPay, the money will be refunded instantly. If the payment was made
|
|
||||||
// using other payment methods, the process can take up to several days. More about capturing and
|
|
||||||
// canceling payments: https://yookassa.
|
|
||||||
// ru/developers/payment-acceptance/getting-started/payment-process#capture-and-cancel.
|
|
||||||
//
|
|
||||||
// POST /payments/{payment_id}/cancel
|
|
||||||
PaymentsPaymentIDCancelPost(ctx context.Context, params PaymentsPaymentIDCancelPostParams) (PaymentsPaymentIDCancelPostRes, error)
|
|
||||||
// PaymentsPaymentIDCapturePost implements POST /payments/{payment_id}/capture operation.
|
|
||||||
//
|
|
||||||
// Confirm you’re ready to accept the payment. Once the payment is captured, the status will change
|
|
||||||
// to succeeded. After that, you can provide the customer with the product or service. You can only
|
|
||||||
// capture payments with the waiting_for_capture status, and only for a certain amount of time
|
|
||||||
// (depending on the payment method). If you do not capture the payment within the allotted time, the
|
|
||||||
// status will change to canceled, and the money will be returned to the user. More about capturing
|
|
||||||
// and canceling payments: https://yookassa.
|
|
||||||
// ru/developers/payment-acceptance/getting-started/payment-process#capture-and-cancel.
|
|
||||||
//
|
|
||||||
// POST /payments/{payment_id}/capture
|
|
||||||
PaymentsPaymentIDCapturePost(ctx context.Context, req *PaymentsPaymentIDCapturePostReq, params PaymentsPaymentIDCapturePostParams) (PaymentsPaymentIDCapturePostRes, error)
|
|
||||||
// PaymentsPaymentIDGet implements GET /payments/{payment_id} operation.
|
|
||||||
//
|
|
||||||
// This request allows you to get the information about the current payment status by its unique ID.
|
|
||||||
//
|
|
||||||
// GET /payments/{payment_id}
|
|
||||||
PaymentsPaymentIDGet(ctx context.Context, params PaymentsPaymentIDGetParams) (PaymentsPaymentIDGetRes, error)
|
|
||||||
// PaymentsPost implements POST /payments operation.
|
|
||||||
//
|
|
||||||
// To accept a payment, you need to create a payment object: https://yookassa.
|
|
||||||
// ru/developers/api#payment_object, Payment. It contains all the necessary payment information
|
|
||||||
// (amount, currency, and status). Payments have a linear life cycle, going from one status to the
|
|
||||||
// next sequentially.
|
|
||||||
//
|
|
||||||
// POST /payments
|
|
||||||
PaymentsPost(ctx context.Context, req *PaymentsPostReq, params PaymentsPostParams) (PaymentsPostRes, error)
|
|
||||||
// PayoutsGet implements GET /payouts operation.
|
|
||||||
//
|
|
||||||
// Use this request to get a list of payouts. You can download payments created over the last 3 years.
|
|
||||||
// You can filter the list by specified criteria. Request authentication details: https://yookassa.
|
|
||||||
// ru/developers/using-api/interaction-format#auth depend on which payment solution you are using:
|
|
||||||
// basic payouts: https://yookassa.ru/developers/payouts/overview or payouts within the Safe Deal:
|
|
||||||
// https://yookassa.ru/developers/solutions-for-platforms/safe-deal/basics. More about working with
|
|
||||||
// lists: https://yookassa.ru/developers/using-api/lists.
|
|
||||||
//
|
|
||||||
// GET /payouts
|
|
||||||
PayoutsGet(ctx context.Context, params PayoutsGetParams) (PayoutsGetRes, error)
|
|
||||||
// PayoutsPayoutIDGet implements GET /payouts/{payout_id} operation.
|
|
||||||
//
|
|
||||||
// Используйте этот запрос, чтобы получить информацию о
|
|
||||||
// текущем состоянии выплаты по ее уникальному
|
|
||||||
// идентификатору. Данные для аутентификации: https://yookassa.
|
|
||||||
// ru/developers/using-api/interaction-format#auth запросов зависят от того,
|
|
||||||
// какое платежное решение вы используете — обычные
|
|
||||||
// выплаты: https://yookassa.ru/developers/payouts/overview или выплаты в
|
|
||||||
// рамках Безопасной сделки: https://yookassa.
|
|
||||||
// ru/developers/solutions-for-platforms/safe-deal/basics.
|
|
||||||
//
|
|
||||||
// GET /payouts/{payout_id}
|
|
||||||
PayoutsPayoutIDGet(ctx context.Context, params PayoutsPayoutIDGetParams) (PayoutsPayoutIDGetRes, error)
|
|
||||||
// PayoutsPost implements POST /payouts operation.
|
|
||||||
//
|
|
||||||
// Используйте этот запрос, чтобы создать в ЮKassa объект
|
|
||||||
// выплаты: https://yookassa.ru/developers/api#payout_object. В запросе
|
|
||||||
// необходимо передать сумму выплаты, данные о способе
|
|
||||||
// получения выплаты (например, номер кошелька ЮMoney),
|
|
||||||
// описание выплаты и при необходимости дополнительные
|
|
||||||
// параметры, связанные с той функциональностью,
|
|
||||||
// которую вы хотите использовать. Передаваемые
|
|
||||||
// параметры и данные для аутентификации: https://yookassa.
|
|
||||||
// ru/developers/using-api/interaction-format#auth запросов зависят от того,
|
|
||||||
// какое платежное решение вы используете — обычные
|
|
||||||
// выплаты: https://yookassa.ru/developers/payouts/overview или выплаты в
|
|
||||||
// рамках Безопасной сделки: https://yookassa.
|
|
||||||
// ru/developers/solutions-for-platforms/safe-deal/basics.
|
|
||||||
//
|
|
||||||
// POST /payouts
|
|
||||||
PayoutsPost(ctx context.Context, req *PayoutRequest, params PayoutsPostParams) (PayoutsPostRes, error)
|
|
||||||
// PayoutsSearchGet implements GET /payouts/search operation.
|
|
||||||
//
|
|
||||||
// Use this request to search for payouts by the specified criteria. Available only for payouts
|
|
||||||
// created over the last 3 months. At this time, only search by the metadata parameter is available.
|
|
||||||
// You can also specify the date and time when the payout was created (the created_at parameter).
|
|
||||||
// Request authentication details: https://yookassa.ru/developers/using-api/interaction-format#auth
|
|
||||||
// depend on which payment solution you are using: basic payouts: https://yookassa.
|
|
||||||
// ru/developers/payouts/overview or payouts within the Safe Deal: https://yookassa.
|
|
||||||
// ru/developers/solutions-for-platforms/safe-deal/basics.
|
|
||||||
//
|
|
||||||
// GET /payouts/search
|
|
||||||
PayoutsSearchGet(ctx context.Context, params PayoutsSearchGetParams) (PayoutsSearchGetRes, error)
|
|
||||||
// PersonalDataPersonalDataIDGet implements GET /personal_data/{personal_data_id} operation.
|
|
||||||
//
|
|
||||||
// С помощью этого запроса вы можете получить
|
|
||||||
// информацию о текущем статусе объекта персональных
|
|
||||||
// данных по его уникальному идентификатору.
|
|
||||||
//
|
|
||||||
// GET /personal_data/{personal_data_id}
|
|
||||||
PersonalDataPersonalDataIDGet(ctx context.Context, params PersonalDataPersonalDataIDGetParams) (PersonalDataPersonalDataIDGetRes, error)
|
|
||||||
// PersonalDataPost implements POST /personal_data operation.
|
|
||||||
//
|
|
||||||
// Используйте этот запрос, чтобы создать в ЮKassa объект
|
|
||||||
// персональных данных: https://yookassa.ru/developers/api#personal_data_object. В
|
|
||||||
// запросе необходимо указать тип данных (с какой целью
|
|
||||||
// они будут использоваться) и передать информацию о
|
|
||||||
// пользователе: фамилию, имя, отчество и другие — в
|
|
||||||
// зависимости от выбранного типа. Идентификатор
|
|
||||||
// созданного объекта персональных данных необходимо
|
|
||||||
// использовать в запросе на создание выплаты: https://yookassa.
|
|
||||||
// ru/developers/api#create_payout.
|
|
||||||
//
|
|
||||||
// POST /personal_data
|
|
||||||
PersonalDataPost(ctx context.Context, req PersonalDataPostReq, params PersonalDataPostParams) (PersonalDataPostRes, error)
|
|
||||||
// ReceiptsGet implements GET /receipts operation.
|
|
||||||
//
|
|
||||||
// Запрос позволяет получить список чеков,
|
|
||||||
// отфильтрованный по заданным критериям. Можно
|
|
||||||
// запросить чеки по конкретному платежу, чеки по
|
|
||||||
// конкретному возврату или все чеки магазина.
|
|
||||||
// Подробнее о работе со списками: https://yookassa.
|
|
||||||
// ru/developers/using-api/lists.
|
|
||||||
//
|
|
||||||
// GET /receipts
|
|
||||||
ReceiptsGet(ctx context.Context, params ReceiptsGetParams) (ReceiptsGetRes, error)
|
|
||||||
// ReceiptsPost implements POST /receipts operation.
|
|
||||||
//
|
|
||||||
// Используйте этот запрос при оплате с соблюдением
|
|
||||||
// требований 54-ФЗ: https://yookassa.
|
|
||||||
// ru/developers/payment-acceptance/receipts/54fz/basics, чтобы создать чек
|
|
||||||
// зачета предоплаты. Если вы работаете по сценарию
|
|
||||||
// Сначала платеж, потом чек: https://yookassa.
|
|
||||||
// ru/developers/payment-acceptance/receipts/54fz/other-services/basics#receipt-after-payment, в
|
|
||||||
// запросе также нужно передавать данные для
|
|
||||||
// формирования чека прихода и чека возврата прихода.
|
|
||||||
//
|
|
||||||
// POST /receipts
|
|
||||||
ReceiptsPost(ctx context.Context, req PostReceiptData, params ReceiptsPostParams) (ReceiptsPostRes, error)
|
|
||||||
// ReceiptsReceiptIDGet implements GET /receipts/{receipt_id} operation.
|
|
||||||
//
|
|
||||||
// Запрос позволяет получить информацию о текущем
|
|
||||||
// состоянии чека по его уникальному идентификатору.
|
|
||||||
//
|
|
||||||
// GET /receipts/{receipt_id}
|
|
||||||
ReceiptsReceiptIDGet(ctx context.Context, params ReceiptsReceiptIDGetParams) (ReceiptsReceiptIDGetRes, error)
|
|
||||||
// RefundsGet implements GET /refunds operation.
|
|
||||||
//
|
|
||||||
// Use this request to get a list of refunds. You can download refunds created over the last 3 years.
|
|
||||||
// You can filter the list by specified criteria. More about working with lists: https://yookassa.
|
|
||||||
// ru/developers/using-api/lists.
|
|
||||||
//
|
|
||||||
// GET /refunds
|
|
||||||
RefundsGet(ctx context.Context, params RefundsGetParams) (RefundsGetRes, error)
|
|
||||||
// RefundsPost implements POST /refunds operation.
|
|
||||||
//
|
|
||||||
// Создает возврат успешного платежа на указанную сумму.
|
|
||||||
// Платеж можно вернуть только в течение трех лет с
|
|
||||||
// момента его создания: https://yookassa.ru/developers/api#create_payment.
|
|
||||||
// Комиссия ЮKassa за проведение платежа не возвращается.
|
|
||||||
//
|
|
||||||
// POST /refunds
|
|
||||||
RefundsPost(ctx context.Context, req *RefundsPostReq, params RefundsPostParams) (RefundsPostRes, error)
|
|
||||||
// RefundsRefundIDGet implements GET /refunds/{refund_id} operation.
|
|
||||||
//
|
|
||||||
// Запрос позволяет получить информацию о текущем
|
|
||||||
// состоянии возврата по его уникальному
|
|
||||||
// идентификатору.
|
|
||||||
//
|
|
||||||
// GET /refunds/{refund_id}
|
|
||||||
RefundsRefundIDGet(ctx context.Context, params RefundsRefundIDGetParams) (RefundsRefundIDGetRes, error)
|
|
||||||
// SbpBanksGet implements GET /sbp_banks operation.
|
|
||||||
//
|
|
||||||
// С помощью этого запроса вы можете получить
|
|
||||||
// актуальный список всех участников СБП. Список нужно
|
|
||||||
// вывести получателю выплаты, идентификатор
|
|
||||||
// выбранного участника СБП необходимо использовать в
|
|
||||||
// запросе на создание выплаты: https://yookassa.
|
|
||||||
// ru/developers/api#create_payout. Подробнее о выплатах через СБП:
|
|
||||||
// https://yookassa.ru/developers/payouts/making-payouts/sbp.
|
|
||||||
//
|
|
||||||
// GET /sbp_banks
|
|
||||||
SbpBanksGet(ctx context.Context) (SbpBanksGetRes, error)
|
|
||||||
// WebhooksGet implements GET /webhooks operation.
|
|
||||||
//
|
|
||||||
// Запрос позволяет узнать, какие webhook есть для
|
|
||||||
// переданного OAuth-токена.
|
|
||||||
//
|
|
||||||
// GET /webhooks
|
|
||||||
WebhooksGet(ctx context.Context) (WebhooksGetRes, error)
|
|
||||||
// WebhooksPost implements POST /webhooks operation.
|
|
||||||
//
|
|
||||||
// Запрос позволяет подписаться на уведомления о
|
|
||||||
// событиях: https://yookassa.ru/developers/using-api/webhooks#events (например,
|
|
||||||
// переход платежа в статус succeeded). C помощью webhook можно
|
|
||||||
// подписаться только на события платежей и возвратов.
|
|
||||||
// Если вы хотите получать уведомления о нескольких
|
|
||||||
// событиях, вам нужно для каждого из них создать свой
|
|
||||||
// webhook. Для каждого OAuth-токена нужно создавать свой
|
|
||||||
// набор webhook.
|
|
||||||
//
|
|
||||||
// POST /webhooks
|
|
||||||
WebhooksPost(ctx context.Context, req *WebhooksPostReq, params WebhooksPostParams) (WebhooksPostRes, error)
|
|
||||||
// WebhooksWebhookIDDelete implements DELETE /webhooks/{webhook_id} operation.
|
|
||||||
//
|
|
||||||
// Запрос позволяет отписаться от уведомлений о событии
|
|
||||||
// для переданного OAuth-токена. Чтобы удалить webhook, вам
|
|
||||||
// нужно передать в запросе его идентификатор.
|
|
||||||
//
|
|
||||||
// DELETE /webhooks/{webhook_id}
|
|
||||||
WebhooksWebhookIDDelete(ctx context.Context, params WebhooksWebhookIDDeleteParams) (WebhooksWebhookIDDeleteRes, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server implements http server based on OpenAPI v3 specification and
|
|
||||||
// calls Handler to handle requests.
|
|
||||||
type Server struct {
|
|
||||||
h Handler
|
|
||||||
sec SecurityHandler
|
|
||||||
baseServer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewServer creates new Server.
|
|
||||||
func NewServer(h Handler, sec SecurityHandler, opts ...ServerOption) (*Server, error) {
|
|
||||||
s, err := newServerConfig(opts...).baseServer()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Server{
|
|
||||||
h: h,
|
|
||||||
sec: sec,
|
|
||||||
baseServer: s,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@@ -1,383 +0,0 @@
|
|||||||
// Code generated by ogen, DO NOT EDIT.
|
|
||||||
|
|
||||||
package gen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
ht "github.com/ogen-go/ogen/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UnimplementedHandler is no-op Handler which returns http.ErrNotImplemented.
|
|
||||||
type UnimplementedHandler struct{}
|
|
||||||
|
|
||||||
var _ Handler = UnimplementedHandler{}
|
|
||||||
|
|
||||||
// DealsDealIDGet implements GET /deals/{deal_id} operation.
|
|
||||||
//
|
|
||||||
// Запрос позволяет получить информацию о текущем
|
|
||||||
// состоянии сделки по ее уникальному идентификатору.
|
|
||||||
//
|
|
||||||
// GET /deals/{deal_id}
|
|
||||||
func (UnimplementedHandler) DealsDealIDGet(ctx context.Context, params DealsDealIDGetParams) (r DealsDealIDGetRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// DealsGet implements GET /deals operation.
|
|
||||||
//
|
|
||||||
// Запрос позволяет получить список сделок,
|
|
||||||
// отфильтрованный по заданным критериям. Подробнее о
|
|
||||||
// работе со списками: https://yookassa.ru/developers/using-api/lists.
|
|
||||||
//
|
|
||||||
// GET /deals
|
|
||||||
func (UnimplementedHandler) DealsGet(ctx context.Context, params DealsGetParams) (r DealsGetRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// DealsPost implements POST /deals operation.
|
|
||||||
//
|
|
||||||
// Запрос позволяет создать сделку, в рамках которой
|
|
||||||
// необходимо принять оплату от покупателя и
|
|
||||||
// перечислить ее продавцу.
|
|
||||||
//
|
|
||||||
// POST /deals
|
|
||||||
func (UnimplementedHandler) DealsPost(ctx context.Context, req *SafeDealRequest, params DealsPostParams) (r DealsPostRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// MeGet implements GET /me operation.
|
|
||||||
//
|
|
||||||
// С помощью этого запроса вы можете получить
|
|
||||||
// информацию о магазине или шлюзе: * Для Сплитования
|
|
||||||
// платежей: https://yookassa.ru/developers/solutions-for-platforms/split-payments/basics: в
|
|
||||||
// запросе необходимо передать параметр on_behalf_of с
|
|
||||||
// идентификатором магазина продавца и ваши данные для
|
|
||||||
// аутентификации: https://yookassa.ru/developers/using-api/interaction-format#auth
|
|
||||||
// (идентификатор и секретный ключ вашей платформы). *
|
|
||||||
// Для партнеров: https://yookassa.
|
|
||||||
// ru/developers/solutions-for-platforms/partners-api/basics: в запросе необходимо
|
|
||||||
// передать OAuth-токен магазина. * Для выплат: https://yookassa.
|
|
||||||
// ru/developers/payouts/overview: в запросе необходимо передать ваши
|
|
||||||
// данные для аутентификации: https://yookassa.
|
|
||||||
// ru/developers/using-api/interaction-format#auth (идентификатор и секретный
|
|
||||||
// ключ вашего шлюза).
|
|
||||||
//
|
|
||||||
// GET /me
|
|
||||||
func (UnimplementedHandler) MeGet(ctx context.Context, params MeGetParams) (r MeGetRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// PaymentMethodsPaymentMethodIDGet implements GET /payment_methods/{payment_method_id} operation.
|
|
||||||
//
|
|
||||||
// Используйте этот запрос, чтобы получить информацию о
|
|
||||||
// текущем состоянии способа оплаты по его уникальному
|
|
||||||
// идентификатору.
|
|
||||||
//
|
|
||||||
// GET /payment_methods/{payment_method_id}
|
|
||||||
func (UnimplementedHandler) PaymentMethodsPaymentMethodIDGet(ctx context.Context, params PaymentMethodsPaymentMethodIDGetParams) (r PaymentMethodsPaymentMethodIDGetRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// PaymentMethodsPost implements POST /payment_methods operation.
|
|
||||||
//
|
|
||||||
// Используйте этот запрос, чтобы создать в ЮKassa объект
|
|
||||||
// способа оплаты: https://yookassa.ru/developers/api#payment_method_object. В
|
|
||||||
// запросе необходимо передать код способа оплаты,
|
|
||||||
// который вы хотите сохранить, и при необходимости
|
|
||||||
// дополнительные параметры, связанные с той
|
|
||||||
// функциональностью, которую вы хотите использовать.
|
|
||||||
// Идентификатор созданного способа оплаты вы можете
|
|
||||||
// использовать при проведении автоплатежей: https://yookassa.
|
|
||||||
// ru/developers/payment-acceptance/scenario-extensions/recurring-payments/create-recurring или
|
|
||||||
// выплат: https://yookassa.ru/developers/payouts/scenario-extensions/multipurpose-token.
|
|
||||||
//
|
|
||||||
// POST /payment_methods
|
|
||||||
func (UnimplementedHandler) PaymentMethodsPost(ctx context.Context, req PaymentMethodsPostReq, params PaymentMethodsPostParams) (r PaymentMethodsPostRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// PaymentsGet implements GET /payments operation.
|
|
||||||
//
|
|
||||||
// Use this request to get a list of payments. You can download payments created over the last 3
|
|
||||||
// years. You can filter the list by specified criteria. More about working with lists:
|
|
||||||
// https://yookassa.ru/developers/using-api/lists.
|
|
||||||
//
|
|
||||||
// GET /payments
|
|
||||||
func (UnimplementedHandler) PaymentsGet(ctx context.Context, params PaymentsGetParams) (r PaymentsGetRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// PaymentsPaymentIDCancelPost implements POST /payments/{payment_id}/cancel operation.
|
|
||||||
//
|
|
||||||
// Cancel payments with the waiting_for_capture status. Payment cancelation means you are not ready
|
|
||||||
// to dispatch a product or to provide a service to the user. Once you cancel the payment, we will
|
|
||||||
// start returning the money to the payer’s account. If the payment was made from a bank card, a
|
|
||||||
// YooMoney wallet, or via SberPay, the money will be refunded instantly. If the payment was made
|
|
||||||
// using other payment methods, the process can take up to several days. More about capturing and
|
|
||||||
// canceling payments: https://yookassa.
|
|
||||||
// ru/developers/payment-acceptance/getting-started/payment-process#capture-and-cancel.
|
|
||||||
//
|
|
||||||
// POST /payments/{payment_id}/cancel
|
|
||||||
func (UnimplementedHandler) PaymentsPaymentIDCancelPost(ctx context.Context, params PaymentsPaymentIDCancelPostParams) (r PaymentsPaymentIDCancelPostRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// PaymentsPaymentIDCapturePost implements POST /payments/{payment_id}/capture operation.
|
|
||||||
//
|
|
||||||
// Confirm you’re ready to accept the payment. Once the payment is captured, the status will change
|
|
||||||
// to succeeded. After that, you can provide the customer with the product or service. You can only
|
|
||||||
// capture payments with the waiting_for_capture status, and only for a certain amount of time
|
|
||||||
// (depending on the payment method). If you do not capture the payment within the allotted time, the
|
|
||||||
// status will change to canceled, and the money will be returned to the user. More about capturing
|
|
||||||
// and canceling payments: https://yookassa.
|
|
||||||
// ru/developers/payment-acceptance/getting-started/payment-process#capture-and-cancel.
|
|
||||||
//
|
|
||||||
// POST /payments/{payment_id}/capture
|
|
||||||
func (UnimplementedHandler) PaymentsPaymentIDCapturePost(ctx context.Context, req *PaymentsPaymentIDCapturePostReq, params PaymentsPaymentIDCapturePostParams) (r PaymentsPaymentIDCapturePostRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// PaymentsPaymentIDGet implements GET /payments/{payment_id} operation.
|
|
||||||
//
|
|
||||||
// This request allows you to get the information about the current payment status by its unique ID.
|
|
||||||
//
|
|
||||||
// GET /payments/{payment_id}
|
|
||||||
func (UnimplementedHandler) PaymentsPaymentIDGet(ctx context.Context, params PaymentsPaymentIDGetParams) (r PaymentsPaymentIDGetRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// PaymentsPost implements POST /payments operation.
|
|
||||||
//
|
|
||||||
// To accept a payment, you need to create a payment object: https://yookassa.
|
|
||||||
// ru/developers/api#payment_object, Payment. It contains all the necessary payment information
|
|
||||||
// (amount, currency, and status). Payments have a linear life cycle, going from one status to the
|
|
||||||
// next sequentially.
|
|
||||||
//
|
|
||||||
// POST /payments
|
|
||||||
func (UnimplementedHandler) PaymentsPost(ctx context.Context, req *PaymentsPostReq, params PaymentsPostParams) (r PaymentsPostRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// PayoutsGet implements GET /payouts operation.
|
|
||||||
//
|
|
||||||
// Use this request to get a list of payouts. You can download payments created over the last 3 years.
|
|
||||||
//
|
|
||||||
// You can filter the list by specified criteria. Request authentication details: https://yookassa.
|
|
||||||
//
|
|
||||||
// ru/developers/using-api/interaction-format#auth depend on which payment solution you are using:
|
|
||||||
// basic payouts: https://yookassa.ru/developers/payouts/overview or payouts within the Safe Deal:
|
|
||||||
// https://yookassa.ru/developers/solutions-for-platforms/safe-deal/basics. More about working with
|
|
||||||
// lists: https://yookassa.ru/developers/using-api/lists.
|
|
||||||
//
|
|
||||||
// GET /payouts
|
|
||||||
func (UnimplementedHandler) PayoutsGet(ctx context.Context, params PayoutsGetParams) (r PayoutsGetRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// PayoutsPayoutIDGet implements GET /payouts/{payout_id} operation.
|
|
||||||
//
|
|
||||||
// Используйте этот запрос, чтобы получить информацию о
|
|
||||||
// текущем состоянии выплаты по ее уникальному
|
|
||||||
// идентификатору. Данные для аутентификации: https://yookassa.
|
|
||||||
// ru/developers/using-api/interaction-format#auth запросов зависят от того,
|
|
||||||
// какое платежное решение вы используете — обычные
|
|
||||||
// выплаты: https://yookassa.ru/developers/payouts/overview или выплаты в
|
|
||||||
// рамках Безопасной сделки: https://yookassa.
|
|
||||||
// ru/developers/solutions-for-platforms/safe-deal/basics.
|
|
||||||
//
|
|
||||||
// GET /payouts/{payout_id}
|
|
||||||
func (UnimplementedHandler) PayoutsPayoutIDGet(ctx context.Context, params PayoutsPayoutIDGetParams) (r PayoutsPayoutIDGetRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// PayoutsPost implements POST /payouts operation.
|
|
||||||
//
|
|
||||||
// Используйте этот запрос, чтобы создать в ЮKassa объект
|
|
||||||
// выплаты: https://yookassa.ru/developers/api#payout_object. В запросе
|
|
||||||
// необходимо передать сумму выплаты, данные о способе
|
|
||||||
// получения выплаты (например, номер кошелька ЮMoney),
|
|
||||||
// описание выплаты и при необходимости дополнительные
|
|
||||||
// параметры, связанные с той функциональностью,
|
|
||||||
// которую вы хотите использовать. Передаваемые
|
|
||||||
// параметры и данные для аутентификации: https://yookassa.
|
|
||||||
// ru/developers/using-api/interaction-format#auth запросов зависят от того,
|
|
||||||
// какое платежное решение вы используете — обычные
|
|
||||||
// выплаты: https://yookassa.ru/developers/payouts/overview или выплаты в
|
|
||||||
// рамках Безопасной сделки: https://yookassa.
|
|
||||||
// ru/developers/solutions-for-platforms/safe-deal/basics.
|
|
||||||
//
|
|
||||||
// POST /payouts
|
|
||||||
func (UnimplementedHandler) PayoutsPost(ctx context.Context, req *PayoutRequest, params PayoutsPostParams) (r PayoutsPostRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// PayoutsSearchGet implements GET /payouts/search operation.
|
|
||||||
//
|
|
||||||
// Use this request to search for payouts by the specified criteria. Available only for payouts
|
|
||||||
// created over the last 3 months. At this time, only search by the metadata parameter is available.
|
|
||||||
// You can also specify the date and time when the payout was created (the created_at parameter).
|
|
||||||
// Request authentication details: https://yookassa.ru/developers/using-api/interaction-format#auth
|
|
||||||
// depend on which payment solution you are using: basic payouts: https://yookassa.
|
|
||||||
// ru/developers/payouts/overview or payouts within the Safe Deal: https://yookassa.
|
|
||||||
// ru/developers/solutions-for-platforms/safe-deal/basics.
|
|
||||||
//
|
|
||||||
// GET /payouts/search
|
|
||||||
func (UnimplementedHandler) PayoutsSearchGet(ctx context.Context, params PayoutsSearchGetParams) (r PayoutsSearchGetRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// PersonalDataPersonalDataIDGet implements GET /personal_data/{personal_data_id} operation.
|
|
||||||
//
|
|
||||||
// С помощью этого запроса вы можете получить
|
|
||||||
// информацию о текущем статусе объекта персональных
|
|
||||||
// данных по его уникальному идентификатору.
|
|
||||||
//
|
|
||||||
// GET /personal_data/{personal_data_id}
|
|
||||||
func (UnimplementedHandler) PersonalDataPersonalDataIDGet(ctx context.Context, params PersonalDataPersonalDataIDGetParams) (r PersonalDataPersonalDataIDGetRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// PersonalDataPost implements POST /personal_data operation.
|
|
||||||
//
|
|
||||||
// Используйте этот запрос, чтобы создать в ЮKassa объект
|
|
||||||
// персональных данных: https://yookassa.ru/developers/api#personal_data_object. В
|
|
||||||
// запросе необходимо указать тип данных (с какой целью
|
|
||||||
// они будут использоваться) и передать информацию о
|
|
||||||
// пользователе: фамилию, имя, отчество и другие — в
|
|
||||||
// зависимости от выбранного типа. Идентификатор
|
|
||||||
// созданного объекта персональных данных необходимо
|
|
||||||
// использовать в запросе на создание выплаты: https://yookassa.
|
|
||||||
// ru/developers/api#create_payout.
|
|
||||||
//
|
|
||||||
// POST /personal_data
|
|
||||||
func (UnimplementedHandler) PersonalDataPost(ctx context.Context, req PersonalDataPostReq, params PersonalDataPostParams) (r PersonalDataPostRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReceiptsGet implements GET /receipts operation.
|
|
||||||
//
|
|
||||||
// Запрос позволяет получить список чеков,
|
|
||||||
// отфильтрованный по заданным критериям. Можно
|
|
||||||
// запросить чеки по конкретному платежу, чеки по
|
|
||||||
// конкретному возврату или все чеки магазина.
|
|
||||||
// Подробнее о работе со списками: https://yookassa.
|
|
||||||
// ru/developers/using-api/lists.
|
|
||||||
//
|
|
||||||
// GET /receipts
|
|
||||||
func (UnimplementedHandler) ReceiptsGet(ctx context.Context, params ReceiptsGetParams) (r ReceiptsGetRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReceiptsPost implements POST /receipts operation.
|
|
||||||
//
|
|
||||||
// Используйте этот запрос при оплате с соблюдением
|
|
||||||
// требований 54-ФЗ: https://yookassa.
|
|
||||||
// ru/developers/payment-acceptance/receipts/54fz/basics, чтобы создать чек
|
|
||||||
// зачета предоплаты. Если вы работаете по сценарию
|
|
||||||
// Сначала платеж, потом чек: https://yookassa.
|
|
||||||
// ru/developers/payment-acceptance/receipts/54fz/other-services/basics#receipt-after-payment, в
|
|
||||||
// запросе также нужно передавать данные для
|
|
||||||
// формирования чека прихода и чека возврата прихода.
|
|
||||||
//
|
|
||||||
// POST /receipts
|
|
||||||
func (UnimplementedHandler) ReceiptsPost(ctx context.Context, req PostReceiptData, params ReceiptsPostParams) (r ReceiptsPostRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReceiptsReceiptIDGet implements GET /receipts/{receipt_id} operation.
|
|
||||||
//
|
|
||||||
// Запрос позволяет получить информацию о текущем
|
|
||||||
// состоянии чека по его уникальному идентификатору.
|
|
||||||
//
|
|
||||||
// GET /receipts/{receipt_id}
|
|
||||||
func (UnimplementedHandler) ReceiptsReceiptIDGet(ctx context.Context, params ReceiptsReceiptIDGetParams) (r ReceiptsReceiptIDGetRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefundsGet implements GET /refunds operation.
|
|
||||||
//
|
|
||||||
// Use this request to get a list of refunds. You can download refunds created over the last 3 years.
|
|
||||||
// You can filter the list by specified criteria. More about working with lists: https://yookassa.
|
|
||||||
// ru/developers/using-api/lists.
|
|
||||||
//
|
|
||||||
// GET /refunds
|
|
||||||
func (UnimplementedHandler) RefundsGet(ctx context.Context, params RefundsGetParams) (r RefundsGetRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefundsPost implements POST /refunds operation.
|
|
||||||
//
|
|
||||||
// Создает возврат успешного платежа на указанную сумму.
|
|
||||||
//
|
|
||||||
// Платеж можно вернуть только в течение трех лет с
|
|
||||||
//
|
|
||||||
// момента его создания: https://yookassa.ru/developers/api#create_payment.
|
|
||||||
// Комиссия ЮKassa за проведение платежа не возвращается.
|
|
||||||
//
|
|
||||||
// POST /refunds
|
|
||||||
func (UnimplementedHandler) RefundsPost(ctx context.Context, req *RefundsPostReq, params RefundsPostParams) (r RefundsPostRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefundsRefundIDGet implements GET /refunds/{refund_id} operation.
|
|
||||||
//
|
|
||||||
// Запрос позволяет получить информацию о текущем
|
|
||||||
// состоянии возврата по его уникальному
|
|
||||||
// идентификатору.
|
|
||||||
//
|
|
||||||
// GET /refunds/{refund_id}
|
|
||||||
func (UnimplementedHandler) RefundsRefundIDGet(ctx context.Context, params RefundsRefundIDGetParams) (r RefundsRefundIDGetRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// SbpBanksGet implements GET /sbp_banks operation.
|
|
||||||
//
|
|
||||||
// С помощью этого запроса вы можете получить
|
|
||||||
// актуальный список всех участников СБП. Список нужно
|
|
||||||
// вывести получателю выплаты, идентификатор
|
|
||||||
// выбранного участника СБП необходимо использовать в
|
|
||||||
// запросе на создание выплаты: https://yookassa.
|
|
||||||
// ru/developers/api#create_payout. Подробнее о выплатах через СБП:
|
|
||||||
// https://yookassa.ru/developers/payouts/making-payouts/sbp.
|
|
||||||
//
|
|
||||||
// GET /sbp_banks
|
|
||||||
func (UnimplementedHandler) SbpBanksGet(ctx context.Context) (r SbpBanksGetRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebhooksGet implements GET /webhooks operation.
|
|
||||||
//
|
|
||||||
// Запрос позволяет узнать, какие webhook есть для
|
|
||||||
// переданного OAuth-токена.
|
|
||||||
//
|
|
||||||
// GET /webhooks
|
|
||||||
func (UnimplementedHandler) WebhooksGet(ctx context.Context) (r WebhooksGetRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebhooksPost implements POST /webhooks operation.
|
|
||||||
//
|
|
||||||
// Запрос позволяет подписаться на уведомления о
|
|
||||||
// событиях: https://yookassa.ru/developers/using-api/webhooks#events (например,
|
|
||||||
// переход платежа в статус succeeded). C помощью webhook можно
|
|
||||||
// подписаться только на события платежей и возвратов.
|
|
||||||
// Если вы хотите получать уведомления о нескольких
|
|
||||||
// событиях, вам нужно для каждого из них создать свой
|
|
||||||
// webhook. Для каждого OAuth-токена нужно создавать свой
|
|
||||||
// набор webhook.
|
|
||||||
//
|
|
||||||
// POST /webhooks
|
|
||||||
func (UnimplementedHandler) WebhooksPost(ctx context.Context, req *WebhooksPostReq, params WebhooksPostParams) (r WebhooksPostRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebhooksWebhookIDDelete implements DELETE /webhooks/{webhook_id} operation.
|
|
||||||
//
|
|
||||||
// Запрос позволяет отписаться от уведомлений о событии
|
|
||||||
// для переданного OAuth-токена. Чтобы удалить webhook, вам
|
|
||||||
// нужно передать в запросе его идентификатор.
|
|
||||||
//
|
|
||||||
// DELETE /webhooks/{webhook_id}
|
|
||||||
func (UnimplementedHandler) WebhooksWebhookIDDelete(ctx context.Context, params WebhooksWebhookIDDeleteParams) (r WebhooksWebhookIDDeleteRes, _ error) {
|
|
||||||
return r, ht.ErrNotImplemented
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
// Code generated by ogen, DO NOT EDIT.
|
|
||||||
|
|
||||||
package gen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/go-faster/errors"
|
|
||||||
"github.com/ogen-go/ogen/conv"
|
|
||||||
"github.com/ogen-go/ogen/uri"
|
|
||||||
"github.com/ogen-go/ogen/validate"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EncodeURI encodes MetadataInQueryParameter as URI form.
|
|
||||||
func (s MetadataInQueryParameter) EncodeURI(e uri.Encoder) error {
|
|
||||||
for k, elem := range s {
|
|
||||||
if err := e.EncodeField(k, func(e uri.Encoder) error {
|
|
||||||
|
|
||||||
return e.EncodeValue(conv.StringToString(elem))
|
|
||||||
}); err != nil {
|
|
||||||
return errors.Wrapf(err, "encode field %q", k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeURI decodes MetadataInQueryParameter from URI form.
|
|
||||||
func (s *MetadataInQueryParameter) DecodeURI(d uri.Decoder) error {
|
|
||||||
if s == nil {
|
|
||||||
return errors.New("invalid: unable to decode MetadataInQueryParameter to nil")
|
|
||||||
}
|
|
||||||
m := s.init()
|
|
||||||
var propertiesCount int
|
|
||||||
if err := d.DecodeFields(func(k string, d uri.Decoder) error {
|
|
||||||
propertiesCount++
|
|
||||||
var elem string
|
|
||||||
if err := func() error {
|
|
||||||
val, err := d.DecodeValue()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := conv.ToString(val)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
elem = c
|
|
||||||
return nil
|
|
||||||
}(); err != nil {
|
|
||||||
return errors.Wrapf(err, "decode field %q", k)
|
|
||||||
}
|
|
||||||
m[string(k)] = elem
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
return errors.Wrap(err, "decode MetadataInQueryParameter")
|
|
||||||
}
|
|
||||||
// Validate properties count.
|
|
||||||
if err := (validate.Object{
|
|
||||||
MinProperties: 0,
|
|
||||||
MinPropertiesSet: false,
|
|
||||||
MaxProperties: 1,
|
|
||||||
MaxPropertiesSet: true,
|
|
||||||
}).ValidateProperties(propertiesCount); err != nil {
|
|
||||||
return errors.Wrap(err, "object")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,8 @@ func WithContext(ctx context.Context) Optional {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
CreatePayout(models.PayoutReq, *orm.User, string, ...Optional) (models.PayoutResp, error)
|
GetSbpBanks(...Optional) ([]models.SBPBank, error)
|
||||||
|
CreatePayout(models.PayoutReq, *orm.User, string, ...Optional) (*models.PayoutResp, error)
|
||||||
GetConfig() yookassaConf.YooKassa
|
GetConfig() yookassaConf.YooKassa
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
generator:
|
|
||||||
ignore_not_implemented: ["all"]
|
|
||||||
# features:
|
|
||||||
# enable:
|
|
||||||
# # Enables paths client generation
|
|
||||||
# - "paths/client"
|
|
||||||
# # Enables validation of client requests
|
|
||||||
# - "client/request/validation"
|
|
||||||
# # Enables validation of server responses
|
|
||||||
# - "server/response/validation"
|
|
||||||
# disable_all: true
|
|
||||||
File diff suppressed because it is too large
Load Diff
72
internal/service/yookassa/yookassa_api.go
Normal file
72
internal/service/yookassa/yookassa_api.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package yookassa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"payouts/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SBPBankResponse struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Items []models.SBPBank `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Amount struct {
|
||||||
|
Value string `json:"value"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Card struct {
|
||||||
|
Number string `json:"number"`
|
||||||
|
First6 string `json:"first6"`
|
||||||
|
Last4 string `json:"last4"`
|
||||||
|
CardType string `json:"card_type"`
|
||||||
|
IssuerCountry string `json:"issuer_country"`
|
||||||
|
IssuerName string `json:"issuer_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PayoutDestination struct {
|
||||||
|
Type models.PayoutType `json:"type"`
|
||||||
|
AccountNumber string `json:"account_number"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
BankID string `json:"bank_id"`
|
||||||
|
RecipientChecked bool `json:"recipient_checked,omitempty"`
|
||||||
|
Card Card `json:"card,omitzero"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Metadata map[string]any
|
||||||
|
|
||||||
|
type PayoutRequest struct {
|
||||||
|
Amount Amount `json:"amount"`
|
||||||
|
PayoutToken string `json:"payout_token,omitempty"`
|
||||||
|
PayoutDestinationData PayoutDestination `json:"payout_destination_data,omitzero"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Metadata Metadata `json:"metadata"`
|
||||||
|
Test bool `json:"test"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PayoutResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Status models.PayoutStatus `json:"status"`
|
||||||
|
Amount Amount `json:"amount"`
|
||||||
|
PayoutDestination PayoutDestination `json:"payout_destination"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
SucceededAt time.Time `json:"succeeded_at"`
|
||||||
|
Metadata Metadata `json:"metadata"`
|
||||||
|
Test bool `json:"test"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Parameter string `json:"parameter"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
return fmt.Sprintf("yookassa error. status %d (%s). %s %s", e.Status, e.Code, e.Description, e.Parameter)
|
||||||
|
}
|
||||||
@@ -6,34 +6,52 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
resty "github.com/go-resty/resty/v2"
|
||||||
|
|
||||||
"payouts/internal/models"
|
"payouts/internal/models"
|
||||||
"payouts/internal/service/database/orm"
|
"payouts/internal/service/database/orm"
|
||||||
"payouts/internal/service/yookassa/config"
|
"payouts/internal/service/yookassa/config"
|
||||||
"payouts/internal/service/yookassa/gen"
|
|
||||||
|
|
||||||
"github.com/ogen-go/ogen/ogenerrors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const IdempotenceHeader = "Idempotence-Key"
|
||||||
|
|
||||||
type yookassaService struct {
|
type yookassaService struct {
|
||||||
conf config.YooKassa
|
conf config.YooKassa
|
||||||
payClient *gen.Client
|
ctx context.Context
|
||||||
ctx context.Context
|
|
||||||
|
client *resty.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewYookassaService(conf config.YooKassa) (Service, error) {
|
func NewYookassaService(conf config.YooKassa) (Service, error) {
|
||||||
|
client := resty.New()
|
||||||
|
client.SetBaseURL(conf.BaseUrl)
|
||||||
|
client.SetBasicAuth(conf.ApiPayoutKey, conf.ApiPayoutSecret)
|
||||||
|
client.SetTimeout(conf.Timeout)
|
||||||
|
|
||||||
|
if conf.Retry.Enabled {
|
||||||
|
client.
|
||||||
|
SetRetryCount(conf.Retry.Count).
|
||||||
|
SetRetryWaitTime(conf.Retry.WaitTime).
|
||||||
|
SetRetryMaxWaitTime(conf.Retry.MaxWaitTime).
|
||||||
|
AddRetryCondition(
|
||||||
|
func(r *resty.Response, err error) bool {
|
||||||
|
// Retry on network errors
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Retry on specific status codes
|
||||||
|
return r.StatusCode() == 429 || // Too Many Requests
|
||||||
|
r.StatusCode() >= 500 // Server errors
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
svc := &yookassaService{
|
svc := &yookassaService{
|
||||||
conf: conf,
|
conf: conf,
|
||||||
ctx: context.Background(),
|
client: client,
|
||||||
|
ctx: context.Background(),
|
||||||
}
|
}
|
||||||
payClient, err := gen.NewClient(conf.BaseUrl, svc, gen.WithClient(&http.Client{
|
|
||||||
Timeout: conf.Timeout,
|
|
||||||
}))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
svc.payClient = payClient
|
|
||||||
return svc, nil
|
return svc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,65 +65,92 @@ func (y *yookassaService) getParams(options ...Optional) *params {
|
|||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
// BasicAuth implements [gen.SecuritySource].
|
// GetSbpBanks implements [Service].
|
||||||
func (y *yookassaService) BasicAuth(ctx context.Context, operationName gen.OperationName) (gen.BasicAuth, error) {
|
func (y *yookassaService) GetSbpBanks(opts ...Optional) ([]models.SBPBank, error) {
|
||||||
return gen.BasicAuth{
|
params := y.getParams(opts...)
|
||||||
Username: y.conf.ApiPaymentKey,
|
|
||||||
Password: y.conf.ApiPaymentSecret,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OAuth2 implements [gen.SecuritySource].
|
yResp := &SBPBankResponse{}
|
||||||
func (y *yookassaService) OAuth2(ctx context.Context, operationName gen.OperationName) (gen.OAuth2, error) {
|
yError := &Error{}
|
||||||
return gen.OAuth2{}, ogenerrors.ErrSkipClientSecurity
|
|
||||||
|
restyResp, err := y.client.R().
|
||||||
|
SetContext(params.ctx).
|
||||||
|
SetResult(yResp).
|
||||||
|
SetError(yError).
|
||||||
|
Get("/sbp_banks")
|
||||||
|
|
||||||
|
slog.Debug(fmt.Sprintf("Got response from yookassa: %v", restyResp))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(errors.New("failed to perform yookassa api post"), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if restyResp.StatusCode() != http.StatusOK {
|
||||||
|
yError.Status = restyResp.StatusCode()
|
||||||
|
return nil, yError
|
||||||
|
}
|
||||||
|
|
||||||
|
return yResp.Items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatePayout implements [Service].
|
// CreatePayout implements [Service].
|
||||||
func (y *yookassaService) CreatePayout(req models.PayoutReq, userSession *orm.User, idempotenceKey string, opts ...Optional) (models.PayoutResp, error) {
|
func (y *yookassaService) CreatePayout(req models.PayoutReq, userSession *orm.User, idempotenceKey string, opts ...Optional) (*models.PayoutResp, error) {
|
||||||
params := y.getParams(opts...)
|
params := y.getParams(opts...)
|
||||||
|
|
||||||
var payoutDestination gen.PayoutRequestPayoutDestinationData
|
yReq := &PayoutRequest{
|
||||||
|
Amount: Amount{
|
||||||
|
Value: fmt.Sprintf("%.2f", req.Amount),
|
||||||
|
Currency: "RUB",
|
||||||
|
},
|
||||||
|
PayoutDestinationData: PayoutDestination{
|
||||||
|
Type: req.PayoutType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
yResp := &PayoutResponse{}
|
||||||
|
yError := &Error{}
|
||||||
|
|
||||||
switch req.PayoutType {
|
switch req.PayoutType {
|
||||||
case models.TypeSBP:
|
case models.TypeSBP:
|
||||||
payoutDestination = gen.NewSbpPayoutRequestPayoutDestinationData(gen.Sbp{
|
yReq.PayoutDestinationData = PayoutDestination{
|
||||||
Type: gen.PayoutDestinationDataTypeSbp,
|
Phone: userSession.Phone,
|
||||||
Phone: userSession.Phone,
|
BankID: req.BankID,
|
||||||
})
|
|
||||||
case models.TypeYooMoney:
|
|
||||||
payoutDestination = gen.NewYooMoneyPayoutRequestPayoutDestinationData(gen.YooMoney{
|
|
||||||
Type: gen.PayoutDestinationDataTypeYooMoney,
|
|
||||||
AccountNumber: req.AccountNumber,
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
return models.PayoutResp{Result: "failed", ErrorReason: "unsupported payout type"}, errors.New("unsupported payout type")
|
|
||||||
}
|
|
||||||
|
|
||||||
payoutOpt := gen.NewOptPayoutRequestPayoutDestinationData(payoutDestination)
|
|
||||||
|
|
||||||
postResp, err := y.payClient.PayoutsPost(params.ctx, &gen.PayoutRequest{
|
|
||||||
Amount: gen.PayoutRequestAmount{
|
|
||||||
Value: fmt.Sprintf("%.2f", req.Amount),
|
|
||||||
Currency: gen.CurrencyCodeRUB,
|
|
||||||
},
|
|
||||||
PayoutDestinationData: payoutOpt,
|
|
||||||
Test: gen.NewOptTest(gen.Test(y.conf.Test)),
|
|
||||||
}, gen.PayoutsPostParams{
|
|
||||||
IdempotenceKey: idempotenceKey,
|
|
||||||
})
|
|
||||||
|
|
||||||
slog.Debug(fmt.Sprintf("Received from yookassa: error %v; response %v", err, postResp))
|
|
||||||
if err == nil {
|
|
||||||
switch payoutResp := postResp.(type) {
|
|
||||||
case *gen.Payout:
|
|
||||||
slog.Info(fmt.Sprintf("Succeeded payout. It's id = %s", payoutResp.ID))
|
|
||||||
return models.PayoutResp{Result: string(payoutResp.Status), PayoutID: string(payoutResp.ID)}, nil
|
|
||||||
default:
|
|
||||||
return models.PayoutResp{Result: "failed", ErrorReason: fmt.Sprintf("%T", postResp)}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case models.TypeYooMoney:
|
||||||
|
yReq.PayoutDestinationData.AccountNumber = req.AccountNumber
|
||||||
|
|
||||||
|
case models.TypeCard:
|
||||||
|
yReq.PayoutDestinationData.Card = Card{
|
||||||
|
Number: req.CardNumber,
|
||||||
|
}
|
||||||
|
|
||||||
|
case models.TypeCardWidget:
|
||||||
|
yReq.PayoutToken = req.PayoutToken
|
||||||
|
yReq.PayoutDestinationData = PayoutDestination{}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unsupported payout type")
|
||||||
}
|
}
|
||||||
|
|
||||||
return models.PayoutResp{Result: "failed", ErrorReason: errors.Join(errors.New("failed to call yookassa api"), err).Error()}, err
|
restyResp, err := y.client.R().
|
||||||
|
SetContext(params.ctx).
|
||||||
|
SetHeader(IdempotenceHeader, idempotenceKey).
|
||||||
|
SetBody(yReq).
|
||||||
|
SetResult(yResp).
|
||||||
|
SetError(yError).
|
||||||
|
Post("/payouts")
|
||||||
|
|
||||||
|
slog.Debug(fmt.Sprintf("Got response from yookassa: %v", restyResp))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(errors.New("failed to perform yookassa api post"), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if restyResp.StatusCode() != http.StatusOK {
|
||||||
|
yError.Status = restyResp.StatusCode()
|
||||||
|
return nil, yError
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.PayoutResp{ID: yResp.ID, Status: yResp.Status}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConfig implements [Service].
|
// GetConfig implements [Service].
|
||||||
|
|||||||
45
internal/templates/payouts-widget.html
Normal file
45
internal/templates/payouts-widget.html
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Payouts Page</title>
|
||||||
|
<script src="https://yookassa.ru/payouts-data/{{ .WidgetVersion }}/widget.js"></script>
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="payout-form"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Инициализация виджета. Все параметры обязательные.
|
||||||
|
const payoutsData = new window.PayoutsData({
|
||||||
|
type: 'payout',
|
||||||
|
account_id: '{{ .ApiPayoutKey }}', // Идентификатор шлюза (agentId в личном кабинете)
|
||||||
|
success_callback: function(data) {
|
||||||
|
// https://yookassa.ru/developers/payouts/making-payouts/bank-card/using-payout-widget/implementing-widget#reference-output-parameters
|
||||||
|
if (window.AndroidCallback) {
|
||||||
|
window.AndroidCallback.onWidgetData(JSON.stringify(data));
|
||||||
|
} else if (window.webkit && window.webkit.messageHandlers.iosCallback) {
|
||||||
|
window.webkit.messageHandlers.iosCallback.onWidgetData(JSON.stringify(data));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error_callback: function(error) {
|
||||||
|
// https://yookassa.ru/developers/payouts/making-payouts/bank-card/using-payout-widget/implementing-widget#reference-output-parameters-error
|
||||||
|
if (window.AndroidCallback) {
|
||||||
|
window.AndroidCallback.onWidgetError(JSON.stringify(error));
|
||||||
|
} else if (window.webkit && window.webkit.messageHandlers.iosCallback) {
|
||||||
|
window.webkit.messageHandlers.iosCallback.onWidgetError(JSON.stringify(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//Отображение формы в контейнере
|
||||||
|
payoutsData.render('payout-form')
|
||||||
|
//Метод возвращает Promise, исполнение которого говорит о полной загрузке формы сбора данных (можно не использовать).
|
||||||
|
.then(() => {
|
||||||
|
//Код, который нужно выполнить после отображения формы.
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
11
internal/templates/templates.go
Normal file
11
internal/templates/templates.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package templates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"html/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed *.html
|
||||||
|
var FS embed.FS
|
||||||
|
|
||||||
|
var Templates = template.Must(template.ParseFS(FS, "*.html"))
|
||||||
415
openapi.yaml
Normal file
415
openapi.yaml
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: Payouts Service API
|
||||||
|
description: API for managing user registrations, authentication, and payouts via YooKassa.
|
||||||
|
version: 1.0.0
|
||||||
|
|
||||||
|
servers:
|
||||||
|
- url: /
|
||||||
|
|
||||||
|
tags:
|
||||||
|
- name: user
|
||||||
|
description: User registration and authentication
|
||||||
|
- name: payout
|
||||||
|
description: Payout operations
|
||||||
|
- name: system
|
||||||
|
description: Health and version endpoints
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/api/v1/user/register:
|
||||||
|
post:
|
||||||
|
tags: [user]
|
||||||
|
summary: Register a new user
|
||||||
|
operationId: userRegister
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/UserRegisterRequest'
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: User created successfully
|
||||||
|
'400':
|
||||||
|
description: Invalid input (empty fields or password mismatch)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
'500':
|
||||||
|
description: Internal server error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
|
||||||
|
/api/v1/user/login:
|
||||||
|
post:
|
||||||
|
tags: [user]
|
||||||
|
summary: Authenticate a user and obtain a session token
|
||||||
|
operationId: userLogin
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/UserLoginRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Login successful
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/UserLoginResponse'
|
||||||
|
'400':
|
||||||
|
description: Invalid input (empty phone or password)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
'401':
|
||||||
|
description: Invalid credentials
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
|
||||||
|
/api/v1/payout/sbp/banks:
|
||||||
|
get:
|
||||||
|
tags: [payout]
|
||||||
|
summary: Get list of SBP (Fast Payment System) banks
|
||||||
|
operationId: getSbpBanks
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: List of SBP banks
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/SBPBankListResponse'
|
||||||
|
'400':
|
||||||
|
description: Bad request (YooKassa API error)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
'500':
|
||||||
|
description: Internal server error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
|
||||||
|
/api/v1/payout/create:
|
||||||
|
post:
|
||||||
|
tags: [payout]
|
||||||
|
summary: Create a new payout
|
||||||
|
operationId: createPayout
|
||||||
|
security:
|
||||||
|
- bearerAuth: []
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PayoutRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Payout created successfully
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PayoutResponse'
|
||||||
|
'400':
|
||||||
|
description: Invalid payout data
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
'401':
|
||||||
|
description: Unauthorized (missing or invalid token)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
'500':
|
||||||
|
description: Internal server error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
|
||||||
|
/api/v1/payout/callback:
|
||||||
|
post:
|
||||||
|
tags: [payout]
|
||||||
|
summary: Receive payout status callback from YooKassa
|
||||||
|
description: |
|
||||||
|
Called by YooKassa to notify of payout status changes.
|
||||||
|
If IP validation is enabled, the request must originate from an allowed subnet.
|
||||||
|
Status updates are processed asynchronously.
|
||||||
|
operationId: payoutCallback
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PayoutResponse'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Callback received and queued for processing
|
||||||
|
'400':
|
||||||
|
description: Invalid JSON payload
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
'403':
|
||||||
|
description: Forbidden (IP address not in allowed subnets)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ErrorResponse'
|
||||||
|
|
||||||
|
/payout/widget:
|
||||||
|
get:
|
||||||
|
tags: [payout]
|
||||||
|
summary: Serve the payout widget HTML page
|
||||||
|
operationId: getPayoutWidget
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Widget HTML page
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
'500':
|
||||||
|
description: Template rendering error
|
||||||
|
|
||||||
|
/health:
|
||||||
|
get:
|
||||||
|
tags: [system]
|
||||||
|
summary: Health check
|
||||||
|
description: Verifies database connectivity.
|
||||||
|
operationId: healthCheck
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Service is healthy
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HealthResponse'
|
||||||
|
'503':
|
||||||
|
description: Service unavailable (database connection failed)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HealthResponse'
|
||||||
|
|
||||||
|
/version:
|
||||||
|
get:
|
||||||
|
tags: [system]
|
||||||
|
summary: Get service version
|
||||||
|
operationId: getVersion
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Version string (optionally including git commit hash)
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: v1.2.3
|
||||||
|
|
||||||
|
components:
|
||||||
|
securitySchemes:
|
||||||
|
bearerAuth:
|
||||||
|
type: http
|
||||||
|
scheme: bearer
|
||||||
|
description: UUID session token obtained from the login endpoint
|
||||||
|
|
||||||
|
schemas:
|
||||||
|
UserRegisterRequest:
|
||||||
|
type: object
|
||||||
|
required: [tin, phone, password, password_cfm]
|
||||||
|
properties:
|
||||||
|
tin:
|
||||||
|
type: string
|
||||||
|
description: Taxpayer Identification Number
|
||||||
|
example: "1234567890"
|
||||||
|
phone:
|
||||||
|
type: string
|
||||||
|
description: User phone number
|
||||||
|
example: "+79001234567"
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
format: password
|
||||||
|
description: User password
|
||||||
|
password_cfm:
|
||||||
|
type: string
|
||||||
|
format: password
|
||||||
|
description: Password confirmation (must match password)
|
||||||
|
|
||||||
|
UserLoginRequest:
|
||||||
|
type: object
|
||||||
|
required: [phone, password]
|
||||||
|
properties:
|
||||||
|
phone:
|
||||||
|
type: string
|
||||||
|
description: Registered phone number
|
||||||
|
example: "+79001234567"
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
format: password
|
||||||
|
|
||||||
|
UserLoginResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
description: Session token (UUID)
|
||||||
|
example: "550e8400-e29b-41d4-a716-446655440000"
|
||||||
|
token_ttl:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: Token expiration as Unix timestamp
|
||||||
|
example: 1711958400
|
||||||
|
|
||||||
|
SBPBankListResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
example: "list"
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/SBPBank'
|
||||||
|
|
||||||
|
SBPBank:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
bank_id:
|
||||||
|
type: string
|
||||||
|
description: Bank identifier
|
||||||
|
example: "100000000111"
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: Human-readable bank name
|
||||||
|
example: "Sberbank"
|
||||||
|
bic:
|
||||||
|
type: string
|
||||||
|
description: Bank Identification Code
|
||||||
|
example: "044525225"
|
||||||
|
|
||||||
|
PayoutType:
|
||||||
|
type: string
|
||||||
|
enum: [spb, yoo_money, bank_card, widget]
|
||||||
|
description: |
|
||||||
|
Payout method:
|
||||||
|
- `spb` — Fast Payment System (SBP)
|
||||||
|
- `yoo_money` — YooMoney wallet
|
||||||
|
- `bank_card` — bank card
|
||||||
|
- `widget` — card via widget
|
||||||
|
|
||||||
|
PayoutRequest:
|
||||||
|
type: object
|
||||||
|
required: [payout_type, amount]
|
||||||
|
properties:
|
||||||
|
payout_type:
|
||||||
|
$ref: '#/components/schemas/PayoutType'
|
||||||
|
payout_token:
|
||||||
|
type: string
|
||||||
|
description: Payment token (used for widget/card payouts)
|
||||||
|
example: "pt_xxxxxxxxxxxxxxxxxxxx"
|
||||||
|
account_number:
|
||||||
|
type: string
|
||||||
|
description: Account/phone number for SBP or YooMoney payouts
|
||||||
|
example: "+79001234567"
|
||||||
|
bank_id:
|
||||||
|
type: string
|
||||||
|
description: Bank identifier (required for SBP payouts)
|
||||||
|
example: "100000000111"
|
||||||
|
card_number:
|
||||||
|
type: string
|
||||||
|
description: Card number (for bank_card payout type)
|
||||||
|
example: "4111111111111111"
|
||||||
|
amount:
|
||||||
|
type: number
|
||||||
|
format: float
|
||||||
|
description: Payout amount in RUB
|
||||||
|
example: 1000.00
|
||||||
|
|
||||||
|
PayoutStatus:
|
||||||
|
type: string
|
||||||
|
enum: [created, pending, succeeded, canceled, failed]
|
||||||
|
|
||||||
|
Amount:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
value:
|
||||||
|
type: string
|
||||||
|
description: Amount as a decimal string
|
||||||
|
example: "1000.00"
|
||||||
|
currency:
|
||||||
|
type: string
|
||||||
|
description: ISO 4217 currency code
|
||||||
|
example: "RUB"
|
||||||
|
|
||||||
|
PayoutDestination:
|
||||||
|
type: object
|
||||||
|
description: Payout destination details (structure depends on payout type)
|
||||||
|
additionalProperties: true
|
||||||
|
|
||||||
|
PayoutResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
description: YooKassa payout ID
|
||||||
|
example: "po_1da5c87d-0984-50e8-a7f3-8de646dd9ec9"
|
||||||
|
status:
|
||||||
|
$ref: '#/components/schemas/PayoutStatus'
|
||||||
|
amount:
|
||||||
|
$ref: '#/components/schemas/Amount'
|
||||||
|
payout_destination:
|
||||||
|
$ref: '#/components/schemas/PayoutDestination'
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
example: "Payout for order #42"
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
example: "2024-04-02T12:00:00Z"
|
||||||
|
succeeded_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
example: "2024-04-02T12:01:00Z"
|
||||||
|
metadata:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
description: Arbitrary key-value metadata
|
||||||
|
test:
|
||||||
|
type: boolean
|
||||||
|
description: Whether this is a test payout
|
||||||
|
example: false
|
||||||
|
|
||||||
|
HealthResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
OK:
|
||||||
|
type: boolean
|
||||||
|
description: Whether the service is healthy
|
||||||
|
Error:
|
||||||
|
type: string
|
||||||
|
description: Error message (only present when OK is false)
|
||||||
|
|
||||||
|
ErrorResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: integer
|
||||||
|
description: HTTP status code
|
||||||
|
example: 400
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
description: Human-readable error message
|
||||||
|
example: "Bad Request"
|
||||||
Reference in New Issue
Block a user