# 创建虚拟货币交易

> 在单个原子事务中为用户画像充值或扣除一种或多种虚拟货币。使用正数 `amount` 表示充值（授予），负数 `amount` 表示扣除（消费）。所有条目一起应用——如果任何条目失败，则全部不应用。
>
> 每个货币代码在每次请求中只能出现一次。通过此端点进行的充值始终创建永不过期的余额。

## OpenAPI

```yaml
/api-specs/adapty-api.yaml post /api/v2/server-side-api/vc/transactions/
openapi: 3.1.0
info:
  title: Adapty 服务端 API
  version: 1.0.0
servers:
  - url: https://api.adapty.io
    description: 生产服务器
paths:
  /api/v2/server-side-api/vc/transactions/:
    post:
      summary: 创建虚拟货币交易
      description: |
        在单个原子事务中为用户画像充值或扣除一种或多种虚拟货币。使用正数 `amount` 表示充值（授予），负数 `amount` 表示扣除（消费）。所有条目一起应用——如果任何条目失败，则全部不应用。

        每个货币代码在每次请求中只能出现一次。通过此端点进行的充值始终创建永不过期的余额。
      operationId: createVirtualCurrencyTransaction
      tags:
        - Virtual Currency
      security:
        - apikeyAuth: []
      parameters:
        - name: adapty-customer-user-id
          in: header
          required: false
          schema:
            type: string
          description: 您系统中客户的唯一 ID。`adapty-customer-user-id` 或 `adapty-profile-id` 二者必填其一。
        - name: adapty-profile-id
          in: header
          required: false
          schema:
            type: string
          description: 您系统中用户画像的唯一 ID。如果您正在使用匿名用户画像，这是最佳选择。`adapty-customer-user-id` 或 `adapty-profile-id` 二者必填其一。
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
            format: uuid
          description: |
            使请求具有幂等性的 UUID v4。如果您使用相同的密钥重试请求，Adapty 将返回原始事务的结果，而不会再次更改余额。必须是有效的 UUID v4。

            密钥的记忆时间最长为一小时，且范围限定为单个用户画像。超过该时间窗口后，使用相同密钥的请求将作为新事务应用——因此请使用该密钥安全地重试调用，而不是用于长期内的重复授予去重。
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VirtualCurrencyTransactionRequest"
            examples:
              debit:
                summary: 消费（扣除）一种货币
                value:
                  items:
                    - currency_code: COINS
                      amount: -10
              credit:
                summary: 授予（充值）一种货币
                value:
                  items:
                    - currency_code: COINS
                      amount: 500
              multi_currency:
                summary: 同时扣除两种货币
                value:
                  items:
                    - currency_code: GOLD
                      amount: -50
                    - currency_code: SILVER
                      amount: -200
              conversion:
                summary: 将一种货币兑换为另一种货币
                value:
                  items:
                    - currency_code: SILVER
                      amount: -100
                    - currency_code: GOLD
                      amount: 10
                  metadata:
                    reason: exchange
      responses:
        "200":
          description: 事务应用成功
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VirtualCurrencyTransactionResponse"
              example:
                transaction_id: 0190e8a4-1c2b-7def-8abc-2c1a4b6d8e90
                balances:
                  - code: COINS
                    name: Gold Coins
                    balance: 12950
                    held: 0
                    available: 12950
        "400":
          description: |
            错误请求。`error_code` 字段标识原因：`insufficient_balance`、`unknown_currency`、`duplicate_currency`、`amount_zero`、`balance_overflow`、`empty_items` 或 `idempotency_key_invalid`。
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                errors:
                  - source: currency_code
                    errors:
                      - Insufficient balance for currency COINS
                error_code: insufficient_balance
                status_code: 400
        "401":
          description: 未授权。API 密钥缺失或无效。
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: 禁止访问。此应用未启用虚拟货币的 Server API。请联系 Adapty 支持以申请访问权限。
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                errors:
                  - source: null
                    errors:
                      - Server API for virtual currencies is not enabled for this app. Contact Adapty support to request access.
                error_code: feature_not_enabled
                status_code: 403
        "404":
          description: 未找到用户画像。
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: |
            冲突。`error_code` 字段标识原因：

            - `idempotency_in_flight` — 具有相同 `Idempotency-Key` 的请求仍在处理中。在 `Retry-After` 响应头指定的时间间隔后重试。
            - `duplicate_source_transaction` — 底层存储事务已被充值，因此不会再次应用。
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                errors:
                  - source: null
                    errors:
                      - A similar request is being processed.
                error_code: idempotency_in_flight
                status_code: 409
        "422":
          description: |
            不可处理的实体。请求体未通过 schema 验证——例如，`items` 包含超过 20 个条目，或 `metadata` 的键或值违反了其约束条件。
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: |
            请求过多。超出了每个应用或全局速率限制（`error_code` 为 `rate_limited`）。默认限制为每个应用每分钟 600 次请求，全局每分钟 6000 次请求。
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: 内部服务器错误
components:
  schemas:
    VirtualCurrencyTransactionRequest:
      type: object
      description: 创建虚拟货币交易的请求体。
      properties:
        items:
          type: array
          minItems: 1
          maxItems: 20
          description: 以原子方式应用的余额调整。每个货币代码只能出现一次。
          items:
            $ref: "#/components/schemas/VirtualCurrencyBalanceAdjustment"
        metadata:
          type: object
          nullable: true
          additionalProperties:
            type: string
          description: |
            与交易一同存储的可选键值对。最多 5 个键。每个键匹配 `^[a-z0-9_]{1,30}$`，每个值最多 200 个字符。
      required:
        - items
    VirtualCurrencyTransactionResponse:
      type: object
      description: 虚拟货币交易的结果。
      properties:
        transaction_id:
          type: string
          format: uuid
          description: 已创建交易的唯一 ID。
        balances:
          type: array
          description: |
            本次请求中每种受影响货币的交易后余额（每个 `items` 条目对应一条记录）。如需读取用户画像的完整余额列表，请使用 `GET /api/v2/server-side-api/vc/balances/`。
          items:
            $ref: "#/components/schemas/VirtualCurrencyBalanceSnapshot"
      required:
        - transaction_id
        - balances
    ErrorResponse:
      type: object
      properties:
        errors:
          type: array
          items:
            type: object
            properties:
              source:
                type: string
                nullable: true
                description: 错误来源
              errors:
                type: array
                items:
                  type: string
                description: 错误消息数组
        error_code:
          type: string
          description: 简短错误名称
        status_code:
          type: integer
          description: HTTP 状态码
      required:
        - errors
        - error_code
        - status_code
    VirtualCurrencyBalanceAdjustment:
      type: object
      properties:
        currency_code:
          type: string
          description: 要调整的虚拟货币代码。
        amount:
          type: integer
          format: int32
          description: |
            要应用的金额。正值表示充入（授予）货币；负值表示扣除（消费）货币。不能为零。
      required:
        - currency_code
        - amount
    VirtualCurrencyBalanceSnapshot:
      type: object
      properties:
        code:
          type: string
          description: 虚拟货币代码。
        name:
          type: string
          description: 虚拟货币的显示名称。
        balance:
          type: integer
          format: int32
          minimum: 0
          description: 总余额，包含当前持有的金额。
        held:
          type: integer
          format: int32
          minimum: 0
          description: 所有有效持有金额的总和（已预留金额）。当前始终为 0。
        available:
          type: integer
          format: int32
          description: 可用于消费的余额，计算方式为 `balance - held`。
      required:
        - code
        - name
        - balance
        - held
        - available
  securitySchemes:
    apikeyAuth:
      type: apiKey
      name: Authorization
      in: header
      default: Api-Key {Your secret API key}
      description: |
        API 请求必须通过您的密钥进行身份验证，将其作为 **Authorization** 请求头，
        值为 `Api-Key {your_secret_api_key}`，例如 `Api-Key secret_live_...`。
        请在 Adapty 看板 -> **App Settings** -> **General** 标签页 -> **API keys** 部分找到此密钥。
```
