diff --git a/.changeset/bumpy-games-chew.md b/.changeset/bumpy-games-chew.md deleted file mode 100644 index 15534e93..00000000 --- a/.changeset/bumpy-games-chew.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@oaknetwork/contracts-sdk": minor ---- - -Add missing events, reads, errors, type-safe constants, simulation results, transaction preparation, and getReceipt; update event log fetching documentation and improve code consistency diff --git a/.changeset/clever-pens-attend.md b/.changeset/clever-pens-attend.md deleted file mode 100644 index c8b4a292..00000000 --- a/.changeset/clever-pens-attend.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@oaknetwork/payments-sdk": minor ---- - -updated sandbox url and provider registration types diff --git a/.changeset/huge-boats-attack.md b/.changeset/huge-boats-attack.md deleted file mode 100644 index 47a682f4..00000000 --- a/.changeset/huge-boats-attack.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@oaknetwork/contracts-sdk": patch ---- - -updated docs diff --git a/.changeset/lazy-chairs-exist.md b/.changeset/lazy-chairs-exist.md deleted file mode 100644 index 0f89ddd6..00000000 --- a/.changeset/lazy-chairs-exist.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@oaknetwork/payments-sdk': minor ---- - -Remove deprecated field values from payment method and add new webhook field diff --git a/.changeset/lemon-rice-cross.md b/.changeset/lemon-rice-cross.md deleted file mode 100644 index f9a22a6e..00000000 --- a/.changeset/lemon-rice-cross.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@oaknetwork/contracts-sdk": minor ---- - -Add multicall utility and metrics aggregation module diff --git a/.changeset/light-glasses-build.md b/.changeset/light-glasses-build.md deleted file mode 100644 index 79019d65..00000000 --- a/.changeset/light-glasses-build.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@oaknetwork/contracts-sdk": minor ---- - -Add comprehensive event log handling across all contract entities diff --git a/.changeset/many-parrots-give.md b/.changeset/many-parrots-give.md deleted file mode 100644 index 6851d33d..00000000 --- a/.changeset/many-parrots-give.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@oaknetwork/payments-sdk': minor ---- - -Update payment sdk with the recent changes from Crowdsplit diff --git a/.changeset/payments-generated-types-layer.md b/.changeset/payments-generated-types-layer.md new file mode 100644 index 00000000..2973c524 --- /dev/null +++ b/.changeset/payments-generated-types-layer.md @@ -0,0 +1,5 @@ +--- +"@oaknetwork/payments-sdk": minor +--- + +Add an internal generated types layer sourced from the Crowdsplit OpenAPI contract (v2.1.1). Types are generated into `src/generated/` from a committed spec snapshot and kept in sync by CI. Not part of the public API surface yet; services will adopt them progressively. diff --git a/.changeset/six-memes-remain.md b/.changeset/six-memes-remain.md deleted file mode 100644 index f7d892b5..00000000 --- a/.changeset/six-memes-remain.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@oaknetwork/payments-sdk': minor ---- - -Add `createCountryService` for listing countries with KYC support status via the public `GET /api/v1/countries` endpoint diff --git a/.changeset/solid-colts-spend.md b/.changeset/solid-colts-spend.md deleted file mode 100644 index 05889208..00000000 --- a/.changeset/solid-colts-spend.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@oaknetwork/contracts-sdk': major ---- - -Initial release diff --git a/.changeset/young-rings-speak.md b/.changeset/young-rings-speak.md deleted file mode 100644 index 17f11ecd..00000000 --- a/.changeset/young-rings-speak.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@oaknetwork/payments-sdk": minor ---- - -updated new cs domain diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..78cab721 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# Generated files — collapsed by default in PR review (still expandable). +packages/payments/src/generated/** linguist-generated=true +packages/payments/openapi/openapi.bundled.yaml linguist-generated=true +packages/contracts/src/contracts/*/abi.ts linguist-generated=true +packages/contracts/src/contracts/abi-manifest.json linguist-generated=true diff --git a/.github/workflows/backend-sync.yml b/.github/workflows/backend-sync.yml index 4bcf1375..c9d2f842 100644 --- a/.github/workflows/backend-sync.yml +++ b/.github/workflows/backend-sync.yml @@ -129,6 +129,9 @@ jobs: `2. Determine if SDK types, services, or methods need updating`, `3. Follow SDK patterns (Result, factory functions, withAuth)`, ``, + `> **Note:** when this change reaches a stable release, the Crowdsplit API Sync workflow`, + `> will open a PR here automatically with the updated spec snapshot and generated types.`, + ``, `---`, `${runLink} · _Auto-generated by Backend Staging Sync workflow_`, ].join('\n'), diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40915663..4c3d9275 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,9 @@ jobs: - name: Build all packages run: pnpm -r --workspace-concurrency=Infinity build + - name: Check generated ABI modules against manifest + run: pnpm --filter @oaknetwork/contracts-sdk abis:check + - name: Run tests with coverage (enforces 100% threshold) run: pnpm -r --workspace-concurrency=Infinity test --coverage env: @@ -68,3 +71,42 @@ jobs: - name: Run lint run: pnpm -r --workspace-concurrency=Infinity lint + + # Verifies packages/payments/src/generated/api.ts is exactly what the + # committed spec snapshot generates - catches hand edits to the generated + # file, snapshot edits without regeneration, and openapi-typescript bumps + # without regeneration. Node 20 pinned (openapi-typescript engine floor); + # single job on purpose - no need to triple it across the node matrix. + api-types-freshness: + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + run_install: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Regenerate types from committed spec snapshot + run: pnpm --filter @oaknetwork/payments-sdk exec openapi-typescript openapi/openapi.bundled.yaml -o src/generated/api.ts + + - name: Fail on drift + run: | + if ! git diff --exit-code -- packages/payments/src/generated; then + echo "::error::packages/payments/src/generated is out of sync with the committed spec snapshot." >&2 + echo "Never hand-edit src/generated - re-run scripts/sync-crowdsplit.mjs (or the regen command above) and commit the result." >&2 + exit 1 + fi diff --git a/.github/workflows/contracts-sync.yml b/.github/workflows/contracts-sync.yml new file mode 100644 index 00000000..e93fdafc --- /dev/null +++ b/.github/workflows/contracts-sync.yml @@ -0,0 +1,282 @@ +# Contracts Sync +# +# Fired by oak-network/contracts on every stable GitHub Release (see its +# release.yml → repository_dispatch: contracts-release-published), or manually +# via workflow_dispatch with an existing release tag. +# +# Downloads the release's deterministic ABI bundle, verifies its checksum +# against the dispatch payload, regenerates the vendored ABI modules + +# manifest + README table, and opens a human-reviewed PR against develop. +# Re-runs for the same tag converge on the same sync/contracts- PR. +# +# Secrets: +# SDK_BOT_TOKEN - fine-grained PAT scoped to this repo (Contents r/w + +# Pull requests r/w). A PAT (not GITHUB_TOKEN) is required so the created +# PR triggers the CI workflow. Rotate before expiry (max 1 year). +name: Contracts Sync + +on: + repository_dispatch: + types: [contracts-release-published] + workflow_dispatch: + inputs: + tag: + description: "contracts release tag (e.g. v1.1.0)" + required: true + +# contents stays read-only for GITHUB_TOKEN: the sync branch is pushed with +# the SDK_BOT_TOKEN PAT, not the workflow token. +permissions: + contents: read + pull-requests: write + issues: write + +concurrency: + group: contracts-sync-${{ github.event.client_payload.tag || inputs.tag }} + cancel-in-progress: true + +jobs: + sync: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + # Dispatch payloads are untrusted input - validate before any use in shell. + - name: Resolve and validate inputs + id: meta + env: + PAYLOAD_TAG: ${{ github.event.client_payload.tag }} + INPUT_TAG: ${{ inputs.tag }} + PAYLOAD_SHA: ${{ github.event.client_payload.sha }} + PAYLOAD_PREVIOUS_TAG: ${{ github.event.client_payload.previous_tag }} + PAYLOAD_BUNDLE_SHA256: ${{ github.event.client_payload.bundle_sha256 }} + run: | + TAG="${PAYLOAD_TAG:-$INPUT_TAG}" + if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then + echo "error: tag '$TAG' does not match vMAJOR.MINOR.PATCH[-prerelease]" >&2 + exit 1 + fi + if [[ -n "$PAYLOAD_SHA" && ! "$PAYLOAD_SHA" =~ ^[0-9a-f]{40}$ ]]; then + echo "error: payload sha is not a 40-char hex sha" >&2 + exit 1 + fi + if [[ -n "$PAYLOAD_PREVIOUS_TAG" && ! "$PAYLOAD_PREVIOUS_TAG" =~ ^v[0-9A-Za-z.-]+$ ]]; then + echo "error: payload previous_tag is malformed" >&2 + exit 1 + fi + if [[ -n "$PAYLOAD_BUNDLE_SHA256" && ! "$PAYLOAD_BUNDLE_SHA256" =~ ^[0-9a-f]{64}$ ]]; then + echo "error: payload bundle_sha256 is not a sha256 hex digest" >&2 + exit 1 + fi + { + echo "tag=$TAG" + echo "sha=$PAYLOAD_SHA" + echo "previous_tag=$PAYLOAD_PREVIOUS_TAG" + echo "bundle_sha256=$PAYLOAD_BUNDLE_SHA256" + } >> "$GITHUB_OUTPUT" + + - name: Checkout develop + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: develop + fetch-depth: 0 + persist-credentials: false + + - name: Setup pnpm + uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d # v4.1.0 + with: + run_install: false + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: 20 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Download and verify ABI bundle + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # contracts repo is public + TAG: ${{ steps.meta.outputs.tag }} + EXPECTED_SHA256: ${{ steps.meta.outputs.bundle_sha256 }} + run: | + mkdir -p "$RUNNER_TEMP/bundle-dl" "$RUNNER_TEMP/bundle" + gh release download "$TAG" -R oak-network/contracts \ + -p "abis-$TAG.tar.gz" -p SHA256SUMS -D "$RUNNER_TEMP/bundle-dl" + cd "$RUNNER_TEMP/bundle-dl" + # Payload checksum (when dispatched) pins the exact bundle that was + # built for the release - a re-uploaded asset fails here. + if [[ -n "$EXPECTED_SHA256" ]]; then + echo "$EXPECTED_SHA256 abis-$TAG.tar.gz" | sha256sum -c - + fi + # --ignore-missing keeps this robust if SHA256SUMS ever lists assets + # beyond the two downloaded here; the grep guards against a SHA256SUMS + # that no longer covers the bundle at all. + grep -q "abis-$TAG.tar.gz" SHA256SUMS + sha256sum -c --ignore-missing SHA256SUMS + tar -xzf "abis-$TAG.tar.gz" -C "$RUNNER_TEMP/bundle" + + - name: Cross-check bundle metadata + env: + TAG: ${{ steps.meta.outputs.tag }} + EXPECTED_SHA: ${{ steps.meta.outputs.sha }} + run: | + BUNDLE_TAG=$(jq -r .tag "$RUNNER_TEMP/bundle/metadata.json") + BUNDLE_SHA=$(jq -r .sha "$RUNNER_TEMP/bundle/metadata.json") + if [[ "$BUNDLE_TAG" != "$TAG" ]]; then + echo "error: bundle metadata tag '$BUNDLE_TAG' != requested tag '$TAG'" >&2 + exit 1 + fi + if [[ -n "$EXPECTED_SHA" && "$BUNDLE_SHA" != "$EXPECTED_SHA" ]]; then + echo "error: bundle metadata sha '$BUNDLE_SHA' != dispatched sha '$EXPECTED_SHA'" >&2 + exit 1 + fi + + - name: Regenerate ABI modules + run: pnpm --filter @oaknetwork/contracts-sdk abis:generate --bundle "$RUNNER_TEMP/bundle" --report "$RUNNER_TEMP/sync-report.md" + + - name: Detect changes + id: diff + run: | + if git status --porcelain -- packages/contracts | grep -q .; then + echo "changed=true" >> "$GITHUB_OUTPUT" + else + echo "changed=false" >> "$GITHUB_OUTPUT" + echo "ABIs already in sync with ${{ steps.meta.outputs.tag }}, no PR needed." >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Write changeset + if: steps.diff.outputs.changed == 'true' + env: + TAG: ${{ steps.meta.outputs.tag }} + run: | + SLUG=$(echo "$TAG" | tr '.' '-') + cat > ".changeset/contracts-sync-$SLUG.md" < [!WARNING]" + echo "> The SDK does **not typecheck** against these ABIs: the entity layer" + echo "> (reads/writes/simulate/events) references changed or removed contract members." + echo "> Update the affected entities on this branch before merging." + echo "" + fi + cat "$RUNNER_TEMP/sync-report.md" + echo "" + echo "### Reviewer checklist" + echo "" + echo "- [ ] ABI diff reviewed against the contracts release notes" + echo "- [ ] Changeset escalated to \`major\` if functions were removed/renamed (defaults to \`minor\`)" + echo "- [ ] Entity wrappers (reads/writes/simulate/events) updated for new or changed functions" + echo "- [ ] README table + docs pages still accurate for changed entities" + echo "" + echo "_Auto-generated by the Contracts Sync workflow ([run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}))._" + } > "$RUNNER_TEMP/pr-body.md" + + - name: Create or update sync PR + if: steps.diff.outputs.changed == 'true' + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 + with: + token: ${{ secrets.SDK_BOT_TOKEN }} + branch: sync/contracts-${{ steps.meta.outputs.tag }} + base: develop + delete-branch: true + add-paths: | + packages/contracts/src/contracts/** + packages/contracts/README.md + .changeset/contracts-sync-*.md + commit-message: "chore(contracts): sync ABIs from oak-network/contracts ${{ steps.meta.outputs.tag }}" + title: "[Contracts Sync] ${{ steps.meta.outputs.tag }}" + labels: contracts-sync + body-path: ${{ runner.temp }}/pr-body.md + + on-failure: + needs: sync + if: failure() + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + issues: write + steps: + - name: Open or update contracts-sync issue + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + env: + TAG: ${{ github.event.client_payload.tag || inputs.tag }} + with: + script: | + // TAG may be a raw, unvalidated dispatch payload here (the sync job + // can fail at validation), so sanitize before using it in the issue. + const tag = (process.env.TAG || '').replace(/[^\w.\-]/g, '').slice(0, 64) || 'unknown'; + const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; + const title = `Contracts sync failed for ${tag}`; + const body = [ + `The Contracts Sync workflow failed for \`${tag}\`.`, + '', + `**Run:** ${runUrl}`, + '', + 'Common causes: a mapped contract was renamed/removed upstream (update', + '`packages/contracts/src/scripts/contract-map.ts`), a bundle checksum mismatch', + '(investigate before overriding: the release asset may have been re-uploaded),', + 'or an expired token. Re-run via workflow_dispatch with the tag once fixed.', + ].join('\n'); + const existing = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + labels: 'contracts-sync', + state: 'open', + per_page: 10, + }); + const match = existing.data.find(i => !i.pull_request && i.title === title); + if (match) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: match.number, + body: `Failed again: ${runUrl}`, + }); + } else { + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title, + body, + labels: ['contracts-sync'], + }); + } diff --git a/.github/workflows/crowdsplit-sync.yml b/.github/workflows/crowdsplit-sync.yml new file mode 100644 index 00000000..e435e3a6 --- /dev/null +++ b/.github/workflows/crowdsplit-sync.yml @@ -0,0 +1,197 @@ +# Crowdsplit API Sync +# +# Fired by oak-network/crowdsplit on every stable release tag (see its +# notify-docs-spec-update.yml → repository_dispatch: crowdsplit-released), or +# manually via workflow_dispatch with an existing stable tag. +# +# Checks out crowdsplit at the tag, bundles the OpenAPI spec, snapshots it into +# packages/payments/openapi/, regenerates the internal generated-types layer, +# and opens a human-reviewed PR against develop with an endpoint-level diff. +# Re-runs for the same tag converge on the same sync/crowdsplit- PR; +# releases with no spec change are green no-ops. +# +# Secrets: +# CROWDSPLIT_READ_TOKEN - fine-grained PAT, Contents: read on +# oak-network/crowdsplit only (the repo is private). +# SDK_BOT_TOKEN - fine-grained PAT scoped to this repo (Contents r/w + +# Pull requests r/w). A PAT (not GITHUB_TOKEN) is required so the created +# PR triggers the CI workflow. Rotate both before expiry (max 1 year). +name: Crowdsplit API Sync + +on: + repository_dispatch: + types: [crowdsplit-released] + workflow_dispatch: + inputs: + tag: + description: "Crowdsplit stable tag (vX.Y.Z)" + required: true + +# contents stays read-only for GITHUB_TOKEN: the sync branch is pushed with +# the SDK_BOT_TOKEN PAT, not the workflow token. +permissions: + contents: read + pull-requests: write + issues: write + +concurrency: + group: crowdsplit-sync # serialize; a re-dispatch queues behind + cancel-in-progress: false + +env: + OASDIFF_VERSION: v1.23.0 + +jobs: + sync: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + # Dispatch payloads are untrusted input - validate before any use in shell. + - name: Resolve and validate inputs + id: meta + env: + PAYLOAD_TAG: ${{ github.event.client_payload.tag }} + INPUT_TAG: ${{ inputs.tag }} + run: | + TAG="${PAYLOAD_TAG:-$INPUT_TAG}" + if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "error: tag '$TAG' does not match stable release pattern vX.Y.Z" >&2 + exit 1 + fi + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + + - name: Checkout develop + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: develop + persist-credentials: false + + - name: Checkout crowdsplit at ${{ steps.meta.outputs.tag }} + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: oak-network/crowdsplit + ref: ${{ steps.meta.outputs.tag }} + token: ${{ secrets.CROWDSPLIT_READ_TOKEN }} + path: .sync-tmp/crowdsplit + persist-credentials: false + + - name: Setup pnpm + uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d # v4.1.0 + with: + run_install: false + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: 20 # openapi-typescript@7 requires >=20 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Install oasdiff + run: | + curl -fsSL "https://github.com/oasdiff/oasdiff/releases/download/${OASDIFF_VERSION}/oasdiff_$(echo "$OASDIFF_VERSION" | tr -d v)_linux_amd64.tar.gz" \ + -o "$RUNNER_TEMP/oasdiff.tar.gz" + tar -xzf "$RUNNER_TEMP/oasdiff.tar.gz" -C "$RUNNER_TEMP" oasdiff + install -m 0755 "$RUNNER_TEMP/oasdiff" /usr/local/bin/oasdiff + oasdiff --version + + - name: Run sync + id: sync + env: + TAG: ${{ steps.meta.outputs.tag }} + run: | + # Resolve the commit from the tag checkout itself; dispatch payloads + # carry no sha (annotated tags make github.sha unreliable upstream). + SHA=$(git -C .sync-tmp/crowdsplit rev-parse HEAD) + node scripts/sync-crowdsplit.mjs --crowdsplit-dir .sync-tmp/crowdsplit --tag "$TAG" --sha "$SHA" + + # The add-paths allowlist on the PR step already keeps the crowdsplit + # checkout out of the commit; removing it here is a second guard. + - name: Remove crowdsplit checkout + run: rm -rf .sync-tmp + + - name: Upload full API changelog + if: steps.sync.outputs.changed == 'true' + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: api-changelog-${{ steps.meta.outputs.tag }} + path: ${{ steps.sync.outputs.changelog_path }} + retention-days: 90 + + - name: Create or update sync PR + if: steps.sync.outputs.changed == 'true' + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 + with: + token: ${{ secrets.SDK_BOT_TOKEN }} + branch: sync/crowdsplit-${{ steps.meta.outputs.tag }} + base: develop + delete-branch: true + add-paths: | + packages/payments/openapi/** + packages/payments/src/generated/** + .changeset/crowdsplit-sync-*.md + commit-message: "chore(payments): sync API spec + generated types from crowdsplit ${{ steps.meta.outputs.tag }}" + title: "[API Sync] Crowdsplit ${{ steps.meta.outputs.tag }}" + labels: ${{ steps.sync.outputs.breaking == 'true' && 'api-sync, crowdsplit, breaking-api-change' || 'api-sync, crowdsplit' }} + body-path: ${{ steps.sync.outputs.body_path }} + + - name: No changes + if: steps.sync.outputs.changed != 'true' + env: + TAG: ${{ steps.meta.outputs.tag }} + run: echo "Spec unchanged at $TAG, no PR needed." >> "$GITHUB_STEP_SUMMARY" + + on-failure: + needs: sync + if: failure() + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + issues: write + steps: + - name: Open or update api-sync issue + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + env: + TAG: ${{ github.event.client_payload.tag || inputs.tag }} + with: + script: | + // TAG may be a raw, unvalidated dispatch payload here (the sync job + // can fail at validation), so sanitize before using it in the issue. + const tag = (process.env.TAG || '').replace(/[^\w.\-]/g, '').slice(0, 64) || 'unknown'; + const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; + const title = `Crowdsplit sync failed for ${tag}`; + const body = [ + `The Crowdsplit API Sync workflow failed for \`${tag}\`.`, + '', + `**Run:** ${runUrl}`, + '', + 'Common causes: an expired `CROWDSPLIT_READ_TOKEN` (checkout step), a spec', + 'bundling failure at the tag, or an expired `SDK_BOT_TOKEN` (PR step).', + 'Fix the cause, then re-run via workflow_dispatch with the tag.', + ].join('\n'); + const existing = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + labels: 'api-sync', + state: 'open', + per_page: 10, + }); + const match = existing.data.find(i => !i.pull_request && i.title === title); + if (match) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: match.number, + body: `Failed again: ${runUrl}`, + }); + } else { + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title, + body, + labels: ['api-sync'], + }); + } diff --git a/.gitignore b/.gitignore index ecf40f3c..ccf07bd6 100644 --- a/.gitignore +++ b/.gitignore @@ -111,4 +111,6 @@ test-sdk.ts **/test-sdk.ts .specstory -.specstory/** */ \ No newline at end of file +.specstory/** */ +# Crowdsplit checkout used by scripts/sync-crowdsplit.mjs in CI +.sync-tmp/ diff --git a/CLAUDE.md b/CLAUDE.md index b74d0f74..dbe87e9f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -879,6 +879,24 @@ export function verifyWebhookSignature( --- +## Generated Artifacts & Sync Pipelines + +Release-triggered workflows keep source-derived artifacts in sync with the +upstream repos. **Never hand-edit these files** - CI rejects drift: + +| Artifact | Source of truth | Regenerate with | +| --- | --- | --- | +| `packages/contracts/src/contracts/*/abi.ts` + `abi-manifest.json` | oak-network/contracts release ABI bundle | `pnpm --filter @oaknetwork/contracts-sdk abis:generate --bundle ` | +| `packages/contracts` README "Available Entities" table (between AUTOGEN markers) | `packages/contracts/src/scripts/contract-map.ts` | `pnpm --filter @oaknetwork/contracts-sdk readme:update` | +| `packages/payments/openapi/openapi.bundled.yaml` + `.sync-meta.json` + `src/generated/api.ts` | Crowdsplit OpenAPI spec at a stable release tag | `node scripts/sync-crowdsplit.mjs --crowdsplit-dir --tag vX.Y.Z` | + +- `packages/payments/src/generated/` is **internal-only** - not exported from + the package. Services migrate to these types progressively. +- Sync PRs are opened automatically by `contracts-sync.yml` / + `crowdsplit-sync.yml` on upstream releases and are always human-reviewed. +- To onboard a new contract: add a `contract-map.ts` entry, regenerate, then + build the entity layer + tests. + ## CI/CD Requirements ### CI Workflow diff --git a/packages/contracts/CHANGELOG.md b/packages/contracts/CHANGELOG.md index 864dea5c..df18447b 100644 --- a/packages/contracts/CHANGELOG.md +++ b/packages/contracts/CHANGELOG.md @@ -1,5 +1,30 @@ --- +## 1.2.0 +### Minor Changes + +### Minor Changes +- Add missing events, reads, errors, type-safe constants, simulation results, transaction preparation, and getReceipt; update event log fetching documentation and improve code consistency + +## 1.1.1 +### Patch Changes + +### Patch Changes +- updated docs + +## 1.1.0 +### Minor Changes + +### Minor Changes +- Add multicall utility and metrics aggregation module +- Add comprehensive event log handling across all contract entities + +## 1.0.0 +### Major Changes + +### Major Changes +- Initial release + ## 1.0.0 ### Major Changes diff --git a/packages/contracts/README.md b/packages/contracts/README.md index 59dfcf2f..a36c82f6 100644 --- a/packages/contracts/README.md +++ b/packages/contracts/README.md @@ -224,6 +224,8 @@ const unwatch = gp.events.watchPlatformEnlisted((logs) => { /* ... */ }); ### Available Entities + + | Entity | Factory method | Description | Docs | | --- | --- | --- | --- | | **GlobalParams** | `oak.globalParams(addr)` | Protocol-wide config: platforms, fees, currencies, registry | [Docs](https://oaknetwork.org/docs/contracts-sdk/global-params) | @@ -231,10 +233,12 @@ const unwatch = gp.events.watchPlatformEnlisted((logs) => { /* ... */ }); | **CampaignInfo** | `oak.campaignInfo(addr)` | Per-campaign state: deadlines, goals, funding progress | [Docs](https://oaknetwork.org/docs/contracts-sdk/campaign-info) | | **TreasuryFactory** | `oak.treasuryFactory(addr)` | Deploys and manages treasury implementations | [Docs](https://oaknetwork.org/docs/contracts-sdk/treasury-factory) | | **PaymentTreasury** | `oak.paymentTreasury(addr)` | Fiat-style payments, confirmations, refunds, withdrawals | [Docs](https://oaknetwork.org/docs/contracts-sdk/payment-treasury) | -| **AllOrNothing** | `oak.allOrNothingTreasury(addr)` | Crowdfunding treasury — funds released only if goal is met | [Docs](https://oaknetwork.org/docs/contracts-sdk/all-or-nothing) | -| **KeepWhatsRaised** | `oak.keepWhatsRaisedTreasury(addr)` | Crowdfunding treasury — creator keeps all funds raised | [Docs](https://oaknetwork.org/docs/contracts-sdk/keep-whats-raised) | +| **AllOrNothing** | `oak.allOrNothingTreasury(addr)` | Crowdfunding treasury - funds released only if goal is met | [Docs](https://oaknetwork.org/docs/contracts-sdk/all-or-nothing) | +| **KeepWhatsRaised** | `oak.keepWhatsRaisedTreasury(addr)` | Crowdfunding treasury - creator keeps all funds raised | [Docs](https://oaknetwork.org/docs/contracts-sdk/keep-whats-raised) | | **ItemRegistry** | `oak.itemRegistry(addr)` | Manages purchasable items with metadata | [Docs](https://oaknetwork.org/docs/contracts-sdk/item-registry) | + + > `paymentTreasury()` supports both **PaymentTreasury** and **TimeConstrainedPaymentTreasury** variants — same ABI, same SDK interface. --- diff --git a/packages/contracts/__tests__/unit/scripts.test.ts b/packages/contracts/__tests__/unit/scripts.test.ts index d8c6f882..c08b9a32 100644 --- a/packages/contracts/__tests__/unit/scripts.test.ts +++ b/packages/contracts/__tests__/unit/scripts.test.ts @@ -1,12 +1,377 @@ -import { checkAbis } from "../../src/scripts/check-abis"; -import { generateAbis } from "../../src/scripts/generate-abis"; +import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync, existsSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { abiSha256, canonicalizeAbi, canonicalJson, type AbiItem } from "../../src/scripts/abi-canonical"; +import { CONTRACT_MAP, KNOWN_UNMAPPED } from "../../src/scripts/contract-map"; +import { checkRegistryKeys } from "../../src/scripts/check-registry-keys"; +import { + generateAbis, + printTsLiteral, + renderAbiModule, + renderReportMarkdown, + MANIFEST_RELATIVE_PATH, +} from "../../src/scripts/generate-abis"; +import { + BEGIN_MARKER, + END_MARKER, + renderEntitiesTable, + replaceEntitiesTable, + updateReadme, +} from "../../src/scripts/update-readme"; -describe("script stubs", () => { - it("checkAbis throws TODO error", () => { - expect(() => checkAbis()).toThrow("TODO"); +const SAMPLE_ABI: AbiItem[] = [ + { + type: "function", + name: "transfer", + inputs: [ + { name: "to", type: "address" }, + { name: "amount", type: "uint256" }, + ], + outputs: [{ name: "", type: "bool" }], + stateMutability: "nonpayable", + }, + { type: "error", name: "Unauthorized", inputs: [] }, + { + type: "event", + name: "Transfer", + anonymous: false, + inputs: [ + { indexed: true, name: "from", type: "address" }, + { indexed: true, name: "to", type: "address" }, + ], + }, +]; + +/** Builds a DataRegistryKeys.sol fixture, optionally with extra constant lines appended. */ +function registrySolWith(extraConstants = ""): string { + return ` +library DataRegistryKeys { + bytes32 public constant BUFFER_TIME = keccak256("bufferTime"); + bytes32 public constant MAX_PAYMENT_EXPIRATION = keccak256("maxPaymentExpiration"); + bytes32 public constant CAMPAIGN_LAUNCH_BUFFER = keccak256("campaignLaunchBuffer"); + bytes32 public constant MINIMUM_CAMPAIGN_DURATION = keccak256("minimumCampaignDuration"); +${extraConstants}} +`; +} + +const REGISTRY_SOL = registrySolWith(); + +/** Writes a minimal but complete release bundle covering every mapped contract. */ +function writeFixtureBundle( + dir: string, + options: { extraDeployable?: string[]; omit?: string[]; registrySol?: string } = {}, +): void { + mkdirSync(join(dir, "abis"), { recursive: true }); + mkdirSync(join(dir, "sources"), { recursive: true }); + const included = CONTRACT_MAP.filter((entry) => !(options.omit ?? []).includes(entry.contractName)); + for (const entry of included) { + writeFileSync( + join(dir, "abis", `${entry.contractName}.json`), + JSON.stringify({ contractName: entry.contractName, sourcePath: entry.sourcePath, abi: SAMPLE_ABI }), + ); + } + writeFileSync(join(dir, "sources", "DataRegistryKeys.sol"), options.registrySol ?? REGISTRY_SOL); + const deployable = [ + ...included.map((entry) => entry.contractName), + ...KNOWN_UNMAPPED, + ...(options.extraDeployable ?? []), + ]; + writeFileSync( + join(dir, "metadata.json"), + JSON.stringify({ + tag: "v9.9.9", + sha: "a".repeat(40), + solcVersion: "0.8.22", + foundryVersion: "forge test-fixture", + contracts: deployable, + deployableContracts: deployable, + }), + ); +} + +describe("abi-canonical", () => { + it("sorts entries by type, then name, and alphabetizes object keys", () => { + const canonical = canonicalizeAbi(SAMPLE_ABI); + expect(canonical.map((item) => item.type)).toEqual(["error", "event", "function"]); + expect(Object.keys(canonical[2])).toEqual([...Object.keys(canonical[2])].sort()); + }); + + it("preserves parameter order inside inputs", () => { + const canonical = canonicalizeAbi(SAMPLE_ABI); + const fn = canonical.find((item) => item.type === "function"); + expect((fn?.inputs as Array<{ name: string }>).map((p) => p.name)).toEqual(["to", "amount"]); + }); + + it("is stable across repeated runs and input orderings", () => { + const reversed = [...SAMPLE_ABI].reverse(); + expect(canonicalJson(SAMPLE_ABI)).toBe(canonicalJson(reversed)); + expect(canonicalJson(canonicalizeAbi(SAMPLE_ABI))).toBe(canonicalJson(SAMPLE_ABI)); + }); + + it("hashes identically regardless of entry order, differently for different ABIs", () => { + const reversed = [...SAMPLE_ABI].reverse(); + expect(abiSha256(SAMPLE_ABI)).toBe(abiSha256(reversed)); + expect(abiSha256(SAMPLE_ABI)).not.toBe(abiSha256(SAMPLE_ABI.slice(1))); + }); + + it("orders same-name overloads by input arity", () => { + const overloads: AbiItem[] = [ + { type: "function", name: "get", inputs: [{ type: "uint256" }, { type: "address" }] }, + { type: "function", name: "get", inputs: [] }, + { type: "function", name: "get", inputs: [{ type: "uint256" }] }, + ]; + const canonical = canonicalizeAbi(overloads); + expect(canonical.map((item) => item.inputs?.length)).toEqual([0, 1, 2]); + }); +}); + +describe("check-registry-keys", () => { + it("reports no drift for the real key set", () => { + expect(checkRegistryKeys(REGISTRY_SOL)).toEqual([]); + }); + + it("reports keys present upstream but missing from the SDK", () => { + const withExtra = registrySolWith(' bytes32 public constant NEW_KEY = keccak256("newKey");\n'); + const drift = checkRegistryKeys(withExtra); + expect(drift).toHaveLength(1); + expect(drift[0].key).toBe("NEW_KEY"); + expect(drift[0].problem).toContain("missing from DATA_REGISTRY_KEYS"); + }); + + it("reports SDK keys that no longer exist upstream", () => { + const withoutBuffer = REGISTRY_SOL.replace(/^.*BUFFER_TIME.*$\n/m, ""); + const drift = checkRegistryKeys(withoutBuffer); + expect(drift).toHaveLength(1); + expect(drift[0].key).toBe("BUFFER_TIME"); + expect(drift[0].problem).toContain("no longer declared"); + }); + + it("reports hash mismatches when an upstream key string changes", () => { + const renamed = REGISTRY_SOL.replace('keccak256("bufferTime")', 'keccak256("bufferTimeV2")'); + const drift = checkRegistryKeys(renamed); + expect(drift).toHaveLength(1); + expect(drift[0].key).toBe("BUFFER_TIME"); + expect(drift[0].problem).toContain("hash mismatch"); + }); + + it("throws when no constants can be parsed at all", () => { + expect(() => checkRegistryKeys("// empty file")).toThrow("No bytes32 keccak256 constants"); + }); +}); + +describe("renderAbiModule", () => { + it("renders a formatted module with header and as-const export", () => { + const entry = CONTRACT_MAP[0]; + const rendered = renderAbiModule(entry, canonicalizeAbi(SAMPLE_ABI), { + tag: "v9.9.9", + sha: "a".repeat(40), + }); + expect(rendered).toContain(`export const ${entry.exportName} = [`); + expect(rendered).toContain("] as const;"); + expect(rendered).toContain("Do not edit by hand"); + expect(rendered).toContain(`v9.9.9 (${"a".repeat(7)})`); + const again = renderAbiModule(entry, canonicalizeAbi(SAMPLE_ABI), { + tag: "v9.9.9", + sha: "a".repeat(40), + }); + expect(again).toBe(rendered); + }); + + it("prints literals prettier-style: unquoted keys, inline-when-short, expanded with trailing commas", () => { + expect(printTsLiteral({ name: "to", type: "address" })).toBe('{ name: "to", type: "address" }'); + const long = printTsLiteral(SAMPLE_ABI); + expect(long).toContain("[\n"); + expect(long).toContain("},\n"); + expect(long).not.toContain('"name":'); + expect(printTsLiteral({ "not-an-identifier": 1 })).toBe('{ "not-an-identifier": 1 }'); + expect(printTsLiteral([])).toBe("[]"); + expect(printTsLiteral({})).toBe("{}"); + }); +}); + +describe("generateAbis", () => { + let bundleDir: string; + let packageRoot: string; + + beforeEach(() => { + bundleDir = mkdtempSync(join(tmpdir(), "abi-bundle-")); + packageRoot = mkdtempSync(join(tmpdir(), "sdk-pkg-")); + }); + + afterEach(() => { + rmSync(bundleDir, { recursive: true, force: true }); + rmSync(packageRoot, { recursive: true, force: true }); + }); + + it("creates all mapped modules and the manifest, then reports unchanged on re-run", async () => { + writeFixtureBundle(bundleDir); + const first = await generateAbis({ bundleDir, packageRoot }); + expect(first.changes.map((c) => c.kind)).toEqual(CONTRACT_MAP.map(() => "created")); + expect(first.newContracts).toEqual([]); + expect(first.registryDrift).toEqual([]); + + const manifest = JSON.parse(readFileSync(join(packageRoot, MANIFEST_RELATIVE_PATH), "utf8")); + expect(manifest.tag).toBe("v9.9.9"); + expect(Object.keys(manifest.contracts)).toHaveLength(CONTRACT_MAP.length); + expect(manifest.contracts[CONTRACT_MAP[0].contractName]).toBe(abiSha256(SAMPLE_ABI)); + + const second = await generateAbis({ bundleDir, packageRoot }); + expect(second.changes.every((c) => c.kind === "unchanged")).toBe(true); + }); + + it("treats an ABI-identical release under a new tag as a no-op", async () => { + writeFixtureBundle(bundleDir); + await generateAbis({ bundleDir, packageRoot }); + const abiPath = join(packageRoot, "src", "contracts", CONTRACT_MAP[0].dir, "abi.ts"); + const moduleBefore = readFileSync(abiPath, "utf8"); + + const newTagBundle = mkdtempSync(join(tmpdir(), "abi-bundle-retag-")); + try { + writeFixtureBundle(newTagBundle); + const meta = JSON.parse(readFileSync(join(newTagBundle, "metadata.json"), "utf8")); + writeFileSync( + join(newTagBundle, "metadata.json"), + JSON.stringify({ ...meta, tag: "v9.9.10", sha: "b".repeat(40) }), + ); + + const report = await generateAbis({ bundleDir: newTagBundle, packageRoot }); + expect(report.changes.every((c) => c.kind === "unchanged")).toBe(true); + // Module header and manifest keep the provenance of the last content change. + expect(readFileSync(abiPath, "utf8")).toBe(moduleBefore); + const manifest = JSON.parse(readFileSync(join(packageRoot, MANIFEST_RELATIVE_PATH), "utf8")); + expect(manifest.tag).toBe("v9.9.9"); + } finally { + rmSync(newTagBundle, { recursive: true, force: true }); + } + }); + + it("classifies a changed ABI as updated and rewrites module and manifest", async () => { + writeFixtureBundle(bundleDir); + await generateAbis({ bundleDir, packageRoot }); + + const target = CONTRACT_MAP[0]; + const changedAbi = [...SAMPLE_ABI, { type: "function", name: "newMethod", inputs: [] }]; + writeFileSync( + join(bundleDir, "abis", `${target.contractName}.json`), + JSON.stringify({ contractName: target.contractName, sourcePath: target.sourcePath, abi: changedAbi }), + ); + const meta = JSON.parse(readFileSync(join(bundleDir, "metadata.json"), "utf8")); + writeFileSync( + join(bundleDir, "metadata.json"), + JSON.stringify({ ...meta, tag: "v9.9.10", sha: "b".repeat(40) }), + ); + + const report = await generateAbis({ bundleDir, packageRoot }); + const kinds = Object.fromEntries(report.changes.map((c) => [c.contractName, c.kind])); + expect(kinds[target.contractName]).toBe("updated"); + expect(report.changes.filter((c) => c.kind === "unchanged")).toHaveLength(CONTRACT_MAP.length - 1); + + const abiPath = join(packageRoot, "src", "contracts", target.dir, "abi.ts"); + expect(readFileSync(abiPath, "utf8")).toContain("newMethod"); + expect(readFileSync(abiPath, "utf8")).toContain("v9.9.10"); + const manifest = JSON.parse(readFileSync(join(packageRoot, MANIFEST_RELATIVE_PATH), "utf8")); + expect(manifest.tag).toBe("v9.9.10"); + expect(manifest.contracts[target.contractName]).toBe(abiSha256(changedAbi)); + }); + + it("throws when a mapped contract is missing from the bundle", async () => { + writeFixtureBundle(bundleDir, { omit: ["GlobalParams"] }); + await expect(generateAbis({ bundleDir, packageRoot })).rejects.toThrow( + /GlobalParams.*update src\/scripts\/contract-map\.ts/s, + ); + }); + + it("reports new deployable contracts without auto-adding them", async () => { + writeFixtureBundle(bundleDir, { extraDeployable: ["BrandNewTreasury"] }); + const report = await generateAbis({ bundleDir, packageRoot }); + expect(report.newContracts).toEqual(["BrandNewTreasury"]); + expect(existsSync(join(packageRoot, "src", "contracts", "brand-new-treasury"))).toBe(false); + }); + + it("does not treat KNOWN_UNMAPPED contracts as new", async () => { + writeFixtureBundle(bundleDir); + const report = await generateAbis({ bundleDir, packageRoot }); + expect(report.newContracts).toEqual([]); + }); + + it("surfaces registry drift from the bundled DataRegistryKeys.sol", async () => { + writeFixtureBundle(bundleDir, { + registrySol: registrySolWith(' bytes32 public constant EXTRA = keccak256("extra");\n'), + }); + const report = await generateAbis({ bundleDir, packageRoot }); + expect(report.registryDrift).toHaveLength(1); + expect(report.registryDrift[0].key).toBe("EXTRA"); + }); + + it("writes nothing in check mode but still classifies changes", async () => { + writeFixtureBundle(bundleDir); + const report = await generateAbis({ bundleDir, packageRoot, check: true }); + expect(report.changes.every((c) => c.kind === "created")).toBe(true); + expect(existsSync(join(packageRoot, MANIFEST_RELATIVE_PATH))).toBe(false); + expect(existsSync(join(packageRoot, "src", "contracts", CONTRACT_MAP[0].dir, "abi.ts"))).toBe(false); + }); + + it("rejects a bundle with malformed metadata", async () => { + writeFixtureBundle(bundleDir); + writeFileSync(join(bundleDir, "metadata.json"), JSON.stringify({ tag: "v1.0.0" })); + await expect(generateAbis({ bundleDir, packageRoot })).rejects.toThrow("metadata.json"); + }); + + it("renders a markdown report covering changes, new contracts, and drift", async () => { + writeFixtureBundle(bundleDir, { extraDeployable: ["BrandNewTreasury"] }); + const report = await generateAbis({ bundleDir, packageRoot }); + const markdown = renderReportMarkdown(report); + expect(markdown).toContain("### ABI changes"); + expect(markdown).toContain("| GlobalParams |"); + expect(markdown).toContain("New contracts detected"); + expect(markdown).toContain("`BrandNewTreasury`"); + }); +}); + +describe("update-readme", () => { + const wrap = (table: string) => `# Title\n\nintro prose\n\n${table}\n\ntrailing prose\n`; + + it("replaces stale marker content with the rendered table", () => { + const stale = wrap(`${BEGIN_MARKER}\nstale table\n${END_MARKER}`); + const updated = replaceEntitiesTable(stale); + expect(updated).toContain(renderEntitiesTable()); + expect(updated).not.toContain("stale table"); + expect(updated).toContain("intro prose"); + expect(updated).toContain("trailing prose"); + }); + + it("is idempotent once in sync", () => { + const synced = wrap(renderEntitiesTable()); + expect(replaceEntitiesTable(synced)).toBe(synced); + }); + + it("renders one row per mapped contract", () => { + const table = renderEntitiesTable(); + for (const entry of CONTRACT_MAP) { + expect(table).toContain(`| **${entry.contractName}** | \`${entry.factoryMethod}\``); + } + }); + + it("throws on missing or duplicated markers", () => { + expect(() => replaceEntitiesTable("no markers here")).toThrow("marker pair"); + expect(() => + replaceEntitiesTable(wrap(`${BEGIN_MARKER}\nx\n${END_MARKER}\n${BEGIN_MARKER}\ny\n${END_MARKER}`)), + ).toThrow("marker pair"); + expect(() => replaceEntitiesTable(wrap(`${END_MARKER}\nx\n${BEGIN_MARKER}`))).toThrow("out of order"); }); - it("generateAbis throws TODO error", () => { - expect(() => generateAbis()).toThrow("TODO"); + it("updateReadme writes in fix mode and only reports in check mode", async () => { + const dir = mkdtempSync(join(tmpdir(), "readme-")); + try { + const readmePath = join(dir, "README.md"); + writeFileSync(readmePath, wrap(`${BEGIN_MARKER}\nstale\n${END_MARKER}`)); + expect(await updateReadme({ check: true, readmePath })).toBe(false); + expect(readFileSync(readmePath, "utf8")).toContain("stale"); + expect(await updateReadme({ readmePath })).toBe(true); + expect(readFileSync(readmePath, "utf8")).toContain(renderEntitiesTable()); + expect(await updateReadme({ check: true, readmePath })).toBe(true); + } finally { + rmSync(dir, { recursive: true, force: true }); + } }); }); diff --git a/packages/contracts/package.json b/packages/contracts/package.json index d31d4966..ef0ddf13 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@oaknetwork/contracts-sdk", - "version": "0.0.0", + "version": "1.2.0", "description": "TypeScript SDK for Oak Network smart contracts", "keywords": [ "sdk", @@ -49,8 +49,11 @@ } }, "scripts": { + "abis:check": "tsx src/scripts/cli.ts check", + "abis:generate": "tsx src/scripts/cli.ts generate", "build": "tsup", "prepublishOnly": "pnpm run build", + "readme:update": "tsx src/scripts/cli.ts readme", "test": "jest --coverage", "test:unit": "jest --testPathPatterns='__tests__/unit' --coverage", "test:integration": "jest --testPathPatterns='__tests__/integration' --coverage --coverageThreshold='{}'", @@ -67,6 +70,7 @@ "ts-jest": "^29.4.6", "ts-node": "^10.9.2", "tsup": "^8.5.1", + "tsx": "^4.20.0", "typescript": "^5.5.4" }, "author": "oaknetwork", diff --git a/packages/contracts/src/contracts/abi-manifest.json b/packages/contracts/src/contracts/abi-manifest.json new file mode 100644 index 00000000..998a164f --- /dev/null +++ b/packages/contracts/src/contracts/abi-manifest.json @@ -0,0 +1,15 @@ +{ + "contractsRepo": "oak-network/contracts", + "tag": "sdk-baseline", + "sha": "", + "contracts": { + "AllOrNothing": "f4643b3f39e03f4c080e184e89dfc4a7c540f031b327391701513f3fab9fdb79", + "CampaignInfo": "2fb2731557455933d9ad4b4c46e597d47244791cc1cb20f92bf3c09abb400544", + "CampaignInfoFactory": "87ca40befd1d9e64f04ebd5d61a218454c6f0fcc2ecdc6466e1b9970f93d62f8", + "GlobalParams": "2900ce34e7482d60ca98ad3630c383345b086dd04824d30470ecfbd950ba94fa", + "ItemRegistry": "7621a2609ce9d5c95566eccc8e0f3e7ead3a7045062dbfa4dc6da5b38a987d36", + "KeepWhatsRaised": "db570918ea3d31983b595fec887c2019ebc7b22a6488dc56b0bfd08108d0a6e7", + "PaymentTreasury": "2d612dee28fb4290ec801b3b54787c5e2f917801d51e67150b8875f6c600c5e2", + "TreasuryFactory": "e895755be96f99b2385c47ff67b63d09b8384f7d7bd10814ecb63ede97be4f7d" + } +} diff --git a/packages/contracts/src/scripts/abi-canonical.ts b/packages/contracts/src/scripts/abi-canonical.ts new file mode 100644 index 00000000..900fe20e --- /dev/null +++ b/packages/contracts/src/scripts/abi-canonical.ts @@ -0,0 +1,77 @@ +/** + * @file scripts/abi-canonical.ts + * Canonical ABI representation shared by the generator and the drift checker. + * + * Canonical form = ABI entries sorted by (type, name, input arity, JSON) with + * alphabetically ordered object keys. Parameter order inside `inputs`/`outputs`/ + * `components` is semantic and is always preserved. Hashing the canonical JSON + * makes drift detection immune to formatter and compiler-ordering churn. + */ + +import { createHash } from "node:crypto"; + +/** A single ABI item as emitted by solc (function, event, error, constructor, ...). */ +export interface AbiItem { + type: string; + name?: string; + inputs?: unknown[]; + [key: string]: unknown; +} + +/** Recursively re-creates a JSON value with alphabetically sorted object keys (arrays preserved). */ +function sortKeysDeep(value: unknown): unknown { + if (Array.isArray(value)) { + return value.map(sortKeysDeep); + } + if (value !== null && typeof value === "object") { + const sorted: Record = {}; + for (const key of Object.keys(value as Record).sort()) { + sorted[key] = sortKeysDeep((value as Record)[key]); + } + return sorted; + } + return value; +} + +/** + * Returns the ABI in canonical form: entries sorted deterministically, object + * keys alphabetized. Parameter arrays keep their on-chain order. + * + * @param abi - ABI array as parsed from a compiled artifact + * @returns New canonicalized ABI array (input is not mutated) + */ +export function canonicalizeAbi(abi: readonly AbiItem[]): AbiItem[] { + const normalized = abi.map((item) => sortKeysDeep(item) as AbiItem); + return normalized.sort((a, b) => { + if (a.type !== b.type) return a.type < b.type ? -1 : 1; + const aName = a.name ?? ""; + const bName = b.name ?? ""; + if (aName !== bName) return aName < bName ? -1 : 1; + const aArity = a.inputs?.length ?? 0; + const bArity = b.inputs?.length ?? 0; + if (aArity !== bArity) return aArity - bArity; + const aJson = JSON.stringify(a); + const bJson = JSON.stringify(b); + return aJson < bJson ? -1 : aJson > bJson ? 1 : 0; + }); +} + +/** + * Serializes a canonicalized ABI to its canonical JSON string (stable across runs). + * + * @param abi - ABI array (canonicalize first via {@link canonicalizeAbi}) + * @returns Deterministic JSON string + */ +export function canonicalJson(abi: readonly AbiItem[]): string { + return JSON.stringify(canonicalizeAbi(abi)); +} + +/** + * Hashes an ABI's canonical JSON with SHA-256. + * + * @param abi - ABI array in any order/formatting + * @returns Lowercase hex digest + */ +export function abiSha256(abi: readonly AbiItem[]): string { + return createHash("sha256").update(canonicalJson(abi)).digest("hex"); +} diff --git a/packages/contracts/src/scripts/check-abis.ts b/packages/contracts/src/scripts/check-abis.ts index e9fd65e1..4d246974 100644 --- a/packages/contracts/src/scripts/check-abis.ts +++ b/packages/contracts/src/scripts/check-abis.ts @@ -1,20 +1,130 @@ /** * @file scripts/check-abis.ts - * CI check: detect ABI drift between compiled artifacts and SDK source. + * CI drift check for generated ABI modules. * - * TODO: Implement. Compare each contracts/{name}/abi.ts against current - * compiled artifacts; exit with non-zero if any ABI is stale or missing. - * Run in CI on every PR to prevent SDK/contract mismatch. + * Modes: + * manifest (default) - offline; re-hashes each committed `contracts/{dir}/abi.ts` + * export and compares against `abi-manifest.json`. Catches hand edits to + * generated files. Also verifies the README entities table. Runs on every PR. + * bundle (`--bundle `) - compares the working tree against a release ABI + * bundle via `generateAbis({ check: true })`. Used by the sync workflow and + * for local verification against a fresh `forge build`. + * + * CLI entry: src/scripts/cli.ts (pnpm abis:check [--bundle ]). This module + * is standalone dev tooling - not imported by SDK source. + */ + +import { existsSync } from "node:fs"; +import { readFile, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { pathToFileURL } from "node:url"; +import { abiSha256, type AbiItem } from "./abi-canonical"; +import { CONTRACT_MAP } from "./contract-map"; +import { MANIFEST_RELATIVE_PATH } from "./generate-abis"; + +/** One failed manifest comparison. */ +export interface AbiCheckFailure { + contractName: string; + problem: string; +} + +/** Committed manifest shape (written by generate-abis). */ +interface AbiManifest { + contractsRepo: string; + tag: string; + sha: string; + contracts: Record; +} + +/** + * Bootstrap only: seeds `abi-manifest.json` from the currently committed ABI + * modules, so the CI drift gate can start guarding hand-edits before the first + * release-bundle sync has run. The manifest records `tag: "sdk-baseline"` until + * the first real sync overwrites it with genuine release provenance. + * + * @param packageRoot - Package root (defaults to the working directory) + * @throws {Error} When a mapped ABI module is missing or malformed */ +export async function seedManifestFromCommitted(packageRoot = process.cwd()): Promise { + const contracts: Record = {}; + for (const entry of CONTRACT_MAP) { + const abiPath = join(packageRoot, "src", "contracts", entry.dir, "abi.ts"); + const module = (await import(pathToFileURL(abiPath).href)) as Record; + const abi = module[entry.exportName]; + if (!Array.isArray(abi)) { + throw new Error(`src/contracts/${entry.dir}/abi.ts does not export an array named ${entry.exportName}`); + } + contracts[entry.contractName] = abiSha256(abi as AbiItem[]); + } + const manifest: AbiManifest = { + contractsRepo: "oak-network/contracts", + tag: "sdk-baseline", + sha: "", + contracts: Object.fromEntries(Object.entries(contracts).sort(([a], [b]) => (a < b ? -1 : 1))), + }; + const manifestPath = join(packageRoot, MANIFEST_RELATIVE_PATH); + await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8"); +} /** - * Compares each `contracts/{name}/abi.ts` against current compiled Solidity artifacts. - * Exits with a non-zero code if any ABI is stale or missing. - * Intended to run in CI on every pull request. + * Manifest mode: verifies every committed ABI module hashes to its manifest entry. * - * @returns true if all ABIs are current, false if any are stale - * @throws {Error} Not yet implemented + * @param packageRoot - Package root (defaults to the working directory, i.e. this package when run via pnpm) + * @returns All failures (empty array = everything in sync) + * @throws {Error} When the manifest itself is missing or unreadable */ -export function checkAbis(): boolean { - throw new Error("TODO: check-abis not implemented"); +export async function checkAbisAgainstManifest(packageRoot = process.cwd()): Promise { + const manifestPath = join(packageRoot, MANIFEST_RELATIVE_PATH); + if (!existsSync(manifestPath)) { + throw new Error( + `${MANIFEST_RELATIVE_PATH} not found. Run \`pnpm abis:generate --bundle \` to seed it.`, + ); + } + const manifest = JSON.parse(await readFile(manifestPath, "utf8")) as AbiManifest; + const failures: AbiCheckFailure[] = []; + + for (const entry of CONTRACT_MAP) { + const expected = manifest.contracts[entry.contractName]; + if (!expected) { + failures.push({ + contractName: entry.contractName, + problem: "mapped in contract-map.ts but has no entry in abi-manifest.json", + }); + continue; + } + const abiPath = join(packageRoot, "src", "contracts", entry.dir, "abi.ts"); + if (!existsSync(abiPath)) { + failures.push({ contractName: entry.contractName, problem: `missing ${abiPath}` }); + continue; + } + const module = (await import(pathToFileURL(abiPath).href)) as Record; + const abi = module[entry.exportName]; + if (!Array.isArray(abi)) { + failures.push({ + contractName: entry.contractName, + problem: `src/contracts/${entry.dir}/abi.ts does not export an array named ${entry.exportName}`, + }); + continue; + } + const actual = abiSha256(abi as AbiItem[]); + if (actual !== expected) { + failures.push({ + contractName: entry.contractName, + problem: + `hash mismatch (manifest ${expected.slice(0, 12)}…, committed ${actual.slice(0, 12)}…). ` + + "Generated ABI modules must not be hand-edited - regenerate with pnpm abis:generate.", + }); + } + } + + for (const name of Object.keys(manifest.contracts)) { + if (!CONTRACT_MAP.some((entry) => entry.contractName === name)) { + failures.push({ + contractName: name, + problem: "present in abi-manifest.json but not in contract-map.ts", + }); + } + } + + return failures; } diff --git a/packages/contracts/src/scripts/check-registry-keys.ts b/packages/contracts/src/scripts/check-registry-keys.ts new file mode 100644 index 00000000..c3419c3f --- /dev/null +++ b/packages/contracts/src/scripts/check-registry-keys.ts @@ -0,0 +1,72 @@ +/** + * @file scripts/check-registry-keys.ts + * Drift check between `DataRegistryKeys.sol` (from a release bundle) and the + * hand-written `DATA_REGISTRY_KEYS` in `src/constants/registry.ts`. + * + * The SDK constant stays hand-written (it carries the `scopedToPlatform` helper + * and curated docs); this check guarantees it never silently diverges from the + * on-chain source of truth. Standalone dev tooling - not imported by SDK source. + */ + +import { keccak256, toHex } from "../lib"; +import { DATA_REGISTRY_KEYS } from "../constants/registry"; + +/** One discrepancy between DataRegistryKeys.sol and the SDK registry constants. */ +export interface RegistryDrift { + /** Constant name (Solidity side) or SDK key name. */ + key: string; + /** Human-readable description of the mismatch. */ + problem: string; +} + +const SOL_CONSTANT_PATTERN = /bytes32\s+public\s+constant\s+(\w+)\s*=\s*keccak256\("([^"]*)"\)/g; + +/** + * Parses `bytes32 public constant NAME = keccak256("...")` declarations and + * compares them against the SDK's `DATA_REGISTRY_KEYS`. + * + * @param solSource - Full text of DataRegistryKeys.sol + * @returns All drift findings (empty array = in sync) + * @throws {Error} When no constants can be parsed (source moved/renamed upstream) + */ +export function checkRegistryKeys(solSource: string): RegistryDrift[] { + const solKeys = new Map(); + for (const match of solSource.matchAll(SOL_CONSTANT_PATTERN)) { + solKeys.set(match[1], match[2]); + } + if (solKeys.size === 0) { + throw new Error( + "No bytes32 keccak256 constants found in DataRegistryKeys.sol - " + + "the file format may have changed upstream; update check-registry-keys.ts.", + ); + } + + const drift: RegistryDrift[] = []; + const sdkKeys = DATA_REGISTRY_KEYS as Record; + + for (const [name, rawString] of solKeys) { + const expectedHash = keccak256(toHex(rawString)); + if (!(name in sdkKeys)) { + drift.push({ + key: name, + problem: `present in DataRegistryKeys.sol (keccak256("${rawString}")) but missing from DATA_REGISTRY_KEYS`, + }); + } else if (sdkKeys[name] !== expectedHash) { + drift.push({ + key: name, + problem: `hash mismatch - SDK has ${sdkKeys[name]}, DataRegistryKeys.sol yields ${expectedHash}`, + }); + } + } + + for (const name of Object.keys(sdkKeys)) { + if (!solKeys.has(name)) { + drift.push({ + key: name, + problem: "present in DATA_REGISTRY_KEYS but no longer declared in DataRegistryKeys.sol", + }); + } + } + + return drift; +} diff --git a/packages/contracts/src/scripts/cli.ts b/packages/contracts/src/scripts/cli.ts new file mode 100644 index 00000000..93bbef23 --- /dev/null +++ b/packages/contracts/src/scripts/cli.ts @@ -0,0 +1,146 @@ +/** + * @file scripts/cli.ts + * CLI entry point for the ABI/docs dev tooling (run via tsx from package.json): + * + * pnpm abis:generate --bundle [--check] [--dry-run] [--report ] + * pnpm abis:check [--bundle ] + * pnpm readme:update [--check] + * tsx src/scripts/cli.ts seed (bootstrap only: manifest from committed ABIs) + * + * All argv parsing and process.exit handling lives here so the library modules + * stay importable from tests. Not imported by SDK source. + */ + +import { writeFile } from "node:fs/promises"; +import { parseArgs } from "node:util"; +import { checkAbisAgainstManifest, seedManifestFromCommitted } from "./check-abis"; +import { generateAbis, renderReportMarkdown } from "./generate-abis"; +import { updateReadme } from "./update-readme"; + +async function runGenerate(args: string[]): Promise { + const { values } = parseArgs({ + args, + options: { + bundle: { type: "string" }, + check: { type: "boolean", default: false }, + "dry-run": { type: "boolean", default: false }, + report: { type: "string" }, + }, + }); + if (!values.bundle) { + console.error( + "Usage: pnpm abis:generate --bundle [--check] [--dry-run] [--report ]", + ); + process.exit(2); + } + + const report = await generateAbis({ + bundleDir: values.bundle, + check: values.check, + dryRun: values["dry-run"], + }); + + for (const change of report.changes) { + console.log(`${change.kind.padEnd(9)} ${change.contractName} → src/contracts/${change.dir}/abi.ts`); + } + if (report.newContracts.length > 0) { + console.warn(`New unmapped contracts (not auto-added): ${report.newContracts.join(", ")}`); + } + for (const drift of report.registryDrift) { + console.warn(`Registry key drift: ${drift.key} - ${drift.problem}`); + } + + const readmeSynced = await updateReadme({ check: values.check || values["dry-run"] }); + if (!readmeSynced) { + console.warn("README entities table is out of sync with contract-map.ts."); + } + + if (values.report) { + await writeFile(values.report, renderReportMarkdown(report), "utf8"); + console.log(`Report written to ${values.report}`); + } + + const changed = report.changes.some((c) => c.kind !== "unchanged"); + if (values.check && (changed || !readmeSynced || report.registryDrift.length > 0)) { + process.exit(1); + } +} + +async function runCheck(args: string[]): Promise { + const { values } = parseArgs({ args, options: { bundle: { type: "string" } } }); + + if (values.bundle) { + const report = await generateAbis({ bundleDir: values.bundle, check: true }); + const stale = report.changes.filter((c) => c.kind !== "unchanged"); + for (const change of stale) { + console.error(`stale: ${change.contractName} (src/contracts/${change.dir}/abi.ts is ${change.kind})`); + } + for (const drift of report.registryDrift) { + console.error(`registry drift: ${drift.key} - ${drift.problem}`); + } + if (report.newContracts.length > 0) { + console.warn(`New unmapped contracts (informational): ${report.newContracts.join(", ")}`); + } + const readmeSynced = await updateReadme({ check: true }); + if (!readmeSynced) { + console.error("README entities table is out of sync with contract-map.ts. Run: pnpm readme:update"); + } + if (stale.length > 0 || report.registryDrift.length > 0 || !readmeSynced) { + console.error( + "ABI drift detected against the release bundle. Run: pnpm abis:generate --bundle ", + ); + process.exit(1); + } + console.log("ABIs are in sync with the release bundle."); + return; + } + + const failures = await checkAbisAgainstManifest(); + for (const failure of failures) { + console.error(`${failure.contractName}: ${failure.problem}`); + } + const readmeOk = await updateReadme({ check: true }); + if (!readmeOk) { + console.error("README entities table is out of sync with contract-map.ts. Run: pnpm readme:update"); + } + if (failures.length > 0 || !readmeOk) { + process.exit(1); + } + console.log("Committed ABI modules match abi-manifest.json; README table is in sync."); +} + +async function runReadme(args: string[]): Promise { + const { values } = parseArgs({ args, options: { check: { type: "boolean", default: false } } }); + const ok = await updateReadme({ check: values.check }); + if (!ok) { + console.error( + "README entities table is out of sync with src/scripts/contract-map.ts. Run: pnpm readme:update", + ); + process.exit(1); + } + console.log(values.check ? "README entities table is in sync." : "README entities table updated."); +} + +async function main(): Promise { + const [command, ...rest] = process.argv.slice(2); + switch (command) { + case "generate": + return runGenerate(rest); + case "check": + return runCheck(rest); + case "readme": + return runReadme(rest); + case "seed": + await seedManifestFromCommitted(); + console.log("abi-manifest.json seeded from committed ABI modules (tag: sdk-baseline)."); + return; + default: + console.error(`Unknown command "${command ?? ""}". Expected: generate | check | readme`); + process.exit(2); + } +} + +main().catch((error: unknown) => { + console.error(error instanceof Error ? error.message : error); + process.exit(1); +}); diff --git a/packages/contracts/src/scripts/contract-map.ts b/packages/contracts/src/scripts/contract-map.ts new file mode 100644 index 00000000..570f458c --- /dev/null +++ b/packages/contracts/src/scripts/contract-map.ts @@ -0,0 +1,110 @@ +/** + * @file scripts/contract-map.ts + * Single source of truth mapping oak-network/contracts contracts to SDK ABI modules. + * + * The ABI generator, the drift checker, and the README table renderer all consume + * this map. To onboard a new contract into the SDK: add an entry here, run + * `pnpm abis:generate` against a release bundle, then build the entity layer + * (reads/writes/simulate/events) and its tests. + */ + +/** Mapping between one Solidity contract and its generated SDK ABI module. */ +export interface ContractMapEntry { + /** Solidity contract name; matches `abis/{contractName}.json` in a release bundle. */ + contractName: string; + /** Source path in oak-network/contracts (documentation + rename detection). */ + sourcePath: string; + /** SDK directory under `src/contracts/` that holds the generated `abi.ts`. */ + dir: string; + /** Exported const name in the generated `abi.ts`. */ + exportName: string; + /** Client factory method, rendered in the README entities table. */ + factoryMethod: string; + /** One-line description, rendered in the README entities table. */ + description: string; + /** Docs page, rendered in the README entities table. */ + docsUrl: string; +} + +/** All contracts whose ABIs are vendored into the SDK, in README table order. */ +export const CONTRACT_MAP: readonly ContractMapEntry[] = [ + { + contractName: "GlobalParams", + sourcePath: "src/GlobalParams.sol", + dir: "global-params", + exportName: "GLOBAL_PARAMS_ABI", + factoryMethod: "oak.globalParams(addr)", + description: "Protocol-wide config: platforms, fees, currencies, registry", + docsUrl: "https://oaknetwork.org/docs/contracts-sdk/global-params", + }, + { + contractName: "CampaignInfoFactory", + sourcePath: "src/CampaignInfoFactory.sol", + dir: "campaign-info-factory", + exportName: "CAMPAIGN_INFO_FACTORY_ABI", + factoryMethod: "oak.campaignInfoFactory(addr)", + description: "Deploys new CampaignInfo contracts", + docsUrl: "https://oaknetwork.org/docs/contracts-sdk/campaign-info-factory", + }, + { + contractName: "CampaignInfo", + sourcePath: "src/CampaignInfo.sol", + dir: "campaign-info", + exportName: "CAMPAIGN_INFO_ABI", + factoryMethod: "oak.campaignInfo(addr)", + description: "Per-campaign state: deadlines, goals, funding progress", + docsUrl: "https://oaknetwork.org/docs/contracts-sdk/campaign-info", + }, + { + contractName: "TreasuryFactory", + sourcePath: "src/TreasuryFactory.sol", + dir: "treasury-factory", + exportName: "TREASURY_FACTORY_ABI", + factoryMethod: "oak.treasuryFactory(addr)", + description: "Deploys and manages treasury implementations", + docsUrl: "https://oaknetwork.org/docs/contracts-sdk/treasury-factory", + }, + { + contractName: "PaymentTreasury", + sourcePath: "src/treasuries/PaymentTreasury.sol", + dir: "payment-treasury", + exportName: "PAYMENT_TREASURY_ABI", + factoryMethod: "oak.paymentTreasury(addr)", + description: "Fiat-style payments, confirmations, refunds, withdrawals", + docsUrl: "https://oaknetwork.org/docs/contracts-sdk/payment-treasury", + }, + { + contractName: "AllOrNothing", + sourcePath: "src/treasuries/AllOrNothing.sol", + dir: "all-or-nothing", + exportName: "ALL_OR_NOTHING_ABI", + factoryMethod: "oak.allOrNothingTreasury(addr)", + description: "Crowdfunding treasury - funds released only if goal is met", + docsUrl: "https://oaknetwork.org/docs/contracts-sdk/all-or-nothing", + }, + { + contractName: "KeepWhatsRaised", + sourcePath: "src/treasuries/KeepWhatsRaised.sol", + dir: "keep-whats-raised", + exportName: "KEEP_WHATS_RAISED_ABI", + factoryMethod: "oak.keepWhatsRaisedTreasury(addr)", + description: "Crowdfunding treasury - creator keeps all funds raised", + docsUrl: "https://oaknetwork.org/docs/contracts-sdk/keep-whats-raised", + }, + { + contractName: "ItemRegistry", + sourcePath: "src/utils/ItemRegistry.sol", + dir: "item-registry", + exportName: "ITEM_REGISTRY_ABI", + factoryMethod: "oak.itemRegistry(addr)", + description: "Manages purchasable items with metadata", + docsUrl: "https://oaknetwork.org/docs/contracts-sdk/item-registry", + }, +]; + +/** + * Deployable contracts that intentionally have no SDK ABI module of their own. + * TimeConstrainedPaymentTreasury shares the PaymentTreasury ABI surface - + * `oak.paymentTreasury(addr)` handles both variants (see README). + */ +export const KNOWN_UNMAPPED: readonly string[] = ["TimeConstrainedPaymentTreasury"]; diff --git a/packages/contracts/src/scripts/generate-abis.ts b/packages/contracts/src/scripts/generate-abis.ts index 1cc974bd..48f7b02f 100644 --- a/packages/contracts/src/scripts/generate-abis.ts +++ b/packages/contracts/src/scripts/generate-abis.ts @@ -1,22 +1,287 @@ /** * @file scripts/generate-abis.ts - * Extracts ABIs from compiled Solidity artifacts into contracts/{name}/abi.ts. + * Regenerates `src/contracts/{dir}/abi.ts` modules from an oak-network/contracts + * release ABI bundle, plus the `abi-manifest.json` used by CI drift checks. * - * TODO: Implement. Read Hardhat/Foundry artifacts (e.g. out/ or artifacts/) - * and write each contract's ABI to the corresponding contracts/{name}/abi.ts - * as a typed const array. Run after contract recompile to keep SDK in sync. - * This script is standalone — not imported by SDK source. + * Bundle layout (produced by contracts repo `.github/scripts/build-abi-bundle.sh`): + * abis/{ContractName}.json - { contractName, sourcePath, abi } + * sources/DataRegistryKeys.sol + * metadata.json - { tag, sha, solcVersion, foundryVersion, + * contracts[], deployableContracts[] } + * + * CLI entry: src/scripts/cli.ts (pnpm abis:generate). This module is standalone + * dev tooling - not imported by SDK source. + */ + +import { existsSync } from "node:fs"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { dirname, join, resolve } from "node:path"; +import { abiSha256, canonicalizeAbi, type AbiItem } from "./abi-canonical"; +import { CONTRACT_MAP, KNOWN_UNMAPPED, type ContractMapEntry } from "./contract-map"; +import { checkRegistryKeys, type RegistryDrift } from "./check-registry-keys"; + +/** `metadata.json` shape inside a release ABI bundle. */ +export interface BundleMetadata { + tag: string; + sha: string; + solcVersion: string; + foundryVersion: string; + /** All src/ contracts whose ABIs are included in the bundle. */ + contracts: string[]; + /** Subset with deployable bytecode - used for new-contract detection. */ + deployableContracts: string[]; +} + +/** One `abis/{ContractName}.json` file inside a release ABI bundle. */ +interface BundleAbiFile { + contractName: string; + sourcePath: string; + abi: AbiItem[]; +} + +/** How one mapped contract's generated module changed relative to the working tree. */ +export interface ContractChange { + contractName: string; + dir: string; + kind: "unchanged" | "updated" | "created"; +} + +/** Outcome of a generate/check run, consumed by the CLI, CI, and the sync PR body. */ +export interface GenerateReport { + changes: ContractChange[]; + /** Deployable contracts in the bundle that have no CONTRACT_MAP / KNOWN_UNMAPPED entry. */ + newContracts: string[]; + registryDrift: RegistryDrift[]; + metadata: BundleMetadata; +} + +/** Committed manifest shape. */ +interface AbiManifestFile { + contractsRepo: string; + tag: string; + sha: string; + contracts: Record; +} + +/** Options for {@link generateAbis}. */ +export interface GenerateOptions { + /** Extracted release-bundle root (contains `abis/`, `sources/`, `metadata.json`). */ + bundleDir: string; + /** Compare only; never write. Used by `abis:check --bundle`. */ + check?: boolean; + /** Log planned changes; never write. */ + dryRun?: boolean; + /** Package root override (defaults to the working directory, i.e. this package when run via pnpm). */ + packageRoot?: string; +} + +/** Path of the generated manifest, relative to the package root. */ +export const MANIFEST_RELATIVE_PATH = join("src", "contracts", "abi-manifest.json"); + +async function readJson(path: string): Promise { + return JSON.parse(await readFile(path, "utf8")) as T; +} + +function assertBundleMetadata(value: unknown, bundleDir: string): asserts value is BundleMetadata { + const meta = value as Partial | null; + const stringFields: Array = ["tag", "sha", "solcVersion", "foundryVersion"]; + const arrayFields: Array = ["contracts", "deployableContracts"]; + if ( + meta === null || + typeof meta !== "object" || + stringFields.some((f) => typeof meta[f] !== "string") || + arrayFields.some((f) => !Array.isArray(meta[f])) + ) { + throw new Error(`Invalid or incomplete metadata.json in bundle at ${bundleDir}`); + } +} + +const PRINT_WIDTH = 100; +const INDENT = " "; +const IDENTIFIER_KEY = /^[A-Za-z_$][A-Za-z0-9_$]*$/; + +/** + * Prints a JSON value as a TypeScript literal in the repo's prettier style: + * unquoted identifier keys, double-quoted strings, objects/arrays inlined when + * they fit within the print width, expanded with trailing commas otherwise. + * Deliberately dependency-free - a formatter version bump must never churn + * generated files. */ +export function printTsLiteral(value: unknown, indentLevel = 0): string { + const pad = INDENT.repeat(indentLevel); + const childPad = INDENT.repeat(indentLevel + 1); + + if (Array.isArray(value)) { + if (value.length === 0) return "[]"; + const inlineItems = value.map((item) => printTsLiteral(item, 0)); + const inline = `[${inlineItems.join(", ")}]`; + if (!inline.includes("\n") && pad.length + inline.length <= PRINT_WIDTH) return inline; + const items = value.map((item) => `${childPad}${printTsLiteral(item, indentLevel + 1)},`); + return `[\n${items.join("\n")}\n${pad}]`; + } + if (value !== null && typeof value === "object") { + const entries = Object.entries(value as Record); + if (entries.length === 0) return "{}"; + const renderKey = (key: string) => (IDENTIFIER_KEY.test(key) ? key : JSON.stringify(key)); + const inlineEntries = entries.map(([k, v]) => `${renderKey(k)}: ${printTsLiteral(v, 0)}`); + const inline = `{ ${inlineEntries.join(", ")} }`; + if (!inline.includes("\n") && pad.length + inline.length <= PRINT_WIDTH) return inline; + const lines = entries.map(([k, v]) => `${childPad}${renderKey(k)}: ${printTsLiteral(v, indentLevel + 1)},`); + return `{\n${lines.join("\n")}\n${pad}}`; + } + return JSON.stringify(value); +} + +/** + * Renders one generated `abi.ts` module (deterministic, prettier-style). + * + * @param entry - Contract map entry being rendered + * @param abi - Canonicalized ABI + * @param metadata - Bundle metadata (tag/sha recorded in the header) + * @returns Formatted TypeScript source + */ +export function renderAbiModule( + entry: ContractMapEntry, + abi: readonly AbiItem[], + metadata: Pick, +): string { + const shortSha = metadata.sha.slice(0, 7); + return [ + `// Generated from oak-network/contracts ${metadata.tag} (${shortSha}) - ${entry.sourcePath}.`, + `// Do not edit by hand. Regenerate with: pnpm abis:generate --bundle `, + `export const ${entry.exportName} = ${printTsLiteral([...abi])} as const;`, + "", + ].join("\n"); +} + +/** + * Regenerates all mapped ABI modules and the manifest from a release bundle. + * + * @param opts - See {@link GenerateOptions} + * @returns Report of per-contract changes, unmapped/missing contracts, registry drift + * @throws {Error} When the bundle is malformed or a mapped contract is missing from it + */ +export async function generateAbis(opts: GenerateOptions): Promise { + const bundleDir = resolve(opts.bundleDir); + const packageRoot = opts.packageRoot ?? process.cwd(); + const write = !opts.check && !opts.dryRun; + + const metadataPath = join(bundleDir, "metadata.json"); + if (!existsSync(metadataPath)) { + throw new Error(`Bundle metadata not found: ${metadataPath}`); + } + const metadata = await readJson(metadataPath); + assertBundleMetadata(metadata, bundleDir); + + const manifestPath = join(packageRoot, MANIFEST_RELATIVE_PATH); + const previousManifest = existsSync(manifestPath) + ? await readJson(manifestPath) + : null; + + const changes: ContractChange[] = []; + const missingContracts: string[] = []; + const manifestContracts: Record = {}; + + for (const entry of CONTRACT_MAP) { + const abiPath = join(bundleDir, "abis", `${entry.contractName}.json`); + if (!existsSync(abiPath)) { + missingContracts.push(entry.contractName); + continue; + } + const abiFile = await readJson(abiPath); + if (!Array.isArray(abiFile.abi)) { + throw new Error(`Bundle ABI file has no "abi" array: ${abiPath}`); + } + const canonical = canonicalizeAbi(abiFile.abi); + const hash = abiSha256(canonical); + manifestContracts[entry.contractName] = hash; + + // Content comparison is by canonical ABI hash, not file text: a release + // whose ABIs are byte-identical to the last sync must be a no-op, so the + // generated header (which embeds the tag) keeps the provenance of the + // last content change instead of churning on every release. + const targetPath = join(packageRoot, "src", "contracts", entry.dir, "abi.ts"); + const kind: ContractChange["kind"] = !existsSync(targetPath) + ? "created" + : previousManifest?.contracts?.[entry.contractName] === hash + ? "unchanged" + : "updated"; + changes.push({ contractName: entry.contractName, dir: entry.dir, kind }); + + if (write && kind !== "unchanged") { + await mkdir(dirname(targetPath), { recursive: true }); + await writeFile(targetPath, renderAbiModule(entry, canonical, metadata), "utf8"); + } + } + + if (missingContracts.length > 0) { + throw new Error( + `Mapped contracts missing from bundle: ${missingContracts.join(", ")}. ` + + `If a contract was renamed or removed upstream, update src/scripts/contract-map.ts.`, + ); + } + + const mapped = new Set(CONTRACT_MAP.map((e) => e.contractName)); + const knownUnmapped = new Set(KNOWN_UNMAPPED); + const newContracts = metadata.deployableContracts.filter( + (name) => !mapped.has(name) && !knownUnmapped.has(name), + ); + + const registrySolPath = join(bundleDir, "sources", "DataRegistryKeys.sol"); + const registryDrift = existsSync(registrySolPath) + ? checkRegistryKeys(await readFile(registrySolPath, "utf8")) + : []; + + const manifest: AbiManifestFile = { + contractsRepo: "oak-network/contracts", + tag: metadata.tag, + sha: metadata.sha, + contracts: Object.fromEntries(Object.entries(manifestContracts).sort(([a], [b]) => (a < b ? -1 : 1))), + }; + const anyContentChange = changes.some((c) => c.kind !== "unchanged"); + if (write && (anyContentChange || previousManifest === null)) { + await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8"); + } + + return { changes, newContracts, registryDrift, metadata }; +} /** - * Reads compiled Solidity artifacts and writes each contract's ABI into - * the corresponding `contracts/{name}/abi.ts` as a typed const array. - * Run after a contract recompile to keep SDK sources in sync. + * Renders a report as a markdown fragment for the sync PR body. * - * @returns void - * @throws {Error} Not yet implemented - * @internal + * @param report - Result of {@link generateAbis} + * @returns Markdown text */ -export function generateAbis(): void { - throw new Error("TODO: generate-abis not implemented"); +export function renderReportMarkdown(report: GenerateReport): string { + const lines: string[] = []; + lines.push("### ABI changes", ""); + lines.push("| Contract | Module | Status |", "| --- | --- | --- |"); + for (const change of report.changes) { + lines.push(`| ${change.contractName} | \`src/contracts/${change.dir}/abi.ts\` | ${change.kind} |`); + } + if (report.newContracts.length > 0) { + lines.push( + "", + "### ⚠️ New contracts detected (not auto-added)", + "", + ...report.newContracts.map((name) => `- \`${name}\``), + "", + "New deployable contracts need an SDK entity (reads/writes/simulate/events), tests,", + "and a `src/scripts/contract-map.ts` entry in a follow-up PR before their ABI is vendored.", + ); + } + if (report.registryDrift.length > 0) { + lines.push( + "", + "### ⚠️ Data registry key drift", + "", + "| Key | Problem |", + "| --- | --- |", + ...report.registryDrift.map((d) => `| \`${d.key}\` | ${d.problem} |`), + "", + "Update `src/constants/registry.ts` to match `DataRegistryKeys.sol`.", + ); + } + lines.push(""); + return lines.join("\n"); } diff --git a/packages/contracts/src/scripts/update-readme.ts b/packages/contracts/src/scripts/update-readme.ts new file mode 100644 index 00000000..033f3af4 --- /dev/null +++ b/packages/contracts/src/scripts/update-readme.ts @@ -0,0 +1,81 @@ +/** + * @file scripts/update-readme.ts + * Regenerates the "Available Entities" table in the package README from + * `CONTRACT_MAP`, between AUTOGEN markers. Hand-written prose outside the + * markers is never touched. Standalone dev tooling - not imported by SDK source. + * + * CLI entry: src/scripts/cli.ts (pnpm readme:update [--check]). + */ + +import { readFile, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { CONTRACT_MAP } from "./contract-map"; + +export const BEGIN_MARKER = + ""; +export const END_MARKER = ""; + +/** + * Renders the entities table (markers included) from the contract map. + * + * @returns Markdown block replacing everything between the markers + */ +export function renderEntitiesTable(): string { + const rows = CONTRACT_MAP.map( + (entry) => + `| **${entry.contractName}** | \`${entry.factoryMethod}\` | ${entry.description} | [Docs](${entry.docsUrl}) |`, + ); + return [ + BEGIN_MARKER, + "", + "| Entity | Factory method | Description | Docs |", + "| --- | --- | --- | --- |", + ...rows, + "", + END_MARKER, + ].join("\n"); +} + +/** + * Replaces the marker-delimited table inside a README source string. + * + * @param readme - Full README text + * @returns Updated README text + * @throws {Error} When markers are missing, duplicated, or out of order + */ +export function replaceEntitiesTable(readme: string): string { + const beginCount = readme.split(BEGIN_MARKER).length - 1; + const endCount = readme.split(END_MARKER).length - 1; + if (beginCount !== 1 || endCount !== 1) { + throw new Error( + `Expected exactly one AUTOGEN:entities-table marker pair in README.md ` + + `(found ${beginCount} begin / ${endCount} end). Restore the markers before regenerating.`, + ); + } + const begin = readme.indexOf(BEGIN_MARKER); + const end = readme.indexOf(END_MARKER); + if (end < begin) { + throw new Error("AUTOGEN:entities-table markers are out of order in README.md."); + } + return readme.slice(0, begin) + renderEntitiesTable() + readme.slice(end + END_MARKER.length); +} + +/** + * Updates (or verifies) the package README entities table. + * + * @param opts - `check`: verify only, write nothing; `readmePath`: override for tests + * @returns true when the README is (already) in sync, false when check mode found drift + */ +export async function updateReadme(opts: { check?: boolean; readmePath?: string } = {}): Promise { + const readmePath = opts.readmePath ?? join(process.cwd(), "README.md"); + const current = await readFile(readmePath, "utf8"); + const updated = replaceEntitiesTable(current); + if (current === updated) { + return true; + } + if (opts.check) { + return false; + } + await writeFile(readmePath, updated, "utf8"); + return true; +} diff --git a/packages/payments/CHANGELOG.md b/packages/payments/CHANGELOG.md index 8509c38b..dd92c3d3 100644 --- a/packages/payments/CHANGELOG.md +++ b/packages/payments/CHANGELOG.md @@ -1,5 +1,38 @@ # @oaknetwork/payments-sdk +## 1.5.0 + +### Minor Changes + +### Minor Changes + +- updated sandbox url and provider registration types + +## 1.4.0 + +### Minor Changes + +### Minor Changes + +- updated new cs domain + +## 1.3.0 + +### Minor Changes + +### Minor Changes + +- Remove deprecated field values from payment method and add new webhook field +- Add `createCountryService` for listing countries with KYC support status via the public `GET /api/v1/countries` endpoint + +## 1.2.0 + +### Minor Changes + +### Minor Changes + +- Update payment sdk with the recent changes from Crowdsplit + ## 1.1.0 ### Minor Changes diff --git a/packages/payments/jest.config.js b/packages/payments/jest.config.js index d79c667f..180866cb 100644 --- a/packages/payments/jest.config.js +++ b/packages/payments/jest.config.js @@ -15,6 +15,7 @@ module.exports = { "src/**/*.{ts,tsx}", "!src/**/*.d.ts", "!src/**/index.ts", + "!src/generated/**", ], coverageThreshold: { global: { diff --git a/packages/payments/openapi/.sync-meta.json b/packages/payments/openapi/.sync-meta.json new file mode 100644 index 00000000..4146ba80 --- /dev/null +++ b/packages/payments/openapi/.sync-meta.json @@ -0,0 +1,86 @@ +{ + "tag": "v2.1.1", + "commit": "30e494af1838dc8cd5741da0186bc7842983b117", + "previousTag": null, + "specSha256": "b2804720af57f4ad481b4579475573f13c49caf079763e560de6d55709efb3cd", + "redoclyVersion": "1.34.11", + "openapiTypescriptVersion": "7.13.0", + "endpoints": [ + "DELETE /api/v1/customers/{customer_id}/payment_methods/{id}", + "DELETE /api/v1/files/{file_id}", + "DELETE /api/v1/merchant/webhooks/{id}", + "DELETE /api/v1/subscription/plans/{planId}", + "GET /api/auth/reset-password/token-verify", + "GET /api/auth/signup/verify-email", + "GET /api/health", + "GET /api/v1/countries", + "GET /api/v1/customers", + "GET /api/v1/customers/{customer_id}/balances", + "GET /api/v1/customers/{customer_id}/files", + "GET /api/v1/customers/{customer_id}/payment_methods", + "GET /api/v1/customers/{customer_id}/payment_methods/{id}", + "GET /api/v1/customers/{id}", + "GET /api/v1/disputes", + "GET /api/v1/files", + "GET /api/v1/files/{file_id}", + "GET /api/v1/merchant/key/create", + "GET /api/v1/merchant/webhooks", + "GET /api/v1/merchant/webhooks/notifications", + "GET /api/v1/merchant/webhooks/notifications/{id}", + "GET /api/v1/merchant/webhooks/{id}", + "GET /api/v1/provider-registration/schema", + "GET /api/v1/provider-registration/{customer_id}/status", + "GET /api/v1/subscription/list", + "GET /api/v1/subscription/plans", + "GET /api/v1/subscription/plans/{planId}", + "GET /api/v1/subscription/{subscriptionId}", + "GET /api/v1/transactions", + "GET /api/v1/transactions/{id}", + "GET /api/v1/wallets/{customer_id}/balance", + "PATCH /api/v1/merchant/webhooks/{id}/toggle", + "PATCH /api/v1/subscription/plans/{planId}", + "PATCH /api/v1/subscription/plans/{planId}/publish", + "PATCH /api/v1/subscription/subscriptions/{subscriptionId}/cancel", + "PATCH /api/v1/transactions/{id}/settle", + "POST /api/auth/reset-password", + "POST /api/auth/signin", + "POST /api/auth/signup", + "POST /api/auth/signup/verify-email/resend", + "POST /api/auth/token/refresh", + "POST /api/auth/token/validate", + "POST /api/auth/update-password", + "POST /api/v1/buy", + "POST /api/v1/customers", + "POST /api/v1/customers/{customer_id}/files", + "POST /api/v1/customers/{customer_id}/payment_methods", + "POST /api/v1/customers/{customer_id}/platforms", + "POST /api/v1/customers/{id}/sync", + "POST /api/v1/files", + "POST /api/v1/merchant", + "POST /api/v1/merchant/token/grant", + "POST /api/v1/merchant/token/refresh", + "POST /api/v1/merchant/util/transfer-date", + "POST /api/v1/merchant/webhooks", + "POST /api/v1/outbound_payments", + "POST /api/v1/payments", + "POST /api/v1/payments/{id}/cancel", + "POST /api/v1/payments/{id}/capture", + "POST /api/v1/payments/{id}/confirm", + "POST /api/v1/payments/{id}/refund", + "POST /api/v1/provider-registration/{customer_id}/submit", + "POST /api/v1/providers/{provider}/proxy", + "POST /api/v1/subscription/plans", + "POST /api/v1/subscription/subscribe", + "POST /api/v1/subscription/{subscriptionId}/payment", + "POST /api/v1/taxes/calculate", + "POST /api/v1/transfer", + "POST /api/v1/transfer/webhook", + "POST /api/v1/wallets/trades/buy", + "PUT /api/v1/customers/{customer_id}/payment_methods/{id}", + "PUT /api/v1/customers/{id}", + "PUT /api/v1/disputes/{dispute_id}/close", + "PUT /api/v1/disputes/{dispute_id}/evidence", + "PUT /api/v1/disputes/{dispute_id}/submit", + "PUT /api/v1/merchant/webhooks/{id}" + ] +} diff --git a/packages/payments/openapi/openapi.bundled.yaml b/packages/payments/openapi/openapi.bundled.yaml new file mode 100644 index 00000000..e8e564d0 --- /dev/null +++ b/packages/payments/openapi/openapi.bundled.yaml @@ -0,0 +1,7416 @@ +openapi: 3.1.0 +info: + title: CrowdSplit API + version: 1.0.0 + description: | + CrowdSplit is a payment-orchestration API that routes payment, + payout, transfer, buy/sell, KYC, and wallet operations across + multiple upstream providers (Stripe, Bridge, and others) behind a + single merchant-facing contract. + + ## Authentication + + Three auth schemes are in use: + + - **`bearerAuth`** — a JWT issued via `POST /api/v1/merchant/token/grant` + using your `client_id` + `client_secret`. Pass as + `Authorization: Bearer `. This is the default for every + merchant-facing operation unless an operation documents otherwise. + - **`accessTokenAuth`** — user-scoped JWT (obtained via + `POST /api/auth/signin`) passed in the `x-access-token` header. + Used for admin/internal operations. + - **`refAppKeyAuth`** — subscription-service shared key passed in + the `Ref-App-Key` header, combined with `Payment-Type: recurring_payment`. + Used only for recurring-payment flows. + + ## Webhooks + + CrowdSplit delivers webhooks as `POST` requests to merchant-registered + URLs. Every delivery carries the envelope + `{ id, event, category, data }` and two HTTP headers: + + - `CrowdSplit-Signature` — HMAC-SHA256 over the raw body, formatted + as `t={unix-ts},v1={hex-hmac}`. Verify with the secret returned at + webhook registration. + - `CrowdSplit-Timestamp` — seconds since epoch at dispatch. Reject + if the skew exceeds 5 minutes. + + Use the envelope's `id` for idempotency — the same `id` is used for + all retry attempts of a given notification. + + Failed deliveries are retried on a linear schedule controlled by the + `WEBHOOK_MAXIMUM_RETRY_COUNT` and `WEBHOOK_RETRY_INTERVAL` deployment + settings. The *n*-th retry is scheduled + `n × WEBHOOK_RETRY_INTERVAL × 60` seconds after the previous failed + attempt, and retries stop once `n × WEBHOOK_RETRY_INTERVAL` exceeds + `WEBHOOK_MAXIMUM_RETRY_COUNT`. Total delivery attempts equal + `floor(WEBHOOK_MAXIMUM_RETRY_COUNT / WEBHOOK_RETRY_INTERVAL) + 1`. + + With the reference configuration + (`WEBHOOK_MAXIMUM_RETRY_COUNT=15`, `WEBHOOK_RETRY_INTERVAL=5`) this + yields **4 total delivery attempts**, with retries scheduled 5, 10, + and 15 minutes after each preceding failure. After the final attempt + the notification is left un-acknowledged and no further attempts are + made. + + ## Pagination + + List endpoints accept `limit` and `offset` query parameters. The + response envelope is `{ count, _list }`. Default and + maximum `limit` values vary per resource; see each endpoint. + + ## Errors + + Every 4xx/5xx response uses the shared `ApiErrorResponse` schema: + `{ msg, data: null, provider_message? }`. `provider_message` is + present only when the failure originated from an upstream provider, + and forwards the provider's own error text verbatim. + + ## Environments + + The `servers` list below describes production, sandbox, and local + environments. Override the server URL in your client or via the + generated Postman / Bruno collections. + contact: + name: CrowdSplit Engineering + url: https://github.com/oak-network/crowdsplit + license: + name: ISC +servers: + - url: https://api.crowdsplit.com + description: Production + - url: https://sandbox-api.crowdsplit.com + description: Sandbox + - url: '{baseUrl}' + description: Local development / self-hosted + variables: + baseUrl: + default: http://localhost:3000 + description: Base URL of a locally running CrowdSplit instance +security: + - bearerAuth: [] +tags: + - name: Auth + description: | + Sign up, sign in, token refresh, password reset, and email + verification. Obtain user-scoped `accessTokenAuth` JWTs here. + - name: Merchants + description: | + Merchant profile, API-key management, merchant-token grant/refresh, + and merchant-webhook registration/listing/toggling. + - name: Customers + description: | + Create, update, list, and sync end-customers (subjects). + Covers customer-level files and balances. + - name: Payment Methods + description: | + Add, list, retrieve, and archive payment methods on a customer + (card, PIX, bank account, wallet, etc.). + - name: Provider Registration + description: | + KYC / provider-registration flow for a customer on each upstream + provider, plus fetching the field schema and polling status. + - name: Payments + description: | + Create payments, confirm / capture / cancel / refund. Supports + card, PIX, and installment payments across providers. + - name: Payouts + description: Create outbound fiat payments to customer-owned accounts. + - name: Transfers + description: Move value between internal wallets. + - name: Buy / Sell + description: Stablecoin / fiat buy and sell flows. + - name: Wallets + description: Read wallet balances and trigger wallet trades. + - name: Transactions + description: Read transaction history, single transactions, and settle. + - name: Disputes + description: | + Manage provider-originated disputes — upload evidence, submit, + and close. + - name: Subscriptions + description: | + Subscription plans and subscriber lifecycle, including recurring + payment initiation (uses `refAppKeyAuth`). + - name: Tax + description: Calculate taxes for a given transaction shape. + - name: Files + description: Upload, list, retrieve, and delete merchant/customer files. + - name: Generic + description: | + Proxy endpoint that forwards provider-specific calls whose shape + varies by upstream provider. + - name: System + description: Health check and service-metadata endpoints. + - name: Webhooks + description: | + Manage the webhook URLs registered on a merchant (create, update, + delete, toggle, and list). + - name: Webhook Events + description: | + Reference list of every webhook event CrowdSplit can deliver to a + merchant URL. See the root-level `Webhooks` section above for the + envelope shape, delivery headers, retry policy, and signing scheme. +paths: + /api/auth/signup: + post: + operationId: signup + summary: User sign up + description: Register a new user account with email and password. A verification email is sent upon successful registration. + tags: + - Auth + security: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - email + - password + properties: + email: + type: string + format: email + description: User's email address + example: user@example.com + password: + type: string + description: User's password + example: SecurePass123! + responses: + '200': + description: User created successfully and verification email sent + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + msg: User Created Successfully!, Created email verification token!, Verification link emailed to user!, User Created Successfully! + data: null + '400': + description: User already exists or email delivery failed + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '422': + $ref: '#/components/responses/ValidationError' + /api/auth/signup/verify-email: + get: + operationId: verifySignupEmail + summary: Verify email address + description: Verifies the user's email address using a token sent during signup. + tags: + - Auth + security: [] + parameters: + - name: token + in: query + required: true + description: JWT verification token sent to the user's email + schema: + type: string + responses: + '200': + description: Email verification successful + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + msg: Email Verification Successful! + data: null + '400': + description: Invalid or expired token, or email already verified + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + /api/auth/signup/verify-email/resend: + post: + operationId: resendVerificationEmail + summary: Resend verification email + description: Resends the email verification link to the specified email address. + tags: + - Auth + security: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - email + properties: + email: + type: string + format: email + description: Email address to resend verification to + example: user@example.com + responses: + '200': + description: Verification email resent successfully + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + msg: Email Verification Token Updated!, Sent Verification Email! + data: null + '400': + description: Email delivery failed or token error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + /api/auth/signin: + post: + operationId: signin + summary: Sign in + description: Authenticate a user with email, password, and role. Returns JWT access and refresh tokens. + tags: + - Auth + security: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - email + - password + - role + properties: + email: + type: string + format: email + description: User's email address + example: user@example.com + password: + type: string + description: User's password + example: SecurePass123! + role: + type: string + description: Role to sign in as + enum: + - ROLE_ADMIN + - ROLE_PLATFORM_ADMIN + - ROLE_PLATFORM_USER + example: ROLE_PLATFORM_ADMIN + responses: + '200': + description: Sign in successful + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + msg: + example: Sign In Successful! + data: + type: object + properties: + id: + type: integer + description: User ID + email: + type: string + format: email + accessToken: + type: string + description: JWT access token + refreshToken: + type: string + description: JWT refresh token + createdMillis: + type: integer + description: User creation timestamp in milliseconds + '400': + description: Email not verified + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '403': + description: Invalid password + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '422': + $ref: '#/components/responses/ValidationError' + /api/auth/token/refresh: + post: + operationId: refreshToken + summary: Refresh access token + description: Generate a new access token and refresh token using a valid refresh token. + tags: + - Auth + security: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - refresh_token + properties: + refresh_token: + type: string + description: JWT refresh token obtained from sign in + responses: + '200': + description: Tokens refreshed successfully + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + msg: + example: Successfully Created New Refresh Token! + data: + type: object + properties: + accessToken: + type: string + description: New JWT access token + refreshToken: + type: string + description: New JWT refresh token + '400': + description: Refresh token expired or invalid + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + /api/auth/token/validate: + post: + operationId: validateToken + summary: Validate access token + description: Check whether an access token is valid and not expired. Token can be passed via x-access-token header or query parameter. + tags: + - Auth + security: + - accessTokenAuth: [] + parameters: + - name: x_access_token + in: query + required: false + description: Access token (alternative to x-access-token header) + schema: + type: string + responses: + '200': + description: Token is valid + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + msg: + example: Token is Valid! + data: + type: object + properties: + id: + type: integer + description: User ID associated with the token + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + /api/auth/reset-password: + post: + operationId: resetPassword + summary: Request password reset + description: Send a password reset link to the provided email address. + tags: + - Auth + security: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - email + properties: + email: + type: string + format: email + description: Email address associated with the account + example: user@example.com + responses: + '200': + description: Password reset link sent + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + msg: Reset password link emailed to user! + data: null + '400': + description: Email delivery failed or reset already requested recently + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '422': + $ref: '#/components/responses/ValidationError' + /api/auth/reset-password/token-verify: + get: + operationId: verifyResetPasswordToken + summary: Verify password reset token + description: Check whether a password reset token is valid and not expired. + tags: + - Auth + security: [] + parameters: + - name: resetPasswordToken + in: query + required: true + description: Reset password token sent to user's email + schema: + type: string + responses: + '200': + description: Token is valid + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + msg: Token is Valid! + data: null + '400': + description: Invalid or expired token + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + /api/auth/update-password: + post: + operationId: updatePassword + summary: Update password + description: Set a new password using a valid reset password token. + tags: + - Auth + security: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - new_password + - reset_password_token + properties: + new_password: + type: string + description: The new password to set + reset_password_token: + type: string + description: Reset password token from email + responses: + '200': + description: Password updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + msg: Password Updated Successfully! + data: null + '400': + description: Invalid token or server error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '422': + $ref: '#/components/responses/ValidationError' + /api/v1/payments: + post: + operationId: createPayment + summary: Create a payment + description: | + Create a payment intent with the provided details. Supports multiple payment providers + (Stripe) and payment methods (Card). + + The request body structure varies by provider and payment method type. + Field casing note: `provider`, `currency`, `payment_method.type`, and `capture_method` + are case-insensitive on input (automatically uppercased internally). + tags: + - Payments + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/RefAppKeyHeader' + - $ref: '#/components/parameters/PaymentTypeHeader' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreatePaymentRequest' + examples: + stripeCard: + summary: Stripe card payment + value: + provider: stripe + source: + amount: 5000 + currency: usd + customer: + id: 550e8400-e29b-41d4-a716-446655440000 + payment_method: + type: card + id: 660e8400-e29b-41d4-a716-446655440000 + capture_method: automatic + confirm: true + metadata: + order_id: ORD-12345 + responses: + '200': + description: Payment created successfully + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + msg: + example: Transaction was Initiated Successfully! + data: + $ref: '#/components/schemas/PaymentResponse' + '400': + description: Validation error or invalid state + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + description: Customer not found + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '422': + $ref: '#/components/responses/ValidationError' + /api/v1/payments/{id}/confirm: + post: + operationId: confirmPayment + summary: Confirm a payment + description: Confirm a previously created payment intent that was not auto-confirmed. + tags: + - Payments + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + description: Payment transaction ID (UUID) + schema: + type: string + format: uuid + responses: + '200': + description: Payment confirmed + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + msg: + example: Transaction is in Progress! + data: + $ref: '#/components/schemas/PaymentResponse' + '400': + description: Invalid state for confirmation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + description: Transaction not found + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + /api/v1/payments/{id}/capture: + post: + operationId: capturePayment + summary: Capture a payment + description: Capture a previously authorized payment (for manual capture_method payments). + tags: + - Payments + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + description: Payment transaction ID (UUID) + schema: + type: string + format: uuid + responses: + '200': + description: Payment captured + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + msg: + example: Payment captured successfully + data: + $ref: '#/components/schemas/PaymentResponse' + '400': + description: Error capturing payment + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + /api/v1/payments/{id}/cancel: + post: + operationId: cancelPayment + summary: Cancel a payment + description: | + Cancel a payment that is in INITIATED status. Only PAYMENT type transactions + can be cancelled; other transaction types will return an error. + tags: + - Payments + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + description: Payment transaction ID (UUID) + schema: + type: string + format: uuid + responses: + '200': + description: Payment cancelled + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + msg: + example: Transaction cancellation successful + data: + $ref: '#/components/schemas/PaymentResponse' + '400': + description: Invalid state or wrong transaction type + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + description: Transaction not found + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + /api/v1/payments/{id}/refund: + post: + operationId: refundPayment + summary: Refund a payment + description: | + Create a refund for a completed payment. Partial refunds are supported by + specifying an amount less than the original. Total refund amount (including + previous refunds) cannot exceed the original transaction amount. + tags: + - Payments + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + description: Payment transaction ID (UUID) + schema: + type: string + format: uuid + requestBody: + required: false + content: + application/json: + schema: + type: object + properties: + amount: + type: integer + minimum: 1 + description: Refund amount in smallest currency unit. Omit for full refund. + metadata: + type: object + description: Additional metadata for the refund + additionalProperties: true + example: + amount: 2500 + metadata: + reason: Customer request + responses: + '200': + description: Refund initiated + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + msg: + example: Transaction was Initiated Successfully! + data: + $ref: '#/components/schemas/PaymentResponse' + '400': + description: Refund amount exceeds original or invalid state + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + description: Transaction not found + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '422': + $ref: '#/components/responses/ValidationError' + /api/v1/merchant: + post: + operationId: createMerchant + summary: Create a merchant account + description: Create a new merchant with the provided business details. + tags: + - Merchants + security: + - accessTokenAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateMerchantRequest' + example: + legal_name: Acme Corp + address: 123 Business St, Suite 100 + website: https://acme.example.com + tax_number: '12345678901' + responses: + '200': + description: Merchant created successfully + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/MerchantResponse' + '400': + description: Validation error or merchant already exists + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '422': + $ref: '#/components/responses/ValidationError' + /api/v1/merchant/key/create: + get: + operationId: createMerchantKey + summary: Create merchant API keys + description: Generate API key pair (public + secret) for a merchant. The secret key is only shown once. + tags: + - Merchants + security: + - accessTokenAuth: [] + responses: + '200': + description: Keys generated successfully + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: object + properties: + secretKey: + type: string + description: Secret key (only shown once, store securely) + publicKey: + type: string + description: Public key + '400': + description: Error generating keys + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + /api/v1/merchant/token/grant: + post: + operationId: grantMerchantToken + summary: Grant merchant token + description: | + Authenticate using client credentials to obtain a Bearer token for API access. + This is the primary authentication method for merchant API calls. + tags: + - Merchants + security: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GrantTokenRequest' + example: + client_id: app_abc123 + client_secret: sk_live_xyz789 + grant_type: client_credentials + responses: + '200': + description: Token granted + content: + application/json: + schema: + $ref: '#/components/schemas/MerchantTokenResponse' + example: + access_token: eyJhbGciOiJIUzI1NiIs... + token_type: Bearer + expires_in: 3600000 + '404': + description: Merchant not found or invalid credentials + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '422': + $ref: '#/components/responses/ValidationError' + /api/v1/merchant/token/refresh: + post: + operationId: refreshMerchantToken + summary: Refresh merchant token + description: Generate new access and refresh tokens using a valid refresh token. + tags: + - Merchants + security: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - refresh_token + properties: + refresh_token: + type: string + description: Refresh token from previous grant or refresh + responses: + '200': + description: Tokens refreshed + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: object + properties: + accessToken: + type: string + refreshToken: + type: string + '400': + description: Token expired or invalid + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '404': + description: Merchant not found + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + /api/v1/merchant/util/transfer-date: + post: + operationId: calculateTransferDate + summary: Calculate transfer date + description: Calculate the next available transfer date based on settlement date, region, and holiday configuration. + tags: + - Merchants + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TransferDateRequest' + responses: + '200': + description: Transfer date calculated + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: object + properties: + transferDate: + type: string + format: date-time + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + /api/v1/merchant/webhooks: + get: + operationId: listWebhooks + summary: List webhooks + description: List all registered webhooks for the authenticated merchant. + tags: + - Webhooks + security: + - bearerAuth: [] + responses: + '200': + description: Webhook list + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/WebhookResponse' + post: + operationId: registerWebhook + summary: Register a webhook + description: Register a new webhook endpoint for the merchant. Returns a signing secret for payload verification. + tags: + - Webhooks + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterWebhookRequest' + example: + url: https://example.com/webhooks/crowdsplit + description: Production webhook endpoint + responses: + '200': + description: Webhook registered. Secret is only shown once. + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: object + properties: + secret: + type: string + description: Signing secret for HMAC-SHA256 verification + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + /api/v1/merchant/webhooks/{id}: + get: + operationId: getWebhook + summary: Get webhook details + description: Get details of a specific webhook by ID. + tags: + - Webhooks + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Webhook details + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/WebhookResponse' + '404': + $ref: '#/components/responses/NotFoundError' + put: + operationId: updateWebhook + summary: Update a webhook + description: Update the URL or description of an existing webhook. + tags: + - Webhooks + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateWebhookRequest' + responses: + '200': + description: Webhook updated + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/WebhookResponse' + '404': + $ref: '#/components/responses/NotFoundError' + delete: + operationId: deleteWebhook + summary: Delete a webhook + description: | + Remove a registered webhook URL. New events for this merchant + will no longer be dispatched to the deleted URL. Notifications + already queued on the broker at the moment of deletion continue + through their retry schedule until acknowledged or exhausted — + the delete does not purge in-flight deliveries. + tags: + - Webhooks + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Webhook deleted + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/NotFoundError' + /api/v1/merchant/webhooks/{id}/toggle: + patch: + operationId: toggleWebhookStatus + summary: Toggle webhook active status + description: | + Flip a registered webhook between active and inactive states + without deleting it. Useful for planned merchant-side maintenance + windows or temporarily silencing a noisy endpoint. Inactive + webhooks receive no new dispatches; deliveries already queued + before the toggle may still fire once before the state takes + effect. + tags: + - Webhooks + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Webhook status toggled + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/WebhookResponse' + '404': + $ref: '#/components/responses/NotFoundError' + /api/v1/merchant/webhooks/notifications: + get: + operationId: listWebhookNotifications + summary: List webhook notifications + description: List all webhook notification events for the merchant with pagination. + tags: + - Webhooks + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/LimitQuery' + - $ref: '#/components/parameters/OffsetQuery' + responses: + '200': + description: Notification list + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: object + properties: + count: + type: integer + description: Total notification count + notification_list: + type: array + items: + $ref: '#/components/schemas/WebhookNotification' + /api/v1/merchant/webhooks/notifications/{id}: + get: + operationId: getWebhookNotification + summary: Get webhook notification details + description: Get details of a specific webhook notification event. + tags: + - Webhooks + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Notification details + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/WebhookNotification' + '404': + $ref: '#/components/responses/NotFoundError' + /api/v1/customers: + post: + operationId: createCustomer + summary: Create a customer + description: | + Register a new customer. Fields document_type, country_code, and gender + are case-insensitive (uppercased internally, returned lowercased). + tags: + - Customers + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateCustomerRequest' + example: + document_number: '12345678901' + document_type: cpf + email: customer@example.com + first_name: John + last_name: Doe + dob: '1990-01-15' + phone_country_code: '+55' + phone_area_code: '11' + phone_number: '999999999' + country_code: br + responses: + '200': + description: Customer created + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/CustomerResponse' + '400': + description: Validation error or customer already exists + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '422': + $ref: '#/components/responses/ValidationError' + get: + operationId: listCustomers + summary: List all customers + description: | + Retrieve a paginated list of customers for the authenticated merchant. + Query parameters for filtering are case-insensitive (uppercased internally). + tags: + - Customers + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/LimitQuery' + - $ref: '#/components/parameters/OffsetQuery' + - name: target_role + in: query + description: Comma-separated roles to filter by (case-insensitive) + schema: + type: string + - name: provider_registration_status + in: query + description: Comma-separated provider registration statuses (case-insensitive) + schema: + type: string + - name: provider + in: query + description: Comma-separated provider names (case-insensitive) + schema: + type: string + - name: email + in: query + description: Comma-separated email addresses to filter by + schema: + type: string + - name: document_type + in: query + description: Comma-separated document types (case-insensitive) + schema: + type: string + - name: country_code + in: query + description: Comma-separated country codes (case-insensitive) + schema: + type: string + - name: strict + in: query + description: Enforce limit restrictions + schema: + type: boolean + responses: + '200': + description: Customer list + headers: + X-Limit-Requested: + description: Requested limit value + schema: + type: integer + X-Limit-Applied: + description: Applied limit value + schema: + type: integer + X-Offset-Requested: + description: Requested offset value + schema: + type: integer + X-Offset-Applied: + description: Applied offset value + schema: + type: integer + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: object + properties: + count: + type: integer + description: Total customer count + customer_list: + type: array + items: + $ref: '#/components/schemas/CustomerResponse' + /api/v1/customers/{id}: + get: + operationId: getCustomer + summary: Get customer details + description: Retrieve details for a specific customer. + tags: + - Customers + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Customer details + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/CustomerResponse' + '404': + $ref: '#/components/responses/NotFoundError' + put: + operationId: updateCustomer + summary: Update a customer + description: Update customer details. All fields are optional. + tags: + - Customers + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateCustomerRequest' + responses: + '200': + description: Customer updated + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/CustomerResponse' + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/NotFoundError' + /api/v1/customers/{id}/sync: + post: + operationId: syncCustomer + summary: Sync customer data + description: Trigger a sync of specified fields with an external provider. + tags: + - Customers + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SyncCustomerRequest' + example: + providers: + - stripe + fields: + - email + - phone + responses: + '200': + description: Sync scheduled + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + msg: Sync scheduled + data: null + '400': + description: Invalid provider or fields + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/NotFoundError' + /api/v1/customers/{customer_id}/balances: + get: + operationId: getCustomerBalances + summary: Get customer balances + description: Retrieve available and pending balances for a customer across providers. + tags: + - Customers + security: + - bearerAuth: [] + parameters: + - name: customer_id + in: path + required: true + schema: + type: string + format: uuid + - name: provider + in: query + description: Comma-separated provider names (case-insensitive) + schema: + type: string + - name: role + in: query + description: Comma-separated roles (case-insensitive) + schema: + type: string + responses: + '200': + description: Balance information + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/BalanceResponse' + '400': + description: Invalid customer ID + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/NotFoundError' + /api/v1/customers/{customer_id}/files: + post: + operationId: uploadCustomerFiles + summary: Upload customer files + description: Upload identity or address verification documents for a customer. + tags: + - Customers + security: + - bearerAuth: [] + parameters: + - name: customer_id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + description: Document file (max 5MB) + file_type: + type: string + description: Document type + enum: + - SSN_BACK + - SSN_FRONT + - CPF_DOCUMENT + - PASSPORT + - ADDRESS_PROOF + responses: + '201': + description: File uploaded successfully + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + msg: file upload successful + data: null + '400': + description: Invalid file or missing data + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/NotFoundError' + get: + operationId: getCustomerFiles + summary: Get customer files + description: Retrieve uploaded files for a customer with presigned download URLs. + tags: + - Customers + security: + - bearerAuth: [] + parameters: + - name: customer_id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: File list + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: array + items: + type: object + properties: + fileType: + type: string + url: + type: string + description: Presigned S3 URL (temporary) + '404': + $ref: '#/components/responses/NotFoundError' + /api/v1/customers/{customer_id}/payment_methods: + post: + operationId: createPaymentMethod + summary: Add a payment method + description: | + Create a payment method for a customer. Fields type, provider, currency, + chain, and bank_account_type are case-insensitive. + tags: + - Payment Methods + security: + - bearerAuth: [] + parameters: + - name: customer_id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreatePaymentMethodRequest' + examples: + bankAccount: + summary: Bank account payment method + value: + type: bank + provider: crowd_split + bank_details: + account_number: '12345678' + account_name: John Doe + bank_name: Banco do Brasil + branch_code: '0001' + account_type: checking + ispb: '00000000' + responses: + '200': + description: Payment method created + content: + application/json: + schema: + type: object + properties: + msg: + type: string + data: + $ref: '#/components/schemas/PaymentMethodResponse' + provider_message: + type: string + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/NotFoundError' + '422': + $ref: '#/components/responses/ValidationError' + get: + operationId: listPaymentMethods + summary: List payment methods + description: List all payment methods for a customer. + tags: + - Payment Methods + security: + - bearerAuth: [] + parameters: + - name: customer_id + in: path + required: true + schema: + type: string + format: uuid + - name: type + in: query + description: Filter by payment method type (case-insensitive) + schema: + type: string + - name: status + in: query + description: Filter by status (case-insensitive) + schema: + type: string + - name: provider + in: query + description: Filter by provider (case-insensitive) + schema: + type: string + responses: + '200': + description: Payment method list + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/PaymentMethodResponse' + /api/v1/customers/{customer_id}/payment_methods/{id}: + get: + operationId: getPaymentMethod + summary: Get payment method details + description: Get details of a specific payment method. + tags: + - Payment Methods + security: + - bearerAuth: [] + parameters: + - name: customer_id + in: path + required: true + schema: + type: string + format: uuid + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Payment method details + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/PaymentMethodResponse' + '404': + $ref: '#/components/responses/NotFoundError' + put: + operationId: updatePaymentMethod + summary: Update a payment method + description: | + Replace mutable fields on a saved payment method — typically + customer-visible label, default-flag, or billing-address metadata. + Provider-issued identifiers such as `card_token` and on-chain + addresses are immutable; delete the payment method and re-create + it to change those. + tags: + - Payment Methods + security: + - bearerAuth: [] + parameters: + - name: customer_id + in: path + required: true + schema: + type: string + format: uuid + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreatePaymentMethodRequest' + responses: + '200': + description: Payment method updated + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/PaymentMethodResponse' + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/NotFoundError' + delete: + operationId: deletePaymentMethod + summary: Delete a payment method + description: Delete a payment method from a customer. + tags: + - Payment Methods + security: + - bearerAuth: [] + parameters: + - name: customer_id + in: path + required: true + schema: + type: string + format: uuid + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Payment method deleted + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/NotFoundError' + /api/v1/customers/{customer_id}/platforms: + post: + operationId: populateKycData + summary: Submit platform registration data + description: Submit KYC/provider registration data for a customer on a specific provider platform. + tags: + - Provider Registration + security: + - bearerAuth: [] + parameters: + - name: customer_id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - provider + - target_role + properties: + provider: + type: string + description: Provider name (case-insensitive) + target_role: + type: string + description: Target role for the provider registration + additionalProperties: true + responses: + '200': + description: Platform data submitted + content: + application/json: + schema: + type: object + properties: + msg: + type: string + data: + type: array + description: | + Registration status for this customer across every + provider they are registered on, one entry per + `(provider, target_role)` pair. Same shape as + `GET /api/v1/provider-registration/{customer_id}/status`. + items: + type: object + properties: + provider: + type: string + description: Provider name (lowercased). + target_role: + type: string + description: Target role on this provider (lowercased). + status: + type: string + description: Current registration status (lowercased). + readiness: + type: object + nullable: true + description: Provider-specific readiness flags (JSONB pass-through). + additionalProperties: true + rejection_reason: + type: string + nullable: true + description: Human-readable rejection reason when rejection-like. + additionalProperties: true + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/NotFoundError' + '422': + $ref: '#/components/responses/ValidationError' + /api/v1/transactions: + get: + operationId: listTransactions + summary: List transactions + description: | + Retrieve a paginated list of transactions for the merchant. + Query filter values are case-insensitive (uppercased internally). + Response fields status, type, provider, currency, payment_method are returned lowercased. + tags: + - Transactions + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/LimitQuery' + - $ref: '#/components/parameters/OffsetQuery' + - name: type_list + in: query + description: Comma-separated transaction types (case-insensitive) + schema: + type: string + - name: status + in: query + description: Filter by status (case-insensitive) + schema: + type: string + - name: payment_method + in: query + description: Filter by payment method type (case-insensitive) + schema: + type: string + - name: source_currency + in: query + description: Filter by source currency (case-insensitive) + schema: + type: string + - name: destination_currency + in: query + description: Filter by destination currency (case-insensitive) + schema: + type: string + - name: provider + in: query + description: Filter by provider (case-insensitive) + schema: + type: string + responses: + '200': + description: Transaction list + headers: + X-Limit-Requested: + schema: + type: integer + X-Limit-Applied: + schema: + type: integer + X-Offset-Requested: + schema: + type: integer + X-Offset-Applied: + schema: + type: integer + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: object + properties: + count: + type: integer + transaction_list: + type: array + items: + $ref: '#/components/schemas/TransactionResponse' + /api/v1/transactions/{id}: + get: + operationId: getTransaction + summary: Get transaction details + description: Retrieve details of a single transaction. + tags: + - Transactions + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Transaction details + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/TransactionResponse' + '404': + $ref: '#/components/responses/NotFoundError' + /api/v1/transactions/{id}/settle: + patch: + operationId: settleTransaction + summary: Settle a transaction + description: Mark a transaction as settled. Requires app-key authentication. + tags: + - Transactions + security: [] + parameters: + - name: id + in: path + required: true + description: Transaction UID + schema: + type: string + format: uuid + - name: app-key + in: header + required: true + description: Application key for settlement authorization + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + type: object + required: + - charge_id + - amount + - status + properties: + charge_id: + type: string + description: Provider charge identifier + amount: + type: integer + description: Settlement amount in smallest currency unit + status: + type: string + example: SETTLED + responses: + '200': + description: Transaction settled + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/TransactionResponse' + '400': + description: Settlement failed or invalid request + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '404': + $ref: '#/components/responses/NotFoundError' + '422': + $ref: '#/components/responses/ValidationError' + /api/v1/outbound_payments: + post: + operationId: createPayout + summary: Create an outbound payment + description: Initiate a payout/outbound payment to a customer's payment method. + tags: + - Payouts + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - payment_method_id + - amount + - currency + - customer_id + properties: + payment_method_id: + type: string + format: uuid + description: Target payment method ID + amount: + type: integer + minimum: 1 + description: Amount in smallest currency unit + currency: + type: string + enum: + - USD + - BRL + description: Currency code + customer_id: + type: string + format: uuid + description: Customer ID + metadata: + type: object + description: Custom metadata + additionalProperties: true + example: + payment_method_id: 660e8400-e29b-41d4-a716-446655440000 + amount: 50000 + currency: BRL + customer_id: 550e8400-e29b-41d4-a716-446655440000 + responses: + '200': + description: Payout initiated + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/TransactionResponse' + '400': + description: Invalid request or payment method not found + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + description: Customer or payment method not found + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '422': + $ref: '#/components/responses/ValidationError' + /api/v1/transfer: + post: + operationId: createTransfer + summary: Create a transfer + description: | + Initiate a transfer between accounts. Supports single-provider and inter-platform transfers. + Fields provider, currency, payment_method.type are case-insensitive. + tags: + - Transfers + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - source + - destination + properties: + provider: + type: string + description: Provider for single-provider transfers (case-insensitive) + source: + type: object + description: Source account details + properties: + provider: + type: string + description: Source provider (for inter-platform transfers) + currency: + type: string + customer: + type: object + properties: + id: + type: string + format: uuid + amount: + type: integer + minimum: 1 + destination: + type: object + description: Destination account details + properties: + provider: + type: string + description: Destination provider (for inter-platform transfers) + customer: + type: object + properties: + id: + type: string + format: uuid + payment_method: + type: object + properties: + type: + type: string + id: + type: string + format: uuid + currency: + type: string + chain: + type: string + metadata: + type: object + description: Custom metadata + additionalProperties: true + responses: + '200': + description: Transfer initiated + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/TransactionResponse' + '400': + description: Invalid request or platform error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/NotFoundError' + '422': + $ref: '#/components/responses/ValidationError' + /api/v1/transfer/webhook: + post: + operationId: sendTransferWebhook + summary: Send transfer webhook (dev) + description: Manually trigger a webhook for a transfer. Intended for development/testing. + tags: + - Transfers + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - transfer_id + - status + properties: + transfer_id: + type: string + format: uuid + description: Transfer transaction UUID + status: + type: string + description: Status to set + responses: + '200': + description: Webhook sent + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + msg: + example: webhook sent successfully + data: + type: 'null' + '400': + description: Error sending webhook + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + /api/v1/buy: + post: + operationId: createBuy + summary: Create a buy transaction + description: | + Initiate a cryptocurrency/asset buy transaction. + Fields provider, currency, payment_method.type are case-insensitive. + tags: + - Buy / Sell + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - provider + - source + properties: + provider: + type: string + description: Provider name (case-insensitive) + source: + type: object + properties: + amount: + type: integer + minimum: 1 + currency: + type: string + customer: + type: object + properties: + id: + type: string + format: uuid + payment_method: + type: object + properties: + type: + type: string + destination: + type: object + properties: + currency: + type: string + payment_method: + type: object + properties: + type: + type: string + chain: + type: string + metadata: + type: object + description: | + Merchant-supplied arbitrary metadata, stored with the + transaction and echoed back on derived webhook deliveries. + Shape is defined by the merchant at request time. + additionalProperties: true + responses: + '200': + description: Buy transaction initiated + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/TransactionResponse' + '400': + description: Invalid request + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '422': + $ref: '#/components/responses/ValidationError' + /api/v1/wallets/{customer_id}/balance: + get: + operationId: getWalletBalance + summary: Get trading wallet balance + description: Retrieve the trading wallet balance for a customer. + tags: + - Wallets + security: + - bearerAuth: [] + parameters: + - name: customer_id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Wallet balance + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: object + description: Trading wallet balance information + '400': + description: Invalid request + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + /api/v1/wallets/trades/buy: + post: + operationId: createWalletBuy + summary: Create a wallet buy trade + description: Initiate a buy trade through the wallet interface. + tags: + - Wallets + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - amount + - provider + - payment_method + - currency + - customer_id + properties: + amount: + type: integer + minimum: 1 + provider: + type: string + payment_method: + type: string + currency: + type: string + customer_id: + type: string + format: uuid + metadata: + type: object + description: | + Merchant-supplied arbitrary metadata, stored with the + transaction and echoed back on derived webhook deliveries. + Shape is defined by the merchant at request time. + additionalProperties: true + responses: + '200': + description: Buy trade initiated + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/TransactionResponse' + '400': + description: Invalid request + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '422': + $ref: '#/components/responses/ValidationError' + /api/v1/disputes: + get: + operationId: listDisputes + summary: List disputes + description: Retrieve a paginated list of disputes for the merchant. + tags: + - Disputes + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/LimitQuery' + - $ref: '#/components/parameters/OffsetQuery' + - name: strict + in: query + description: Enforce limit restrictions + schema: + type: boolean + responses: + '200': + description: Dispute list + headers: + X-Limit-Requested: + schema: + type: integer + X-Limit-Applied: + schema: + type: integer + X-Offset-Requested: + schema: + type: integer + X-Offset-Applied: + schema: + type: integer + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: object + properties: + count: + type: integer + dispute_list: + type: array + items: + $ref: '#/components/schemas/DisputeResponse' + /api/v1/disputes/{dispute_id}/evidence: + put: + operationId: updateDisputeEvidence + summary: Upload dispute evidence + description: Upload file and/or text evidence for a dispute. At least one of file_evidences or text_evidences is required. + tags: + - Disputes + security: + - bearerAuth: [] + parameters: + - name: dispute_id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + file_evidences: + type: array + items: + type: object + required: + - id + - type + properties: + id: + type: string + format: uuid + description: File ID + type: + type: string + enum: + - receipt + - customer_signature + - shipping_documentation + - service_documentation + - refund_policy + - cancellation_policy + - uncategorized_file + text_evidences: + type: array + items: + type: object + required: + - key + - value + properties: + key: + type: string + enum: + - customer_name + - customer_email_address + - customer_purchase_ip + - product_description + - duplicate_charge_id + - enhanced_evidence + - customer_communication + - refund_policy + - refund_policy_disclosure + - refund_refusal_explanation + - service_date + - shipping_address + - shipping_carrier + - shipping_date + - shipping_tracking_number + - shipping_tracking_url + - duplicate_charge_explanation + - cancellation_policy + - cancellation_rebuttal + - uncategorized_text + value: + type: + - string + - 'null' + responses: + '200': + description: Evidence uploaded + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/DisputeResponse' + '400': + description: Invalid evidence data + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/NotFoundError' + '422': + $ref: '#/components/responses/ValidationError' + /api/v1/disputes/{dispute_id}/submit: + put: + operationId: submitDispute + summary: Submit dispute for review + description: Mark the dispute as final and submit it to the provider for review. + tags: + - Disputes + security: + - bearerAuth: [] + parameters: + - name: dispute_id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Dispute submitted + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/DisputeResponse' + '404': + $ref: '#/components/responses/NotFoundError' + /api/v1/disputes/{dispute_id}/close: + put: + operationId: closeDispute + summary: Close a dispute + description: | + Close the dispute by accepting the provider's outcome. Used when + the merchant chooses not to contest (or to stop contesting). Once + closed the dispute state is terminal — subsequent evidence uploads + are no-ops. For the final funds-movement state, listen for the + `payment.dispute.funds_withdrawn` or + `payment.dispute.funds_reinstated` webhook event. + tags: + - Disputes + security: + - bearerAuth: [] + parameters: + - name: dispute_id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Dispute closed + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/DisputeResponse' + '404': + $ref: '#/components/responses/NotFoundError' + /api/v1/subscription/plans: + get: + operationId: listPlans + summary: List subscription plans + description: Retrieve all subscription plans for the merchant. + tags: + - Subscriptions + security: + - bearerAuth: [] + responses: + '200': + description: Plan list + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/PlanResponse' + post: + operationId: createPlan + summary: Create a subscription plan + description: | + Define a recurring-payment plan template. `price` must be between + 100 and 10_000 and `currency` must be `USD`. Plans are not eligible + for subscriptions until they are published via + `PATCH /api/v1/subscription/plans/{planId}/publish`. + tags: + - Subscriptions + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreatePlanRequest' + example: + name: Monthly Pro + description: Monthly professional plan + frequency: 30 + price: 2999 + currency: USD + created_by: admin@example.com + responses: + '201': + description: Plan created + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + msg: + example: plan created + data: + type: string + description: Plan hash ID + /api/v1/subscription/plans/{planId}: + get: + operationId: getPlan + summary: Get plan details + description: Retrieve details of a specific subscription plan. + tags: + - Subscriptions + security: + - bearerAuth: [] + parameters: + - name: planId + in: path + required: true + schema: + type: string + responses: + '200': + description: Plan details + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/PlanResponse' + '404': + $ref: '#/components/responses/NotFoundError' + patch: + operationId: updatePlan + summary: Update a plan + description: Update plan properties. All fields are optional. + tags: + - Subscriptions + security: + - bearerAuth: [] + parameters: + - name: planId + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreatePlanRequest' + responses: + '200': + description: Plan updated + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + msg: + example: plan updated + data: + type: string + description: Plan hash ID + delete: + operationId: deletePlan + summary: Delete a plan + description: | + Soft-delete a subscription plan. The record is retained so that + existing subscribers continue through their current billing cycle + with the plan's snapshotted terms; no new subscriptions can + reference the plan after deletion. Hard removal requires ops + intervention. + tags: + - Subscriptions + security: + - bearerAuth: [] + parameters: + - name: planId + in: path + required: true + schema: + type: string + responses: + '200': + description: Plan deleted + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + msg: + example: plan deleted + data: + type: string + '404': + $ref: '#/components/responses/NotFoundError' + /api/v1/subscription/plans/{planId}/publish: + patch: + operationId: publishPlan + summary: Publish a plan + description: Activate a plan so it can accept subscriptions. + tags: + - Subscriptions + security: + - bearerAuth: [] + parameters: + - name: planId + in: path + required: true + schema: + type: string + responses: + '200': + description: Plan published + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + msg: + example: plan published + data: + type: string + /api/v1/subscription/subscribe: + post: + operationId: subscribe + summary: Subscribe to a plan + description: Create a subscription for a customer to a plan. + tags: + - Subscriptions + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - plan_id + - source_customer_id + - destination_customer_id + - payment_method_id + - payment_method_type + - payment_method_provider + - fee_bearer + properties: + plan_id: + type: string + description: Plan hash ID + source_customer_id: + type: string + format: uuid + destination_customer_id: + type: string + format: uuid + payment_method_id: + type: string + format: uuid + payment_method_type: + type: string + enum: + - CARD + payment_method_provider: + type: string + fee_bearer: + type: string + enum: + - connected_account + responses: + '200': + description: Subscription created + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + msg: + example: Subscription created successfully + data: + type: object + properties: + id: + type: string + description: Subscription hash ID + status: + type: string + example: pending_activation + sub_status: + type: string + example: payment_init + '400': + description: Invalid request + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + /api/v1/subscription/subscriptions/{subscriptionId}/cancel: + patch: + operationId: cancelSubscription + summary: Cancel a subscription + description: | + Cancel an active subscription. Future billing cycles + are skipped; already-captured payments are preserved and must be + refunded separately via `POST /api/v1/payments/{id}/refund` if a + refund is required. + tags: + - Subscriptions + security: + - bearerAuth: [] + parameters: + - name: subscriptionId + in: path + required: true + schema: + type: string + responses: + '200': + description: Subscription cancelled + content: + application/json: + schema: + type: object + properties: + msg: + type: string + example: Subscription cancelled successfully + data: + type: object + description: | + Pass-through response from the upstream subscription + service. Shape is not contractually stable; treat as + opaque acknowledgement. + additionalProperties: true + '404': + $ref: '#/components/responses/NotFoundError' + /api/v1/subscription/list: + get: + operationId: listSubscriptions + summary: List subscriptions + description: List subscriptions for a customer with pagination. + tags: + - Subscriptions + security: + - bearerAuth: [] + parameters: + - name: customer_id + in: query + required: true + schema: + type: string + format: uuid + - name: status + in: query + description: Comma-separated statuses (active, pending_activation, canceled, expired, queued) + schema: + type: string + - name: per_page + in: query + schema: + type: integer + maximum: 100 + - name: page_no + in: query + schema: + type: integer + minimum: 1 + responses: + '200': + description: Subscription list + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/SubscriptionResponse' + pagination: + type: object + properties: + per_page: + type: integer + page_no: + type: integer + total: + type: integer + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + /api/v1/subscription/{subscriptionId}: + get: + operationId: getSubscription + summary: Get subscription details + description: Retrieve details of a specific subscription. + tags: + - Subscriptions + security: + - bearerAuth: [] + parameters: + - name: subscriptionId + in: path + required: true + schema: + type: string + responses: + '200': + description: Subscription details + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/SubscriptionResponse' + '404': + $ref: '#/components/responses/NotFoundError' + /api/v1/subscription/{subscriptionId}/payment: + post: + operationId: initiateSubscriptionPayment + summary: Initiate subscription payment + description: | + Manually initiate a payment for a subscription. + + **Authentication.** Merchant `bearerAuth` only. + tags: + - Subscriptions + security: + - bearerAuth: [] + parameters: + - name: subscriptionId + in: path + required: true + description: Subscription hash ID. + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - customer_id + - payment_method_id + - payment_method_type + - payment_method_provider + properties: + customer_id: + type: string + format: uuid + payment_method_id: + type: string + format: uuid + payment_method_type: + type: string + enum: + - CARD + payment_method_provider: + type: string + responses: + '200': + description: Payment initiated + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: object + properties: + id: + type: string + status: + type: string + sub_status: + type: string + '400': + description: Invalid request + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/NotFoundError' + /api/v1/provider-registration/schema: + get: + operationId: getKycSchema + summary: Get KYC schema + description: Retrieve the provider registration schema definition. + tags: + - Provider Registration + security: + - bearerAuth: [] + responses: + '200': + description: KYC schema + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: object + description: Provider-specific KYC schema definition + /api/v1/provider-registration/{customer_id}/submit: + post: + operationId: submitKyc + summary: Submit provider registration + description: | + Submit KYC/provider registration for a customer. The request body varies + by provider. Fields provider and target_role are case-insensitive. + tags: + - Provider Registration + security: + - bearerAuth: [] + parameters: + - name: customer_id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - provider + - target_role + properties: + provider: + type: string + description: Provider name (case-insensitive) + target_role: + type: string + description: Target role for registration + additionalProperties: true + responses: + '200': + description: Registration submitted + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: object + '400': + description: Validation error or submission failed + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/NotFoundError' + '422': + $ref: '#/components/responses/ValidationError' + /api/v1/provider-registration/{customer_id}/status: + get: + operationId: getKycStatus + summary: Check provider registration status + description: | + Check the KYC/provider registration status for a customer across platforms. + Response fields provider, target_role, and status are returned lowercased. + tags: + - Provider Registration + security: + - bearerAuth: [] + parameters: + - name: customer_id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Registration status + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: object + description: Provider registration status per platform + '404': + $ref: '#/components/responses/NotFoundError' + /api/v1/taxes/calculate: + post: + operationId: calculateTaxes + summary: Calculate taxes + description: | + Calculate applicable taxes for a transaction. + Field provider is case-insensitive. + tags: + - Tax + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - provider + properties: + provider: + type: string + description: Provider name (case-insensitive) + additionalProperties: true + responses: + '200': + description: Tax calculation result + content: + application/json: + schema: + type: object + properties: + msg: + type: string + data: + type: object + description: | + Tax calculation result. Echoes the request body fields + and adds a `provider_response` object containing the + raw tax-provider reply. Exact keys inside + `provider_response` vary per provider. + properties: + provider: + type: string + description: Provider that computed the tax (lowercased). + provider_response: + type: object + description: Raw upstream tax-provider response. Shape varies. + additionalProperties: true + additionalProperties: true + '400': + description: Calculation error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + /api/v1/files: + post: + operationId: uploadFiles + summary: Upload files + description: | + Upload one or more files for the merchant. Requests are + `multipart/form-data`; size and MIME-type limits follow the + server's upload configuration. + tags: + - Files + security: + - bearerAuth: [] + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + description: File to upload (use repeated `file` fields to upload multiple). + responses: + '200': + description: Files uploaded + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: object + '400': + description: Upload error (size / MIME-type violation, storage failure). + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + get: + operationId: listFiles + summary: List files + description: Retrieve a list of uploaded files for the merchant. + tags: + - Files + security: + - bearerAuth: [] + responses: + '200': + description: File list + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: array + items: + type: object + /api/v1/files/{file_id}: + get: + operationId: getFile + summary: Get file details + description: Retrieve details or download URL for a specific file. + tags: + - Files + security: + - bearerAuth: [] + parameters: + - name: file_id + in: path + required: true + schema: + type: string + responses: + '200': + description: File details + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: object + '404': + $ref: '#/components/responses/NotFoundError' + delete: + operationId: deleteFile + summary: Delete a file + description: | + Permanently delete a merchant-owned file, including its underlying + object-storage artifact. Irreversible; pre-signed URLs previously + generated against this file stop resolving once the object is + removed. + tags: + - Files + security: + - bearerAuth: [] + parameters: + - name: file_id + in: path + required: true + schema: + type: string + responses: + '200': + description: File deleted + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/NotFoundError' + /api/v1/providers/{provider}/proxy: + post: + operationId: genericProviderProxy + summary: Generic provider proxy call + description: | + Proxy a request to a specific payment provider's API. + The endpoint and request body must be whitelisted in the merchant's configuration. + tags: + - Generic + security: + - bearerAuth: [] + parameters: + - name: provider + in: path + required: true + description: Provider name + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + description: Provider-specific request body (varies by provider and endpoint) + additionalProperties: true + responses: + '200': + description: Provider response (pass-through) + content: + application/json: + schema: + type: object + description: | + Provider-specific response, passed through verbatim from + the upstream provider's API. Shape varies by `provider` + and by the specific endpoint being proxied — consult the + provider's own API documentation for the concrete shape. + additionalProperties: true + '400': + description: Request not whitelisted or provider error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/NotFoundError' + /api/health: + get: + operationId: healthCheck + summary: Health check + description: Check API server health including database and message queue connectivity. + tags: + - System + security: [] + responses: + '200': + description: All systems healthy + '503': + description: One or more systems unhealthy + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: error + message: + type: string + example: RabbitMQ is not available + /api/v1/countries: + get: + operationId: listCountries + summary: List countries + description: | + Return the hardcoded list of countries currently eligible for KYC + onboarding (mutual Stripe ∩ Bridge support — Stripe Connect card-payments + capability + Bridge allowed). Each entry is tagged with a + `kyc_supported` boolean (always `true` in the current response). The list + is open — no authentication required. Source list: RCS-453. + tags: + - System + security: [] + responses: + '200': + description: Country list + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiResponse' + - type: object + properties: + data: + type: object + required: + - countries + properties: + countries: + type: array + items: + $ref: '#/components/schemas/CountryResponse' +webhooks: + payment.awaiting_confirmation: + post: + summary: Payment awaiting confirmation + description: | + Payment awaiting confirmation. Emitted when a payment transitions to the + `payment.awaiting_confirmation` state in its lifecycle. Payload is the full + `WebhookTransactionData` for the payment. Retries preserve `data.id`; + deduplicate on `CrowdSplit-Notification-Id`. + operationId: webhookPaymentAwaitingConfirmation + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment.awaiting_confirmation + category: + const: payment_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + payment.cancelled: + post: + summary: Payment cancelled + description: | + Payment cancelled. Emitted when a payment transitions to the + `payment.cancelled` state in its lifecycle. Payload is the full + `WebhookTransactionData` for the payment. Retries preserve `data.id`; + deduplicate on `CrowdSplit-Notification-Id`. + operationId: webhookPaymentCancelled + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment.cancelled + category: + const: payment_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + payment.captured: + post: + summary: Payment captured + description: | + Payment captured. Emitted when a payment transitions to the + `payment.captured` state in its lifecycle. Payload is the full + `WebhookTransactionData` for the payment. Retries preserve `data.id`; + deduplicate on `CrowdSplit-Notification-Id`. + operationId: webhookPaymentCaptured + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment.captured + category: + const: payment_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + payment.failed: + post: + summary: Payment failed + description: | + Payment failed. Emitted when a payment transitions to the `payment.failed` + state in its lifecycle. Payload is the full `WebhookTransactionData` for the + payment. Retries preserve `data.id`; deduplicate on + `CrowdSplit-Notification-Id`. + operationId: webhookPaymentFailed + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment.failed + category: + const: payment_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + payment.installment.failed: + post: + summary: Installment payment failed + description: | + Installment payment failed. Emitted for a single installment of a + multi-installment payment. The parent payment's `id` is carried in `data.id`, + and the installment detail is in `data.installments`. Delivery is + per-installment — expect one event per installment transition. + operationId: webhookInstallmentFailed + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment.installment.failed + category: + const: payment_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + payment.installment.succeeded: + post: + summary: Installment payment succeeded + description: | + Installment payment succeeded. Emitted for a single installment of a + multi-installment payment. The parent payment's `id` is carried in `data.id`, + and the installment detail is in `data.installments`. Delivery is + per-installment — expect one event per installment transition. + operationId: webhookInstallmentSucceeded + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment.installment.succeeded + category: + const: payment_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + payment.processing: + post: + summary: Payment is processing + description: | + Payment is processing. Emitted when a payment transitions to the + `payment.processing` state in its lifecycle. Payload is the full + `WebhookTransactionData` for the payment. Retries preserve `data.id`; + deduplicate on `CrowdSplit-Notification-Id`. + operationId: webhookPaymentProcessing + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment.processing + category: + const: payment_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + payment.refunded: + post: + summary: Payment refunded + description: | + Payment refunded. Emitted when a payment transitions to the + `payment.refunded` state in its lifecycle. Payload is the full + `WebhookTransactionData` for the payment. Retries preserve `data.id`; + deduplicate on `CrowdSplit-Notification-Id`. + operationId: webhookPaymentRefunded + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment.refunded + category: + const: payment_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + payment.succeeded: + post: + summary: Payment succeeded + description: | + Payment succeeded. Emitted when a payment transitions to the + `payment.succeeded` state in its lifecycle. Payload is the full + `WebhookTransactionData` for the payment. Retries preserve `data.id`; + deduplicate on `CrowdSplit-Notification-Id`. + operationId: webhookPaymentSucceeded + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment.succeeded + category: + const: payment_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + payment.updated: + post: + summary: Payment updated + description: | + Payment updated. Emitted when a payment transitions to the `payment.updated` + state in its lifecycle. Payload is the full `WebhookTransactionData` for the + payment. Retries preserve `data.id`; deduplicate on + `CrowdSplit-Notification-Id`. + operationId: webhookPaymentUpdated + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment.updated + category: + const: payment_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + transaction.sc_updated: + post: + summary: Transaction settlement/checkpoint updated + description: | + Transaction settlement/checkpoint updated. Emitted when a transaction's + settlement or checkpoint state changes (typically during reconciliation). + Carries the latest `WebhookTransactionData`; `status` reflects the new state. + Retries share the same `CrowdSplit-Notification-Id`. + operationId: webhookTransactionScUpdated + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: transaction.sc_updated + category: + const: payment_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + transaction.updated: + post: + summary: Transaction updated + description: | + Transaction updated. Emitted when a transaction's top-level fields change + outside a lifecycle-specific event (e.g., metadata updates). Payload is the + current `WebhookTransactionData`. + operationId: webhookTransactionUpdated + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: transaction.updated + category: + const: payment_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + payment.refund.created: + post: + summary: Refund created + description: | + Refund created. Emitted during the refund sub-lifecycle of a payment. + `data.id` is the refund transaction's UID; `data.reference_transaction_id` + points to the original payment. Use this alongside the parent `payment.*` + events to reconcile net settlement. + operationId: webhookRefundCreated + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment.refund.created + category: + const: refund_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + payment.refund.failed: + post: + summary: Refund failed + description: | + Refund failed. Emitted during the refund sub-lifecycle of a payment. + `data.id` is the refund transaction's UID; `data.reference_transaction_id` + points to the original payment. Use this alongside the parent `payment.*` + events to reconcile net settlement. + operationId: webhookRefundFailed + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment.refund.failed + category: + const: refund_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + payment.refund.updated: + post: + summary: Refund updated + description: | + Refund updated. Emitted during the refund sub-lifecycle of a payment. + `data.id` is the refund transaction's UID; `data.reference_transaction_id` + points to the original payment. Use this alongside the parent `payment.*` + events to reconcile net settlement. + operationId: webhookRefundUpdated + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment.refund.updated + category: + const: refund_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + refund.failed: + post: + summary: Refund failed (standalone) + description: | + Refund failed (standalone). Emitted for standalone refunds not tied to a + specific payment (e.g., manual refund operations). Payload is the refund's + `WebhookTransactionData`. + operationId: webhookStandaloneRefundFailed + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: refund.failed + category: + const: refund_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + refund.succeeded: + post: + summary: Refund succeeded (standalone) + description: | + Refund succeeded (standalone). Emitted for standalone refunds not tied to a + specific payment (e.g., manual refund operations). Payload is the refund's + `WebhookTransactionData`. + operationId: webhookStandaloneRefundSucceeded + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: refund.succeeded + category: + const: refund_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + payment.dispute.closed: + post: + summary: Dispute closed + description: | + Dispute closed. Emitted during the dispute sub-lifecycle of a disputed + payment. Payload is the dispute record; `data.payment.id` links back to + the disputed payment. + operationId: webhookDisputeClosed + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment.dispute.closed + category: + const: dispute_lifecycle + data: + $ref: '#/components/schemas/WebhookDisputeData' + responses: + '200': + description: Webhook acknowledged + payment.dispute.funds_reinstated: + post: + summary: Dispute funds reinstated + description: | + Dispute funds reinstated. Emitted during the dispute sub-lifecycle of a + disputed payment. Payload is the dispute record; `data.payment.id` links + back to the disputed payment. + operationId: webhookDisputeFundsReinstated + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment.dispute.funds_reinstated + category: + const: dispute_lifecycle + data: + $ref: '#/components/schemas/WebhookDisputeData' + responses: + '200': + description: Webhook acknowledged + payment.dispute.funds_withdrawn: + post: + summary: Dispute funds withdrawn + description: | + Dispute funds withdrawn. Emitted during the dispute sub-lifecycle of a + disputed payment. Payload is the dispute record; `data.payment.id` links + back to the disputed payment. + operationId: webhookDisputeFundsWithdrawn + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment.dispute.funds_withdrawn + category: + const: dispute_lifecycle + data: + $ref: '#/components/schemas/WebhookDisputeData' + responses: + '200': + description: Webhook acknowledged + payment.dispute.updated: + post: + summary: Dispute updated + description: | + Dispute updated. Emitted during the dispute sub-lifecycle of a disputed + payment. Payload is the dispute record; `data.payment.id` links back to + the disputed payment. + operationId: webhookDisputeUpdated + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment.dispute.updated + category: + const: dispute_lifecycle + data: + $ref: '#/components/schemas/WebhookDisputeData' + responses: + '200': + description: Webhook acknowledged + payment.disputed: + post: + summary: Payment disputed + description: | + Payment disputed. Emitted when a cardholder or issuer opens a dispute against + a previously successful payment. This is the first event in the dispute + sub-lifecycle; subsequent updates arrive as `payment.dispute.*`. Gather + evidence promptly — `evidence_due_by` in the payload is authoritative. + operationId: webhookDisputeCreated + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment.disputed + category: + const: dispute_lifecycle + data: + $ref: '#/components/schemas/WebhookDisputeData' + responses: + '200': + description: Webhook acknowledged + customer.action_required: + post: + summary: Customer action required + description: | + Customer action required. Emitted during the customer (subject) lifecycle. + Payload carries the customer's current status. Use + this to keep merchant-side customer records in sync. + operationId: webhookCustomerActionRequired + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: customer.action_required + category: + const: provider_registration_lifecycle + data: + $ref: '#/components/schemas/WebhookProviderRegistrationData' + responses: + '200': + description: Webhook acknowledged + customer.created: + post: + summary: Customer created + description: | + Customer created. Emitted during the customer (subject) lifecycle. Payload carries the customer's current status. Use this to keep + merchant-side customer records in sync. + operationId: webhookCustomerCreated + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: customer.created + category: + const: provider_registration_lifecycle + data: + $ref: '#/components/schemas/WebhookProviderRegistrationData' + responses: + '200': + description: Webhook acknowledged + customer.processing: + post: + summary: Customer processing + description: | + Customer processing. Emitted during the customer (subject) lifecycle. Payload carries the customer's current status. Use this to + keep merchant-side customer records in sync. + operationId: webhookCustomerProcessing + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: customer.processing + category: + const: provider_registration_lifecycle + data: + $ref: '#/components/schemas/WebhookProviderRegistrationData' + responses: + '200': + description: Webhook acknowledged + customer.rejected: + post: + summary: Customer rejected + description: | + Customer rejected. Emitted during the customer (subject) lifecycle. Payload carries the customer's current status. Use this to + keep merchant-side customer records in sync. + operationId: webhookCustomerRejected + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: customer.rejected + category: + const: provider_registration_lifecycle + data: + $ref: '#/components/schemas/WebhookProviderRegistrationData' + responses: + '200': + description: Webhook acknowledged + customer.updated: + post: + summary: Customer updated + description: | + Customer updated. Emitted during the customer (subject) lifecycle. Payload carries the customer's current status. Use this to keep + merchant-side customer records in sync. + operationId: webhookCustomerUpdated + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: customer.updated + category: + const: provider_registration_lifecycle + data: + $ref: '#/components/schemas/WebhookProviderRegistrationData' + responses: + '200': + description: Webhook acknowledged + customer.verified: + post: + summary: Customer verified + description: | + Customer verified. Emitted during the customer (subject) lifecycle. Payload carries the customer's current status. Use this to + keep merchant-side customer records in sync. + operationId: webhookCustomerVerified + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: customer.verified + category: + const: provider_registration_lifecycle + data: + $ref: '#/components/schemas/WebhookProviderRegistrationData' + responses: + '200': + description: Webhook acknowledged + kyc.action_required: + post: + summary: KYC action required + description: | + KYC action required. Emitted for KYC decisions on a customer. Payload is a + `WebhookProviderRegistrationData`; `data.readiness` indicates downstream + capability (e.g., payout-ready). Required fields on `action_required` are in + `data.failure_reason`. + operationId: webhookKycActionRequired + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: kyc.action_required + category: + const: provider_registration_lifecycle + data: + $ref: '#/components/schemas/WebhookProviderRegistrationData' + responses: + '200': + description: Webhook acknowledged + kyc.approved: + post: + summary: KYC approved + description: | + KYC approved. Emitted for KYC decisions on a customer. Payload is a + `WebhookProviderRegistrationData`; `data.readiness` indicates downstream + capability (e.g., payout-ready). Required fields on `action_required` are in + `data.failure_reason`. + operationId: webhookKycApproved + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: kyc.approved + category: + const: provider_registration_lifecycle + data: + $ref: '#/components/schemas/WebhookProviderRegistrationData' + responses: + '200': + description: Webhook acknowledged + kyc.rejected: + post: + summary: KYC rejected + description: | + KYC rejected. Emitted for KYC decisions on a customer. Payload is a + `WebhookProviderRegistrationData`; `data.readiness` indicates downstream + capability (e.g., payout-ready). Required fields on `action_required` are in + `data.failure_reason`. + operationId: webhookKycRejected + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: kyc.rejected + category: + const: provider_registration_lifecycle + data: + $ref: '#/components/schemas/WebhookProviderRegistrationData' + responses: + '200': + description: Webhook acknowledged + provider_registration.action_required: + post: + summary: Provider registration action required + description: | + Provider registration action required. Emitted during a customer's + registration flow at an upstream provider. `data.provider` identifies the + provider and `data.status` reflects the new state. Combined, + `provider_registration.*` events represent the full onboarding lifecycle on + each provider. + operationId: webhookProvRegActionRequired + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: provider_registration.action_required + category: + const: provider_registration_lifecycle + data: + $ref: '#/components/schemas/WebhookProviderRegistrationData' + responses: + '200': + description: Webhook acknowledged + provider_registration.approved: + post: + summary: Provider registration approved + description: | + Provider registration approved. Emitted during a customer's registration flow + at an upstream provider. `data.provider` identifies the provider and + `data.status` reflects the new state. Combined, `provider_registration.*` + events represent the full onboarding lifecycle on each provider. + operationId: webhookProvRegApproved + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: provider_registration.approved + category: + const: provider_registration_lifecycle + data: + $ref: '#/components/schemas/WebhookProviderRegistrationData' + responses: + '200': + description: Webhook acknowledged + provider_registration.awaiting_confirmation: + post: + summary: Provider registration awaiting confirmation + description: | + Provider registration awaiting confirmation. Emitted during a customer's + registration flow at an upstream provider. `data.provider` identifies the + provider and `data.status` reflects the new state. Combined, + `provider_registration.*` events represent the full onboarding lifecycle on + each provider. + operationId: webhookProvRegAwaitingConfirmation + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: provider_registration.awaiting_confirmation + category: + const: provider_registration_lifecycle + data: + $ref: '#/components/schemas/WebhookProviderRegistrationData' + responses: + '200': + description: Webhook acknowledged + provider_registration.documents_uploaded: + post: + summary: Provider registration documents uploaded + description: | + Provider registration documents uploaded. Emitted during a customer's + registration flow at an upstream provider. `data.provider` identifies the + provider and `data.status` reflects the new state. Combined, + `provider_registration.*` events represent the full onboarding lifecycle on + each provider. + operationId: webhookProvRegDocumentsUploaded + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: provider_registration.documents_uploaded + category: + const: provider_registration_lifecycle + data: + $ref: '#/components/schemas/WebhookProviderRegistrationData' + responses: + '200': + description: Webhook acknowledged + provider_registration.processing: + post: + summary: Provider registration processing + description: | + Provider registration processing. Emitted during a customer's registration + flow at an upstream provider. `data.provider` identifies the provider and + `data.status` reflects the new state. Combined, `provider_registration.*` + events represent the full onboarding lifecycle on each provider. + operationId: webhookProvRegProcessing + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: provider_registration.processing + category: + const: provider_registration_lifecycle + data: + $ref: '#/components/schemas/WebhookProviderRegistrationData' + responses: + '200': + description: Webhook acknowledged + provider_registration.rejected: + post: + summary: Provider registration rejected + description: | + Provider registration rejected. Emitted during a customer's registration flow + at an upstream provider. `data.provider` identifies the provider and + `data.status` reflects the new state. Combined, `provider_registration.*` + events represent the full onboarding lifecycle on each provider. + operationId: webhookProvRegRejected + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: provider_registration.rejected + category: + const: provider_registration_lifecycle + data: + $ref: '#/components/schemas/WebhookProviderRegistrationData' + responses: + '200': + description: Webhook acknowledged + provider_registration.restricted: + post: + summary: Provider registration restricted + description: | + Provider registration restricted. Emitted during a customer's registration + flow at an upstream provider. `data.provider` identifies the provider and + `data.status` reflects the new state. Combined, `provider_registration.*` + events represent the full onboarding lifecycle on each provider. + operationId: webhookProvRegRestricted + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: provider_registration.restricted + category: + const: provider_registration_lifecycle + data: + $ref: '#/components/schemas/WebhookProviderRegistrationData' + responses: + '200': + description: Webhook acknowledged + provider_registration.submitted: + post: + summary: Provider registration submitted + description: | + Provider registration submitted. Emitted during a customer's registration + flow at an upstream provider. `data.provider` identifies the provider and + `data.status` reflects the new state. Combined, `provider_registration.*` + events represent the full onboarding lifecycle on each provider. + operationId: webhookProvRegSubmitted + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: provider_registration.submitted + category: + const: provider_registration_lifecycle + data: + $ref: '#/components/schemas/WebhookProviderRegistrationData' + responses: + '200': + description: Webhook acknowledged + provider_registration.updated: + post: + summary: Provider registration updated + description: | + Provider registration updated. Emitted during a customer's registration flow + at an upstream provider. `data.provider` identifies the provider and + `data.status` reflects the new state. Combined, `provider_registration.*` + events represent the full onboarding lifecycle on each provider. + operationId: webhookProvRegUpdated + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: provider_registration.updated + category: + const: provider_registration_lifecycle + data: + $ref: '#/components/schemas/WebhookProviderRegistrationData' + responses: + '200': + description: Webhook acknowledged + provider_registration.verification_expired: + post: + summary: Provider registration verification expired + description: | + Provider registration verification expired. Emitted during a customer's + registration flow at an upstream provider. `data.provider` identifies the + provider and `data.status` reflects the new state. Combined, + `provider_registration.*` events represent the full onboarding lifecycle on + each provider. + operationId: webhookProvRegVerificationExpired + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: provider_registration.verification_expired + category: + const: provider_registration_lifecycle + data: + $ref: '#/components/schemas/WebhookProviderRegistrationData' + responses: + '200': + description: Webhook acknowledged + customer.sync.failed: + post: + summary: Customer sync failed + description: | + Customer sync failed. Emitted while a customer's profile is being + synchronized to an upstream provider. `data.attempt_id` identifies a single + sync attempt across started/succeeded/failed. Retries reuse the same + `attempt_id`. + operationId: webhookCustomerSyncFailed + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: customer.sync.failed + category: + const: customer_sync_lifecycle + data: + $ref: '#/components/schemas/WebhookCustomerSyncData' + responses: + '200': + description: Webhook acknowledged + customer.sync.started: + post: + summary: Customer sync started + description: | + Customer sync started. Emitted while a customer's profile is being + synchronized to an upstream provider. `data.attempt_id` identifies a single + sync attempt across started/succeeded/failed. Retries reuse the same + `attempt_id`. + operationId: webhookCustomerSyncStarted + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: customer.sync.started + category: + const: customer_sync_lifecycle + data: + $ref: '#/components/schemas/WebhookCustomerSyncData' + responses: + '200': + description: Webhook acknowledged + customer.sync.succeeded: + post: + summary: Customer sync succeeded + description: | + Customer sync succeeded. Emitted while a customer's profile is being + synchronized to an upstream provider. `data.attempt_id` identifies a single + sync attempt across started/succeeded/failed. Retries reuse the same + `attempt_id`. + operationId: webhookCustomerSyncSucceeded + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: customer.sync.succeeded + category: + const: customer_sync_lifecycle + data: + $ref: '#/components/schemas/WebhookCustomerSyncData' + responses: + '200': + description: Webhook acknowledged + payment_method.approved: + post: + summary: Payment method approved + description: | + Payment method approved. Emitted during the payment-method lifecycle on a + customer (card, bank account, wallet). Payload is a + `WebhookPaymentMethodData`; `data.type` narrows the shape of + provider-specific fields. + operationId: webhookPaymentMethodApproved + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment_method.approved + category: + const: payment_method_lifecycle + data: + $ref: '#/components/schemas/WebhookPaymentMethodData' + responses: + '200': + description: Webhook acknowledged + payment_method.created: + post: + summary: Payment method created + description: | + Payment method created. Emitted during the payment-method lifecycle on a + customer (card, bank account, wallet). Payload is a + `WebhookPaymentMethodData`; `data.type` narrows the shape of + provider-specific fields. + operationId: webhookPaymentMethodCreated + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment_method.created + category: + const: payment_method_lifecycle + data: + $ref: '#/components/schemas/WebhookPaymentMethodData' + responses: + '200': + description: Webhook acknowledged + payment_method.rejected: + post: + summary: Payment method rejected + description: | + Payment method rejected. Emitted during the payment-method lifecycle on a + customer (card, bank account, wallet). Payload is a + `WebhookPaymentMethodData`; `data.type` narrows the shape of + provider-specific fields. + operationId: webhookPaymentMethodRejected + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment_method.rejected + category: + const: payment_method_lifecycle + data: + $ref: '#/components/schemas/WebhookPaymentMethodData' + responses: + '200': + description: Webhook acknowledged + payment_method.updated: + post: + summary: Payment method updated + description: | + Payment method updated. Emitted during the payment-method lifecycle on a + customer (card, bank account, wallet). Payload is a + `WebhookPaymentMethodData`; `data.type` narrows the shape of + provider-specific fields. + operationId: webhookPaymentMethodUpdated + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment_method.updated + category: + const: payment_method_lifecycle + data: + $ref: '#/components/schemas/WebhookPaymentMethodData' + responses: + '200': + description: Webhook acknowledged + payment_method.verified: + post: + summary: Payment method verified + description: | + Payment method verified. Emitted during the payment-method lifecycle on a + customer (card, bank account, wallet). Payload is a + `WebhookPaymentMethodData`; `data.type` narrows the shape of + provider-specific fields. + operationId: webhookPaymentMethodVerified + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payment_method.verified + category: + const: payment_method_lifecycle + data: + $ref: '#/components/schemas/WebhookPaymentMethodData' + responses: + '200': + description: Webhook acknowledged + transfer.failed: + post: + summary: Transfer failed + description: | + Transfer failed. Emitted when an internal transfer (between + CrowdSplit-managed wallets) completes or fails. Payload is a + `WebhookTransactionData` with `type: transfer`. `data.source` and + `data.destination` identify the legs. + operationId: webhookTransferFailed + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: transfer.failed + category: + const: transfer_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + transfer.succeeded: + post: + summary: Transfer succeeded + description: | + Transfer succeeded. Emitted when an internal transfer (between + CrowdSplit-managed wallets) completes or fails. Payload is a + `WebhookTransactionData` with `type: transfer`. `data.source` and + `data.destination` identify the legs. + operationId: webhookTransferSucceeded + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: transfer.succeeded + category: + const: transfer_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + payout.succeeded: + post: + summary: Payout succeeded + description: | + Payout succeeded. Emitted when an outbound payout to a customer-owned account + succeeds at the upstream provider. Final state — no further events are + expected for the payout. Payload is the payout's `WebhookTransactionData`. + operationId: webhookPayoutSucceeded + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: payout.succeeded + category: + const: payout_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + buy.awaiting_confirmation: + post: + summary: Buy awaiting confirmation + description: | + Buy awaiting confirmation. Emitted during a buy (fiat → stablecoin) flow. + Payload is a `WebhookTransactionData` with `type: buy`; `data.source` is the + fiat leg and `data.destination` is the crypto leg. + `buy.awaiting_confirmation` is the intermediate state while waiting on + on-chain confirmation. + operationId: webhookBuyAwaitingConfirmation + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: buy.awaiting_confirmation + category: + const: buy_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + buy.completed: + post: + summary: Buy completed + description: | + Buy completed. Emitted during a buy (fiat → stablecoin) flow. Payload is a + `WebhookTransactionData` with `type: buy`; `data.source` is the fiat leg and + `data.destination` is the crypto leg. `buy.awaiting_confirmation` is the + intermediate state while waiting on on-chain confirmation. + operationId: webhookBuyCompleted + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: buy.completed + category: + const: buy_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + buy.succeeded: + post: + summary: Buy succeeded + description: | + Buy succeeded. Emitted during a buy (fiat → stablecoin) flow. Payload is a + `WebhookTransactionData` with `type: buy`; `data.source` is the fiat leg and + `data.destination` is the crypto leg. `buy.awaiting_confirmation` is the + intermediate state while waiting on on-chain confirmation. + operationId: webhookBuySucceeded + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: buy.succeeded + category: + const: buy_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + sell.failed: + post: + summary: Sell failed + description: | + Sell failed. Emitted during a sell (stablecoin → fiat) flow. Payload is a + `WebhookTransactionData` with `type: sell`. Use `data.provider_response` to + access provider-specific status codes on failure. + operationId: webhookSellFailed + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: sell.failed + category: + const: sell_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + sell.succeeded: + post: + summary: Sell succeeded + description: | + Sell succeeded. Emitted during a sell (stablecoin → fiat) flow. Payload is a + `WebhookTransactionData` with `type: sell`. Use `data.provider_response` to + access provider-specific status codes on failure. + operationId: webhookSellSucceeded + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: sell.succeeded + category: + const: sell_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + external.deposit.awaiting_confirmation: + post: + summary: External deposit awaiting confirmation + description: | + External deposit awaiting confirmation. Emitted during the external-deposit + lifecycle (funds arriving from outside CrowdSplit, e.g., a bank wire or + on-chain transfer to a virtual account). Payload is a + `WebhookTransactionData` with `type: external_deposit`; reconcile against + bank or on-chain records. + operationId: webhookExternalDepositAwaitingConfirmation + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: external.deposit.awaiting_confirmation + category: + const: externally_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + external.deposit.completed: + post: + summary: External deposit completed + description: | + External deposit completed. Emitted during the external-deposit lifecycle + (funds arriving from outside CrowdSplit, e.g., a bank wire or on-chain + transfer to a virtual account). Payload is a `WebhookTransactionData` with + `type: external_deposit`; reconcile against bank or on-chain records. + operationId: webhookExternalDepositCompleted + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: external.deposit.completed + category: + const: externally_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + external.deposit.received: + post: + summary: External deposit received + description: | + External deposit received. Emitted during the external-deposit lifecycle + (funds arriving from outside CrowdSplit, e.g., a bank wire or on-chain + transfer to a virtual account). Payload is a `WebhookTransactionData` with + `type: external_deposit`; reconcile against bank or on-chain records. + operationId: webhookExternalDepositReceived + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: external.deposit.received + category: + const: externally_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + external.deposit.succeeded: + post: + summary: External deposit succeeded + description: | + External deposit succeeded. Emitted during the external-deposit lifecycle + (funds arriving from outside CrowdSplit, e.g., a bank wire or on-chain + transfer to a virtual account). Payload is a `WebhookTransactionData` with + `type: external_deposit`; reconcile against bank or on-chain records. + operationId: webhookExternalDepositSucceeded + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: external.deposit.succeeded + category: + const: externally_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged + external.virtual_account.funded: + post: + summary: External virtual account funded + description: | + External virtual account funded. Emitted when a virtual account assigned to a + customer receives a deposit from outside the CrowdSplit platform. Payload + identifies the receiving customer and the funding amount. This does not by + itself credit a wallet — use `external.deposit.*` events for the subsequent + credit lifecycle. + operationId: webhookExternalVirtualAccountFunded + tags: + - Webhook Events + parameters: + - $ref: '#/components/parameters/CrowdSplitSignatureHeader' + - $ref: '#/components/parameters/CrowdSplitTimestampHeader' + - $ref: '#/components/parameters/CrowdSplitNotificationIdHeader' + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/WebhookEnvelope' + - type: object + properties: + event: + const: external.virtual_account.funded + category: + const: externally_lifecycle + data: + $ref: '#/components/schemas/WebhookTransactionData' + responses: + '200': + description: Webhook acknowledged +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: | + Merchant authentication token. Obtained via POST /api/v1/merchant/token/grant. + Pass as: Authorization: Bearer + accessTokenAuth: + type: apiKey + in: header + name: x-access-token + description: | + User JWT access token for user-level authentication. + Used primarily for admin and internal operations. + refAppKeyAuth: + type: apiKey + in: header + name: Ref-App-Key + description: | + Subscription service authentication key. + Used with Payment-Type: recurring_payment header for subscription payments. + schemas: + ApiResponse: + type: object + properties: + msg: + type: string + description: Human-readable message describing the result + data: + description: Response payload (null on error) + required: + - msg + - data + ApiErrorResponse: + type: object + properties: + msg: + type: string + description: Human-readable error message + data: + type: 'null' + description: Always null for errors + provider_message: + type: string + description: Optional error message from the underlying payment provider + required: + - msg + - data + BillingAddress: + type: object + properties: + house_number: + type: string + street_number: + type: string + street_name: + type: string + postal_code: + type: string + city: + type: string + state: + type: string + country_code: + type: string + CreatePaymentRequest: + type: object + description: | + Payment creation request. The exact schema varies by provider and payment method type. + Fields like provider, currency, and type are case-insensitive (uppercased internally). + required: + - provider + - source + properties: + provider: + type: string + description: Payment provider + enum: + - stripe + source: + type: object + required: + - amount + - currency + - payment_method + properties: + amount: + type: integer + minimum: 1 + description: Amount in smallest currency unit (e.g. cents) + currency: + type: string + description: ISO 4217 currency code + enum: + - usd + - brl + - cop + - jpy + customer: + type: object + properties: + id: + type: string + format: uuid + description: CrowdSplit customer ID + payment_method: + type: object + required: + - type + properties: + type: + type: string + enum: + - card + id: + type: string + format: uuid + description: Saved payment method ID (for card payments) + card_token: + type: string + description: One-time card token from provider + expiry_date: + type: string + format: date-time + description: PIX QR code expiry (required for PIX payments) + billing_address: + $ref: '#/components/schemas/BillingAddress' + capture_method: + type: string + enum: + - automatic + - manual + description: Whether to capture immediately or manually later + fraud_check: + type: object + properties: + enabled: + type: boolean + description: Fraud check is currently not available. Must be set to false. + installments: + type: integer + minimum: 0 + description: Number of installments (Stripe) + float_rate: + type: number + description: Float rate for installment payments (Stripe) + destination: + type: object + description: Destination details for cross-currency or split payments + properties: + amount: + type: integer + minimum: 1 + currency: + type: string + customer: + type: object + properties: + id: + type: string + format: uuid + fee: + type: object + description: Fee configuration for destination flow + properties: + bearer: + type: string + enum: + - platform + - connected_account + default: platform + flow: + type: string + enum: + - platform + - destination + default: platform + description: Payment flow type (Stripe) + allocations: + type: array + description: Fund allocation splits (for platform flow) + items: + type: object + properties: + type: + type: string + default: uncategorized + receiver: + type: object + properties: + type: + type: string + enum: + - platform + - connected_account + id: + type: string + format: uuid + amount: + type: integer + minimum: 1 + total_installments: + type: integer + minimum: 1 + description: Total installment count + confirm: + type: boolean + description: Whether to auto-confirm the payment + metadata: + type: object + description: | + Merchant-supplied arbitrary metadata, stored with the + transaction and echoed back on the response and on any + derived webhook deliveries. Shape is defined by the merchant. + additionalProperties: true + PaymentResponse: + type: object + description: Payment transaction response object + properties: + id: + type: string + format: uuid + description: Transaction unique ID + status: + type: string + description: Current transaction status (lowercased in response) + enum: + - initiated + - awaiting_confirmation + - processing + - captured + - succeeded + - failed + - canceled + type: + type: string + description: Transaction type (lowercased in response) + enum: + - payment + - refund + provider: + type: string + description: Payment provider (lowercased in response) + source: + type: object + description: Source details (amount + currency + customer + payment method). + properties: + amount: + type: integer + description: Amount in minor units (cents). + currency: + type: string + description: ISO 4217 currency code (lowercased on the wire). + payment_method: + type: object + description: Payment method used on the source leg. Extra keys vary by provider. + properties: + id: + type: string + format: uuid + description: Payment method unique ID. + type: + type: string + description: Payment method type (lowercased — card, pix, bank, …). + chain: + type: string + description: Blockchain network when the method is on-chain (lowercased). + additionalProperties: true + customer: + type: object + properties: + id: + type: string + format: uuid + description: Customer (subject) unique ID. + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + settlement_date: + type: + - string + - 'null' + format: date-time + metadata: + type: object + nullable: true + description: | + Merchant-supplied arbitrary metadata, echoed back unchanged on + the response and on any derived webhook deliveries. Shape is + defined by the merchant at request time. + additionalProperties: true + CreateMerchantRequest: + type: object + required: + - legal_name + - address + - website + - tax_number + properties: + legal_name: + type: string + description: Legal business name + address: + type: string + description: Business address + website: + type: string + description: Official website URL + tax_number: + type: string + description: Tax identification number + MerchantResponse: + type: object + properties: + id: + type: integer + legalName: + type: string + address: + type: string + website: + type: string + taxNumber: + type: string + appId: + type: string + description: Generated application ID + isApproved: + type: boolean + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + GrantTokenRequest: + type: object + required: + - client_id + - client_secret + - grant_type + properties: + client_id: + type: string + description: App ID from merchant creation + client_secret: + type: string + description: Secret key (shown only at creation) + grant_type: + type: string + enum: + - client_credentials + MerchantTokenResponse: + type: object + properties: + access_token: + type: string + description: JWT access token for merchant API calls + token_type: + type: string + enum: + - Bearer + expires_in: + type: integer + description: Token expiry time in milliseconds + required: + - access_token + - token_type + - expires_in + TransferDateRequest: + type: object + required: + - settlementDate + - region + - config + - provider + properties: + settlementDate: + type: string + format: date + description: ISO 8601 date string + region: + type: string + description: 2-letter ISO 3166-1 alpha-2 country code + config: + type: object + required: + - weekends + - holidays + properties: + weekends: + type: array + items: + type: integer + minimum: 0 + maximum: 6 + description: Day numbers (0=Sunday, 6=Saturday) + holidays: + type: array + items: + type: string + format: date + description: ISO 8601 date strings + provider: + type: string + description: Holiday provider name + WebhookResponse: + type: object + properties: + id: + type: string + format: uuid + url: + type: string + description: + type: string + isActive: + type: boolean + secret: + type: string + description: Webhook signing secret (only returned on registration) + RegisterWebhookRequest: + type: object + required: + - url + properties: + url: + type: string + maxLength: 255 + description: Webhook endpoint URL + description: + type: string + maxLength: 255 + description: Webhook description + UpdateWebhookRequest: + type: object + properties: + url: + type: string + maxLength: 255 + description: + type: string + maxLength: 255 + WebhookNotification: + type: object + properties: + notificationId: + type: string + format: uuid + data: + type: object + properties: + type: + type: string + description: Event type (e.g. payment.succeeded) + id: + type: string + format: uuid + isAcknowledged: + type: boolean + CustomerResponse: + type: object + properties: + id: + type: string + format: uuid + document_number: + type: string + tax_id: + type: string + document_type: + type: string + description: Returned lowercased + email: + type: string + format: email + social_name: + type: string + first_name: + type: string + last_name: + type: string + dob: + type: string + format: date-time + house_number: + type: + - string + - 'null' + street_number: + type: + - string + - 'null' + street_name: + type: + - string + - 'null' + postal_code: + type: + - string + - 'null' + city: + type: + - string + - 'null' + state: + type: + - string + - 'null' + phone_country_code: + type: string + phone_area_code: + type: string + phone_number: + type: string + monthly_net_income: + type: + - string + - 'null' + gender: + type: + - string + - 'null' + description: Returned lowercased + country_code: + type: string + description: Returned lowercased + roles: + type: array + items: + type: string + wallet_address: + type: + - string + - 'null' + CreateCustomerRequest: + type: object + description: | + Customer registration request. Fields document_type, country_code, and gender + are case-insensitive (uppercased internally, returned lowercased). + properties: + document_number: + type: string + description: Government document number + document_type: + type: string + description: Document type (case-insensitive) + enum: + - cpf + - cnpj + - passport + - ssn + - itin + - cc + - curp + - personal_tax_id + - company_tax_id + email: + type: string + format: email + first_name: + type: string + last_name: + type: string + tax_id: + type: string + dob: + type: string + format: date + description: Date of birth (ISO 8601) + phone_country_code: + type: string + phone_area_code: + type: string + phone_number: + type: string + street_number: + type: string + street_name: + type: string + house_number: + type: string + postal_code: + type: string + city: + type: string + state: + type: string + country_code: + type: string + description: ISO country code (case-insensitive) + subdivision: + type: string + description: ISO subdivision code + gender: + type: string + enum: + - male + - female + - other + description: Gender (case-insensitive) + mother_name: + type: string + monthly_net_income: + type: string + owner_legal_name: + type: string + owner_document_number: + type: string + owner_document_type: + type: string + company_name: + type: string + company_start_date: + type: string + wallet_address: + type: string + SyncCustomerRequest: + type: object + required: + - providers + - fields + properties: + providers: + type: array + items: + type: string + minItems: 1 + maxItems: 1 + description: Single provider name to sync with + fields: + type: array + items: + type: string + minItems: 1 + description: Fields to sync (from syncable fields list) + BalanceResponse: + type: object + properties: + as_of: + type: string + format: date-time + filters: + type: object + properties: + customer_id: + type: string + format: uuid + provider: + type: string + role: + type: string + balances: + type: array + items: + type: object + properties: + provider: + type: string + role: + type: string + available_balance: + type: number + pending_balance: + type: number + currency: + type: string + PaymentMethodResponse: + type: object + properties: + id: + type: string + format: uuid + type: + type: string + description: Returned lowercased + status: + type: string + description: Returned lowercased + provider: + type: string + description: Returned lowercased + chain: + type: + - string + - 'null' + currency: + type: + - string + - 'null' + source_currency: + type: + - string + - 'null' + destination_currency: + type: + - string + - 'null' + bank_account_type: + type: + - string + - 'null' + bankDetails: + type: + - object + - 'null' + properties: + accountNumber: + type: string + description: Masked account number + accountName: + type: string + accountType: + type: string + bankName: + type: string + branchCode: + type: string + ispb: + type: string + CreatePaymentMethodRequest: + type: object + description: | + Create a payment method for a customer. Fields type, provider, currency, + chain, and bank_account_type are case-insensitive. + required: + - type + properties: + type: + type: string + description: Payment method type (case-insensitive) + enum: + - bank + - card + - pix + - customer_wallet + - liquidation_address + provider: + type: string + description: Provider name (defaults to crowd_split for bank/pix/customer_wallet) + currency: + type: string + description: Currency code + source_currency: + type: string + destination_currency: + type: string + payment_rail: + type: string + bank_account_type: + type: string + enum: + - checking + - savings + - payment + chain: + type: string + description: Blockchain chain + external_account_id: + type: string + description: Required for liquidation_address type + bank_details: + type: object + description: Required for bank type + properties: + pix_string: + type: string + branch_code: + type: string + account_number: + type: string + account_name: + type: string + bank_name: + type: string + account_type: + type: string + enum: + - checking + - savings + - payment + bank_code: + type: string + ispb: + type: string + country: + type: string + routing_number: + type: string + TransactionResponse: + type: object + description: Transaction object (field casing in response is lowercased for status, type, provider, currency) + properties: + id: + type: string + format: uuid + status: + type: string + description: Transaction status (lowercased) + type: + type: string + description: Transaction type (lowercased) + provider: + type: string + description: Provider name (lowercased) + source: + type: object + description: Source details (amount + currency + customer + payment method). + properties: + amount: + type: integer + description: Amount in minor units (cents). + currency: + type: string + description: ISO 4217 currency code (lowercased). + customer: + type: object + properties: + id: + type: string + format: uuid + description: Customer (subject) unique ID. + payment_method: + type: object + description: Payment method used on the source leg. + properties: + id: + type: string + format: uuid + type: + type: string + description: Payment method type (lowercased). + chain: + type: string + description: Blockchain network (lowercased). + additionalProperties: true + destination: + type: object + description: Destination details (when applicable — e.g. for transfers, buy/sell, refunds). + nullable: true + properties: + amount: + type: integer + description: Amount in minor units (cents). + currency: + type: string + description: ISO 4217 currency code (lowercased). + customer: + type: object + properties: + id: + type: string + format: uuid + description: Destination customer (subject) unique ID. + payment_method: + type: object + description: Payment method used on the destination leg. + properties: + id: + type: string + format: uuid + type: + type: string + description: Payment method type (lowercased). + chain: + type: string + description: Blockchain network (lowercased). + additionalProperties: true + metadata: + type: object + nullable: true + description: | + Merchant-supplied arbitrary metadata, echoed back unchanged. + Shape is defined by the merchant at request time. + additionalProperties: true + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + settlement_date: + type: + - string + - 'null' + format: date-time + DisputeResponse: + type: object + properties: + id: + type: string + format: uuid + status: + type: string + transaction_id: + type: string + format: uuid + amount: + type: integer + currency: + type: string + reason: + type: string + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + PlanResponse: + type: object + properties: + hash_id: + type: string + name: + type: string + description: + type: string + frequency: + type: integer + price: + type: integer + overridden_price: + type: integer + is_active: + type: boolean + start_time: + type: string + format: date-time + end_time: + type: string + format: date-time + is_auto_renewable: + type: boolean + currency: + type: string + allow_amount_override: + type: boolean + campaign_id: + type: string + campaign_name: + type: string + created_by: + type: string + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + CreatePlanRequest: + type: object + required: + - name + - description + - frequency + - price + - currency + - created_by + properties: + name: + type: string + description: + type: string + frequency: + type: integer + description: Billing frequency in days + price: + type: integer + description: Price amount + currency: + type: string + created_by: + type: string + overridden_price: + type: integer + start_time: + type: string + format: date + end_time: + type: string + format: date + is_auto_renewable: + type: boolean + allow_amount_override: + type: boolean + campaign_id: + type: string + campaign_name: + type: string + SubscriptionResponse: + type: object + properties: + hash_id: + type: string + start_time: + type: string + format: date-time + end_time: + type: string + format: date-time + plan_hash_id: + type: string + auto_renew: + type: boolean + status: + type: string + enum: + - active + - pending_activation + - canceled + - expired + - queued + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + CountryResponse: + type: object + description: | + A country known to CrowdSplit, with a flag indicating whether KYC is + currently supported (Stripe Connect card-payments capability + Bridge + allowed). Source list: RCS-453. + required: + - iso_alpha_2 + - name + - kyc_supported + properties: + iso_alpha_2: + type: string + description: ISO 3166-1 alpha-2 country code (two uppercase letters). + example: US + name: + type: string + description: Country display name. + example: United States + kyc_supported: + type: boolean + description: | + True when CrowdSplit currently accepts this country for KYC + (mutual Stripe ∩ Bridge support). + WebhookEnvelope: + type: object + description: | + Base envelope delivered to merchant webhook endpoints. Every webhook + request body has the shape `{ id, api_version, event, category, data }`; + concrete event schemas compose this base via `allOf` and refine `event` / + `category` to their `const` values and `data` to a specific schema. + + Each delivery carries three headers: + CrowdSplit-Signature `t={unix-ts},v1={hex-hmac-sha256}` + CrowdSplit-Timestamp seconds since epoch + CrowdSplit-Notification-Id unique per delivery (idempotency) + + Merchants MUST verify the HMAC of the raw body using the webhook + secret returned at registration, and SHOULD reject the delivery if + `CrowdSplit-Timestamp` differs from the current time by more than + 5 minutes. Duplicate `CrowdSplit-Notification-Id` values indicate a + retry of a previously dispatched delivery and should be deduplicated. + required: + - id + - api_version + - event + - category + - data + properties: + id: + type: string + format: uuid + description: Unique notification ID (stable across retry attempts). + api_version: + type: string + pattern: ^v[0-9]+$ + description: | + API version of the payload builder that produced this event + (currently always `v1`). Reports the version of the code that built + `data` — not a globally-bumped number — so when one event type later + moves to v2, only that event reports `v2`. Branch on this field if + you need to handle multiple payload shapes for the same event. + example: v1 + event: + type: string + description: Event name; specialized via `allOf` per operation (e.g. `payment.succeeded`). + category: + type: string + description: Event category; specialized via `allOf` per operation (e.g. `payment_lifecycle`). + data: + type: object + description: Event-specific payload; each operation refines this via `allOf` to a concrete schema. + ProviderName: + type: string + description: | + CrowdSplit-managed upstream provider identifier. Lowercased in every + merchant-facing payload. + + Note: merchant-facing `*.provider` fields emit `avenia` in place + of `brla` in the lowercased form; consumers should treat the two + as equivalent for now. + enum: + - stripe + - bridge + - wallet_service + - crowd_split + TransactionStatus: + type: string + description: | + Transaction lifecycle status (lowercased on the wire). + enum: + - created + - awaiting_confirmation + - processing + - captured + - succeeded + - failed + - canceled_after_completion + - canceled + WebhookTransactionData: + type: object + description: Data payload for payment, refund, transfer, buy, sell, payout, and external events + required: + - id + - provider + - status + - source + - created_at + - updated_at + properties: + id: + type: string + format: uuid + description: Transaction unique ID + provider: + $ref: '#/components/schemas/ProviderName' + status: + $ref: '#/components/schemas/TransactionStatus' + source: + type: object + description: Source details (amount, currency, customer, payment_method). + properties: + amount: + type: integer + description: Amount in minor units (cents). + currency: + type: string + description: ISO 4217 currency code (lowercased on the wire). + customer: + type: object + properties: + id: + type: string + format: uuid + description: Customer (subject) unique ID. + payment_method: + type: object + description: Payment method used on the source leg. Extra keys vary by provider. + properties: + id: + type: string + format: uuid + description: Payment method unique ID. + type: + type: string + description: Payment method type (lowercased — card, pix, bank, …). + chain: + type: string + description: Blockchain network when the method is on-chain (lowercased). + additionalProperties: true + capture_method: + type: string + description: Capture strategy (lowercased — e.g. automatic, manual). + fraud_check: + type: object + description: | + Fraud-check configuration and result (when applicable). Present + only on transaction types that route through fraud screening. + properties: + provider: + type: string + description: Fraud-check provider identifier (lowercased). + config: + type: object + properties: + threshold: + type: string + description: Fraud score threshold applied (lowercased identifier). + sequence: + type: string + description: Sequence strategy identifier (lowercased). + additionalProperties: true + destination: + type: object + description: Destination details (if applicable). + nullable: true + properties: + amount: + type: integer + description: Amount in minor units (cents). + currency: + type: string + description: ISO 4217 currency code (lowercased). + customer: + type: object + properties: + id: + type: string + format: uuid + description: Destination customer (subject) unique ID. + payment_method: + type: object + description: Payment method used on the destination leg. + properties: + id: + type: string + format: uuid + type: + type: string + description: Payment method type (lowercased). + chain: + type: string + description: Blockchain network (lowercased). + additionalProperties: true + fee: + type: object + nullable: true + description: | + Platform / provider fee charged on this transaction, when present. + Shape originates from the merchant's request body. + properties: + bearer: + type: string + description: | + Who bears the fee. Stripe connected-account flows require + `connected_account`; may be absent for other providers or + flow types. + amount: + type: integer + description: Fee amount in minor units (cents). + currency: + type: string + description: Fee currency (lowercased). + additionalProperties: true + reference_transaction_id: + type: + - string + - 'null' + format: uuid + description: Parent transaction ID (for refunds) + total_installments: + type: + - integer + - 'null' + installments: + type: + - array + - 'null' + items: + type: object + properties: + number: + type: integer + amount: + type: integer + status: + type: string + settlement_date: + type: string + format: date-time + settlement_date: + type: + - string + - 'null' + format: date-time + metadata: + type: object + nullable: true + description: | + Merchant-supplied arbitrary metadata, echoed back unchanged. + Shape is defined by the merchant at request time. + additionalProperties: true + provider_response: + type: object + nullable: true + description: | + Raw excerpt of the upstream provider's response. Key set varies + by provider and by call — do not rely on specific keys. Useful + for debugging and for forwarding provider-specific error codes. + additionalProperties: true + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + WebhookDisputeData: + type: object + description: Data payload for dispute events + required: + - id + - status + properties: + id: + type: string + format: uuid + description: Dispute unique ID + status: + type: string + evidence_due_by: + type: + - string + - 'null' + format: date-time + evidences: + type: array + description: | + Attached dispute evidence records. Shape follows the merchant's + submission via `POST /api/v1/disputes/{dispute_id}/evidence`; + keys match those accepted by that endpoint. + items: + type: object + additionalProperties: true + payment: + type: object + description: Associated payment transaction (summary fields only). + properties: + id: + type: string + format: uuid + description: Payment transaction UID. + type: + type: string + description: Transaction type (lowercased — e.g. `payment`). + status: + type: string + description: Payment's current status (lowercased). + NormalizedFailureReason: + type: object + description: | + A single normalized rejection reason in a `failure_reasons` array. + Codes are stable across providers; new codes may be added as more + provider rejection types are mapped — treat unrecognised codes as + `unknown`. + required: + - code + properties: + code: + type: string + description: | + Stable normalized failure code. Current values: + `verification_failed`, `expired_document`, `duplicate_data`, + `invalid_input`, `compliance_review`, `provider_rejected`, + `requirements_incomplete`, `missing_required_information`, + `unknown`. + message: + type: string + description: Human-readable explanation from the provider. + fields: + type: array + items: + type: string + description: | + Affected field names (e.g. `identity_document`, `address`). + Only present when the code identifies specific fields. + endorsement: + type: string + description: | + Endorsement name (e.g. `base`, `sepa`). Only present when + `code` is `requirements_incomplete` and the failure relates + to an endorsement requirement. + WebhookProviderRegistrationData: + type: object + description: Data payload for customer, KYC, and provider registration events + required: + - id + - status + - created_at + - updated_at + properties: + id: + type: string + format: uuid + description: Customer unique ID + status: + type: string + description: Registration status (lowercased) + provider: + $ref: '#/components/schemas/ProviderName' + target_role: + type: string + description: Target role (lowercased) + provider_response: + type: object + nullable: true + description: | + Raw excerpt of the provider's registration response. Shape + varies by provider — do not rely on specific keys. + additionalProperties: true + failure_reasons: + type: + - array + - 'null' + description: | + Structured rejection reasons. Present when the provider rejects + a customer or KYC step, or when endorsement requirements are + incomplete. Null when no failures are recorded. + items: + $ref: '#/components/schemas/NormalizedFailureReason' + readiness: + type: + - string + - 'null' + description: Provider readiness status + changes: + type: + - array + - 'null' + description: | + Field names whose values changed in this event, relative to the + previous emitted state. Useful for merchants that want to + react only to specific field transitions. + items: + type: string + action_required: + type: + - boolean + - 'null' + description: | + Whether merchant or customer action is required. True for + recoverable rejections and approvals with pending future + requirements; false for terminal rejections (e.g. offboarded, + sanctions-blocked). + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + WebhookCustomerSyncData: + type: object + description: Data payload for customer sync events + required: + - id + - provider + - fields + - attempt_id + properties: + id: + type: string + format: uuid + description: Customer unique ID + provider: + $ref: '#/components/schemas/ProviderName' + fields: + type: array + items: + type: string + description: Fields being synced + attempt_id: + type: string + description: Sync attempt identifier + synced_at: + type: + - string + - 'null' + format: date-time + description: Timestamp of successful sync (only on success) + provider_data: + type: object + nullable: true + description: | + Data returned from the provider (only on success). Shape varies + by provider — do not rely on specific keys. + additionalProperties: true + error: + type: + - string + - 'null' + description: Error message (only on failure) + PaymentMethodType: + type: string + description: | + Payment-method type identifier (lowercased). + enum: + - bank + - card + - plaid + - virtual_account + - liquidation_address + - trading_wallet + - customer_wallet + - pix + - evm_address + WebhookPaymentMethodData: + type: object + description: Data payload for payment method events + required: + - id + - customer_id + - provider + - status + - type + - created_at + - updated_at + properties: + id: + type: string + format: uuid + description: Payment method unique ID + customer_id: + type: string + format: uuid + provider: + $ref: '#/components/schemas/ProviderName' + status: + type: string + description: Payment method status (lowercased) + type: + $ref: '#/components/schemas/PaymentMethodType' + country: + type: + - string + - 'null' + chain: + type: + - string + - 'null' + currency: + type: + - string + - 'null' + bank_account_type: + type: + - string + - 'null' + source_currency: + type: + - string + - 'null' + description: | + Source-leg currency on payment methods that mediate a currency + conversion (lowercased). Present only for cross-currency methods. + destination_currency: + type: + - string + - 'null' + description: | + Destination-leg currency on payment methods that mediate a + currency conversion (lowercased). + metadata: + type: object + nullable: true + description: | + Merchant-supplied arbitrary metadata for the payment method. + Shape is defined by the merchant at registration. + additionalProperties: true + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + additionalProperties: true + responses: + ValidationError: + description: Request validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + example: + msg: Validation error message + data: null + UnauthorizedError: + description: Authentication credentials are missing or invalid + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + example: + msg: Unauthorized + data: null + ForbiddenError: + description: Authenticated but not authorized for this resource + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + example: + msg: Forbidden + data: null + NotFoundError: + description: Requested resource not found + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + example: + msg: Resource not found + data: null + parameters: + RefAppKeyHeader: + name: Ref-App-Key + in: header + required: false + description: App key for subscription service authentication + schema: + type: string + PaymentTypeHeader: + name: Payment-Type + in: header + required: false + description: Set to 'recurring_payment' for subscription payment flows + schema: + type: string + enum: + - recurring_payment + LimitQuery: + name: limit + in: query + required: false + description: Number of records to return per page + schema: + type: integer + minimum: 1 + default: 10 + OffsetQuery: + name: offset + in: query + required: false + description: Number of records to skip + schema: + type: integer + minimum: 0 + default: 0 + CrowdSplitSignatureHeader: + name: CrowdSplit-Signature + in: header + required: true + description: | + HMAC-SHA256 signature over the raw request body, signed with the + merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + schema: + type: string + example: t=1713456789,v1=a1b2c3d4e5f6abcdef1234567890abcdef1234567890abcdef1234567890abcd + CrowdSplitTimestampHeader: + name: CrowdSplit-Timestamp + in: header + required: true + description: | + Unix timestamp (seconds since epoch) at dispatch. Merchants should + reject the delivery if the skew against their own clock exceeds + 5 minutes, to limit replay windows. + schema: + type: integer + example: 1713456789 + CrowdSplitNotificationIdHeader: + name: CrowdSplit-Notification-Id + in: header + required: true + description: | + Unique delivery ID. Stable across retries of the same logical event + — use it to deduplicate idempotently on receipt. + schema: + type: string + format: uuid + example: a1b2c3d4-e5f6-7890-abcd-ef1234567890 + headers: {} +x-tagGroups: + - name: Authentication + tags: + - Auth + - name: Merchant Account + tags: + - Merchants + - Subscriptions + - Webhooks + - name: Customers & KYC + tags: + - Customers + - Payment Methods + - Provider Registration + - name: Payment Flows + tags: + - Payments + - Payouts + - Transfers + - Buy / Sell + - Wallets + - name: Operations + tags: + - Transactions + - Disputes + - Tax + - Files + - Generic + - System + - name: Webhook Catalog + tags: + - Webhook Events diff --git a/packages/payments/package.json b/packages/payments/package.json index 050d8bd1..79e20bc1 100644 --- a/packages/payments/package.json +++ b/packages/payments/package.json @@ -1,6 +1,6 @@ { "name": "@oaknetwork/payments-sdk", - "version": "1.1.0", + "version": "1.5.0", "description": "A fully-typed TypeScript SDK for the Oak Network payment API.", "keywords": [ "sdk", @@ -48,6 +48,7 @@ "dotenv": "^17.2.1", "jest": "^30.0.5", "nock": "^14.0.10", + "openapi-typescript": "7.13.0", "ts-jest": "^29.4.6", "ts-node": "^10.9.2", "typescript": "^5.5.4" diff --git a/packages/payments/src/generated/api.ts b/packages/payments/src/generated/api.ts new file mode 100644 index 00000000..484c567f --- /dev/null +++ b/packages/payments/src/generated/api.ts @@ -0,0 +1,9768 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/api/auth/signup": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * User sign up + * @description Register a new user account with email and password. A verification email is sent upon successful registration. + */ + post: operations["signup"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/auth/signup/verify-email": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Verify email address + * @description Verifies the user's email address using a token sent during signup. + */ + get: operations["verifySignupEmail"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/auth/signup/verify-email/resend": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Resend verification email + * @description Resends the email verification link to the specified email address. + */ + post: operations["resendVerificationEmail"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/auth/signin": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Sign in + * @description Authenticate a user with email, password, and role. Returns JWT access and refresh tokens. + */ + post: operations["signin"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/auth/token/refresh": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Refresh access token + * @description Generate a new access token and refresh token using a valid refresh token. + */ + post: operations["refreshToken"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/auth/token/validate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Validate access token + * @description Check whether an access token is valid and not expired. Token can be passed via x-access-token header or query parameter. + */ + post: operations["validateToken"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/auth/reset-password": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Request password reset + * @description Send a password reset link to the provided email address. + */ + post: operations["resetPassword"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/auth/reset-password/token-verify": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Verify password reset token + * @description Check whether a password reset token is valid and not expired. + */ + get: operations["verifyResetPasswordToken"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/auth/update-password": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Update password + * @description Set a new password using a valid reset password token. + */ + post: operations["updatePassword"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/payments": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create a payment + * @description Create a payment intent with the provided details. Supports multiple payment providers + * (Stripe) and payment methods (Card). + * + * The request body structure varies by provider and payment method type. + * Field casing note: `provider`, `currency`, `payment_method.type`, and `capture_method` + * are case-insensitive on input (automatically uppercased internally). + */ + post: operations["createPayment"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/payments/{id}/confirm": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Confirm a payment + * @description Confirm a previously created payment intent that was not auto-confirmed. + */ + post: operations["confirmPayment"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/payments/{id}/capture": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Capture a payment + * @description Capture a previously authorized payment (for manual capture_method payments). + */ + post: operations["capturePayment"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/payments/{id}/cancel": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Cancel a payment + * @description Cancel a payment that is in INITIATED status. Only PAYMENT type transactions + * can be cancelled; other transaction types will return an error. + */ + post: operations["cancelPayment"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/payments/{id}/refund": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Refund a payment + * @description Create a refund for a completed payment. Partial refunds are supported by + * specifying an amount less than the original. Total refund amount (including + * previous refunds) cannot exceed the original transaction amount. + */ + post: operations["refundPayment"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/merchant": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create a merchant account + * @description Create a new merchant with the provided business details. + */ + post: operations["createMerchant"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/merchant/key/create": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Create merchant API keys + * @description Generate API key pair (public + secret) for a merchant. The secret key is only shown once. + */ + get: operations["createMerchantKey"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/merchant/token/grant": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Grant merchant token + * @description Authenticate using client credentials to obtain a Bearer token for API access. + * This is the primary authentication method for merchant API calls. + */ + post: operations["grantMerchantToken"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/merchant/token/refresh": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Refresh merchant token + * @description Generate new access and refresh tokens using a valid refresh token. + */ + post: operations["refreshMerchantToken"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/merchant/util/transfer-date": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Calculate transfer date + * @description Calculate the next available transfer date based on settlement date, region, and holiday configuration. + */ + post: operations["calculateTransferDate"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/merchant/webhooks": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List webhooks + * @description List all registered webhooks for the authenticated merchant. + */ + get: operations["listWebhooks"]; + put?: never; + /** + * Register a webhook + * @description Register a new webhook endpoint for the merchant. Returns a signing secret for payload verification. + */ + post: operations["registerWebhook"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/merchant/webhooks/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get webhook details + * @description Get details of a specific webhook by ID. + */ + get: operations["getWebhook"]; + /** + * Update a webhook + * @description Update the URL or description of an existing webhook. + */ + put: operations["updateWebhook"]; + post?: never; + /** + * Delete a webhook + * @description Remove a registered webhook URL. New events for this merchant + * will no longer be dispatched to the deleted URL. Notifications + * already queued on the broker at the moment of deletion continue + * through their retry schedule until acknowledged or exhausted — + * the delete does not purge in-flight deliveries. + */ + delete: operations["deleteWebhook"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/merchant/webhooks/{id}/toggle": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** + * Toggle webhook active status + * @description Flip a registered webhook between active and inactive states + * without deleting it. Useful for planned merchant-side maintenance + * windows or temporarily silencing a noisy endpoint. Inactive + * webhooks receive no new dispatches; deliveries already queued + * before the toggle may still fire once before the state takes + * effect. + */ + patch: operations["toggleWebhookStatus"]; + trace?: never; + }; + "/api/v1/merchant/webhooks/notifications": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List webhook notifications + * @description List all webhook notification events for the merchant with pagination. + */ + get: operations["listWebhookNotifications"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/merchant/webhooks/notifications/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get webhook notification details + * @description Get details of a specific webhook notification event. + */ + get: operations["getWebhookNotification"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/customers": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List all customers + * @description Retrieve a paginated list of customers for the authenticated merchant. + * Query parameters for filtering are case-insensitive (uppercased internally). + */ + get: operations["listCustomers"]; + put?: never; + /** + * Create a customer + * @description Register a new customer. Fields document_type, country_code, and gender + * are case-insensitive (uppercased internally, returned lowercased). + */ + post: operations["createCustomer"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/customers/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get customer details + * @description Retrieve details for a specific customer. + */ + get: operations["getCustomer"]; + /** + * Update a customer + * @description Update customer details. All fields are optional. + */ + put: operations["updateCustomer"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/customers/{id}/sync": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Sync customer data + * @description Trigger a sync of specified fields with an external provider. + */ + post: operations["syncCustomer"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/customers/{customer_id}/balances": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get customer balances + * @description Retrieve available and pending balances for a customer across providers. + */ + get: operations["getCustomerBalances"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/customers/{customer_id}/files": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get customer files + * @description Retrieve uploaded files for a customer with presigned download URLs. + */ + get: operations["getCustomerFiles"]; + put?: never; + /** + * Upload customer files + * @description Upload identity or address verification documents for a customer. + */ + post: operations["uploadCustomerFiles"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/customers/{customer_id}/payment_methods": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List payment methods + * @description List all payment methods for a customer. + */ + get: operations["listPaymentMethods"]; + put?: never; + /** + * Add a payment method + * @description Create a payment method for a customer. Fields type, provider, currency, + * chain, and bank_account_type are case-insensitive. + */ + post: operations["createPaymentMethod"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/customers/{customer_id}/payment_methods/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get payment method details + * @description Get details of a specific payment method. + */ + get: operations["getPaymentMethod"]; + /** + * Update a payment method + * @description Replace mutable fields on a saved payment method — typically + * customer-visible label, default-flag, or billing-address metadata. + * Provider-issued identifiers such as `card_token` and on-chain + * addresses are immutable; delete the payment method and re-create + * it to change those. + */ + put: operations["updatePaymentMethod"]; + post?: never; + /** + * Delete a payment method + * @description Delete a payment method from a customer. + */ + delete: operations["deletePaymentMethod"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/customers/{customer_id}/platforms": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Submit platform registration data + * @description Submit KYC/provider registration data for a customer on a specific provider platform. + */ + post: operations["populateKycData"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/transactions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List transactions + * @description Retrieve a paginated list of transactions for the merchant. + * Query filter values are case-insensitive (uppercased internally). + * Response fields status, type, provider, currency, payment_method are returned lowercased. + */ + get: operations["listTransactions"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/transactions/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get transaction details + * @description Retrieve details of a single transaction. + */ + get: operations["getTransaction"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/transactions/{id}/settle": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** + * Settle a transaction + * @description Mark a transaction as settled. Requires app-key authentication. + */ + patch: operations["settleTransaction"]; + trace?: never; + }; + "/api/v1/outbound_payments": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create an outbound payment + * @description Initiate a payout/outbound payment to a customer's payment method. + */ + post: operations["createPayout"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/transfer": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create a transfer + * @description Initiate a transfer between accounts. Supports single-provider and inter-platform transfers. + * Fields provider, currency, payment_method.type are case-insensitive. + */ + post: operations["createTransfer"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/transfer/webhook": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Send transfer webhook (dev) + * @description Manually trigger a webhook for a transfer. Intended for development/testing. + */ + post: operations["sendTransferWebhook"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/buy": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create a buy transaction + * @description Initiate a cryptocurrency/asset buy transaction. + * Fields provider, currency, payment_method.type are case-insensitive. + */ + post: operations["createBuy"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/wallets/{customer_id}/balance": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get trading wallet balance + * @description Retrieve the trading wallet balance for a customer. + */ + get: operations["getWalletBalance"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/wallets/trades/buy": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create a wallet buy trade + * @description Initiate a buy trade through the wallet interface. + */ + post: operations["createWalletBuy"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/disputes": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List disputes + * @description Retrieve a paginated list of disputes for the merchant. + */ + get: operations["listDisputes"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/disputes/{dispute_id}/evidence": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** + * Upload dispute evidence + * @description Upload file and/or text evidence for a dispute. At least one of file_evidences or text_evidences is required. + */ + put: operations["updateDisputeEvidence"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/disputes/{dispute_id}/submit": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** + * Submit dispute for review + * @description Mark the dispute as final and submit it to the provider for review. + */ + put: operations["submitDispute"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/disputes/{dispute_id}/close": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** + * Close a dispute + * @description Close the dispute by accepting the provider's outcome. Used when + * the merchant chooses not to contest (or to stop contesting). Once + * closed the dispute state is terminal — subsequent evidence uploads + * are no-ops. For the final funds-movement state, listen for the + * `payment.dispute.funds_withdrawn` or + * `payment.dispute.funds_reinstated` webhook event. + */ + put: operations["closeDispute"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/subscription/plans": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List subscription plans + * @description Retrieve all subscription plans for the merchant. + */ + get: operations["listPlans"]; + put?: never; + /** + * Create a subscription plan + * @description Define a recurring-payment plan template. `price` must be between + * 100 and 10_000 and `currency` must be `USD`. Plans are not eligible + * for subscriptions until they are published via + * `PATCH /api/v1/subscription/plans/{planId}/publish`. + */ + post: operations["createPlan"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/subscription/plans/{planId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get plan details + * @description Retrieve details of a specific subscription plan. + */ + get: operations["getPlan"]; + put?: never; + post?: never; + /** + * Delete a plan + * @description Soft-delete a subscription plan. The record is retained so that + * existing subscribers continue through their current billing cycle + * with the plan's snapshotted terms; no new subscriptions can + * reference the plan after deletion. Hard removal requires ops + * intervention. + */ + delete: operations["deletePlan"]; + options?: never; + head?: never; + /** + * Update a plan + * @description Update plan properties. All fields are optional. + */ + patch: operations["updatePlan"]; + trace?: never; + }; + "/api/v1/subscription/plans/{planId}/publish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** + * Publish a plan + * @description Activate a plan so it can accept subscriptions. + */ + patch: operations["publishPlan"]; + trace?: never; + }; + "/api/v1/subscription/subscribe": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Subscribe to a plan + * @description Create a subscription for a customer to a plan. + */ + post: operations["subscribe"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/subscription/subscriptions/{subscriptionId}/cancel": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** + * Cancel a subscription + * @description Cancel an active subscription. Future billing cycles + * are skipped; already-captured payments are preserved and must be + * refunded separately via `POST /api/v1/payments/{id}/refund` if a + * refund is required. + */ + patch: operations["cancelSubscription"]; + trace?: never; + }; + "/api/v1/subscription/list": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List subscriptions + * @description List subscriptions for a customer with pagination. + */ + get: operations["listSubscriptions"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/subscription/{subscriptionId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get subscription details + * @description Retrieve details of a specific subscription. + */ + get: operations["getSubscription"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/subscription/{subscriptionId}/payment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Initiate subscription payment + * @description Manually initiate a payment for a subscription. + * + * **Authentication.** Merchant `bearerAuth` only. + */ + post: operations["initiateSubscriptionPayment"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/provider-registration/schema": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get KYC schema + * @description Retrieve the provider registration schema definition. + */ + get: operations["getKycSchema"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/provider-registration/{customer_id}/submit": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Submit provider registration + * @description Submit KYC/provider registration for a customer. The request body varies + * by provider. Fields provider and target_role are case-insensitive. + */ + post: operations["submitKyc"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/provider-registration/{customer_id}/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Check provider registration status + * @description Check the KYC/provider registration status for a customer across platforms. + * Response fields provider, target_role, and status are returned lowercased. + */ + get: operations["getKycStatus"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/taxes/calculate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Calculate taxes + * @description Calculate applicable taxes for a transaction. + * Field provider is case-insensitive. + */ + post: operations["calculateTaxes"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/files": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List files + * @description Retrieve a list of uploaded files for the merchant. + */ + get: operations["listFiles"]; + put?: never; + /** + * Upload files + * @description Upload one or more files for the merchant. Requests are + * `multipart/form-data`; size and MIME-type limits follow the + * server's upload configuration. + */ + post: operations["uploadFiles"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/files/{file_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get file details + * @description Retrieve details or download URL for a specific file. + */ + get: operations["getFile"]; + put?: never; + post?: never; + /** + * Delete a file + * @description Permanently delete a merchant-owned file, including its underlying + * object-storage artifact. Irreversible; pre-signed URLs previously + * generated against this file stop resolving once the object is + * removed. + */ + delete: operations["deleteFile"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/providers/{provider}/proxy": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Generic provider proxy call + * @description Proxy a request to a specific payment provider's API. + * The endpoint and request body must be whitelisted in the merchant's configuration. + */ + post: operations["genericProviderProxy"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Health check + * @description Check API server health including database and message queue connectivity. + */ + get: operations["healthCheck"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/countries": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List countries + * @description Return the hardcoded list of countries currently eligible for KYC + * onboarding (mutual Stripe ∩ Bridge support — Stripe Connect card-payments + * capability + Bridge allowed). Each entry is tagged with a + * `kyc_supported` boolean (always `true` in the current response). The list + * is open — no authentication required. Source list: RCS-453. + */ + get: operations["listCountries"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export interface webhooks { + "payment.awaiting_confirmation": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Payment awaiting confirmation + * @description Payment awaiting confirmation. Emitted when a payment transitions to the + * `payment.awaiting_confirmation` state in its lifecycle. Payload is the full + * `WebhookTransactionData` for the payment. Retries preserve `data.id`; + * deduplicate on `CrowdSplit-Notification-Id`. + */ + post: operations["webhookPaymentAwaitingConfirmation"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment.cancelled": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Payment cancelled + * @description Payment cancelled. Emitted when a payment transitions to the + * `payment.cancelled` state in its lifecycle. Payload is the full + * `WebhookTransactionData` for the payment. Retries preserve `data.id`; + * deduplicate on `CrowdSplit-Notification-Id`. + */ + post: operations["webhookPaymentCancelled"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment.captured": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Payment captured + * @description Payment captured. Emitted when a payment transitions to the + * `payment.captured` state in its lifecycle. Payload is the full + * `WebhookTransactionData` for the payment. Retries preserve `data.id`; + * deduplicate on `CrowdSplit-Notification-Id`. + */ + post: operations["webhookPaymentCaptured"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment.failed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Payment failed + * @description Payment failed. Emitted when a payment transitions to the `payment.failed` + * state in its lifecycle. Payload is the full `WebhookTransactionData` for the + * payment. Retries preserve `data.id`; deduplicate on + * `CrowdSplit-Notification-Id`. + */ + post: operations["webhookPaymentFailed"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment.installment.failed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Installment payment failed + * @description Installment payment failed. Emitted for a single installment of a + * multi-installment payment. The parent payment's `id` is carried in `data.id`, + * and the installment detail is in `data.installments`. Delivery is + * per-installment — expect one event per installment transition. + */ + post: operations["webhookInstallmentFailed"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment.installment.succeeded": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Installment payment succeeded + * @description Installment payment succeeded. Emitted for a single installment of a + * multi-installment payment. The parent payment's `id` is carried in `data.id`, + * and the installment detail is in `data.installments`. Delivery is + * per-installment — expect one event per installment transition. + */ + post: operations["webhookInstallmentSucceeded"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment.processing": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Payment is processing + * @description Payment is processing. Emitted when a payment transitions to the + * `payment.processing` state in its lifecycle. Payload is the full + * `WebhookTransactionData` for the payment. Retries preserve `data.id`; + * deduplicate on `CrowdSplit-Notification-Id`. + */ + post: operations["webhookPaymentProcessing"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment.refunded": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Payment refunded + * @description Payment refunded. Emitted when a payment transitions to the + * `payment.refunded` state in its lifecycle. Payload is the full + * `WebhookTransactionData` for the payment. Retries preserve `data.id`; + * deduplicate on `CrowdSplit-Notification-Id`. + */ + post: operations["webhookPaymentRefunded"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment.succeeded": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Payment succeeded + * @description Payment succeeded. Emitted when a payment transitions to the + * `payment.succeeded` state in its lifecycle. Payload is the full + * `WebhookTransactionData` for the payment. Retries preserve `data.id`; + * deduplicate on `CrowdSplit-Notification-Id`. + */ + post: operations["webhookPaymentSucceeded"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment.updated": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Payment updated + * @description Payment updated. Emitted when a payment transitions to the `payment.updated` + * state in its lifecycle. Payload is the full `WebhookTransactionData` for the + * payment. Retries preserve `data.id`; deduplicate on + * `CrowdSplit-Notification-Id`. + */ + post: operations["webhookPaymentUpdated"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "transaction.sc_updated": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Transaction settlement/checkpoint updated + * @description Transaction settlement/checkpoint updated. Emitted when a transaction's + * settlement or checkpoint state changes (typically during reconciliation). + * Carries the latest `WebhookTransactionData`; `status` reflects the new state. + * Retries share the same `CrowdSplit-Notification-Id`. + */ + post: operations["webhookTransactionScUpdated"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "transaction.updated": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Transaction updated + * @description Transaction updated. Emitted when a transaction's top-level fields change + * outside a lifecycle-specific event (e.g., metadata updates). Payload is the + * current `WebhookTransactionData`. + */ + post: operations["webhookTransactionUpdated"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment.refund.created": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Refund created + * @description Refund created. Emitted during the refund sub-lifecycle of a payment. + * `data.id` is the refund transaction's UID; `data.reference_transaction_id` + * points to the original payment. Use this alongside the parent `payment.*` + * events to reconcile net settlement. + */ + post: operations["webhookRefundCreated"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment.refund.failed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Refund failed + * @description Refund failed. Emitted during the refund sub-lifecycle of a payment. + * `data.id` is the refund transaction's UID; `data.reference_transaction_id` + * points to the original payment. Use this alongside the parent `payment.*` + * events to reconcile net settlement. + */ + post: operations["webhookRefundFailed"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment.refund.updated": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Refund updated + * @description Refund updated. Emitted during the refund sub-lifecycle of a payment. + * `data.id` is the refund transaction's UID; `data.reference_transaction_id` + * points to the original payment. Use this alongside the parent `payment.*` + * events to reconcile net settlement. + */ + post: operations["webhookRefundUpdated"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "refund.failed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Refund failed (standalone) + * @description Refund failed (standalone). Emitted for standalone refunds not tied to a + * specific payment (e.g., manual refund operations). Payload is the refund's + * `WebhookTransactionData`. + */ + post: operations["webhookStandaloneRefundFailed"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "refund.succeeded": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Refund succeeded (standalone) + * @description Refund succeeded (standalone). Emitted for standalone refunds not tied to a + * specific payment (e.g., manual refund operations). Payload is the refund's + * `WebhookTransactionData`. + */ + post: operations["webhookStandaloneRefundSucceeded"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment.dispute.closed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Dispute closed + * @description Dispute closed. Emitted during the dispute sub-lifecycle of a disputed + * payment. Payload is the dispute record; `data.payment.id` links back to + * the disputed payment. + */ + post: operations["webhookDisputeClosed"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment.dispute.funds_reinstated": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Dispute funds reinstated + * @description Dispute funds reinstated. Emitted during the dispute sub-lifecycle of a + * disputed payment. Payload is the dispute record; `data.payment.id` links + * back to the disputed payment. + */ + post: operations["webhookDisputeFundsReinstated"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment.dispute.funds_withdrawn": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Dispute funds withdrawn + * @description Dispute funds withdrawn. Emitted during the dispute sub-lifecycle of a + * disputed payment. Payload is the dispute record; `data.payment.id` links + * back to the disputed payment. + */ + post: operations["webhookDisputeFundsWithdrawn"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment.dispute.updated": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Dispute updated + * @description Dispute updated. Emitted during the dispute sub-lifecycle of a disputed + * payment. Payload is the dispute record; `data.payment.id` links back to + * the disputed payment. + */ + post: operations["webhookDisputeUpdated"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment.disputed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Payment disputed + * @description Payment disputed. Emitted when a cardholder or issuer opens a dispute against + * a previously successful payment. This is the first event in the dispute + * sub-lifecycle; subsequent updates arrive as `payment.dispute.*`. Gather + * evidence promptly — `evidence_due_by` in the payload is authoritative. + */ + post: operations["webhookDisputeCreated"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "customer.action_required": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Customer action required + * @description Customer action required. Emitted during the customer (subject) lifecycle. + * Payload carries the customer's current status. Use + * this to keep merchant-side customer records in sync. + */ + post: operations["webhookCustomerActionRequired"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "customer.created": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Customer created + * @description Customer created. Emitted during the customer (subject) lifecycle. Payload carries the customer's current status. Use this to keep + * merchant-side customer records in sync. + */ + post: operations["webhookCustomerCreated"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "customer.processing": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Customer processing + * @description Customer processing. Emitted during the customer (subject) lifecycle. Payload carries the customer's current status. Use this to + * keep merchant-side customer records in sync. + */ + post: operations["webhookCustomerProcessing"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "customer.rejected": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Customer rejected + * @description Customer rejected. Emitted during the customer (subject) lifecycle. Payload carries the customer's current status. Use this to + * keep merchant-side customer records in sync. + */ + post: operations["webhookCustomerRejected"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "customer.updated": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Customer updated + * @description Customer updated. Emitted during the customer (subject) lifecycle. Payload carries the customer's current status. Use this to keep + * merchant-side customer records in sync. + */ + post: operations["webhookCustomerUpdated"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "customer.verified": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Customer verified + * @description Customer verified. Emitted during the customer (subject) lifecycle. Payload carries the customer's current status. Use this to + * keep merchant-side customer records in sync. + */ + post: operations["webhookCustomerVerified"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "kyc.action_required": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * KYC action required + * @description KYC action required. Emitted for KYC decisions on a customer. Payload is a + * `WebhookProviderRegistrationData`; `data.readiness` indicates downstream + * capability (e.g., payout-ready). Required fields on `action_required` are in + * `data.failure_reason`. + */ + post: operations["webhookKycActionRequired"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "kyc.approved": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * KYC approved + * @description KYC approved. Emitted for KYC decisions on a customer. Payload is a + * `WebhookProviderRegistrationData`; `data.readiness` indicates downstream + * capability (e.g., payout-ready). Required fields on `action_required` are in + * `data.failure_reason`. + */ + post: operations["webhookKycApproved"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "kyc.rejected": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * KYC rejected + * @description KYC rejected. Emitted for KYC decisions on a customer. Payload is a + * `WebhookProviderRegistrationData`; `data.readiness` indicates downstream + * capability (e.g., payout-ready). Required fields on `action_required` are in + * `data.failure_reason`. + */ + post: operations["webhookKycRejected"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "provider_registration.action_required": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Provider registration action required + * @description Provider registration action required. Emitted during a customer's + * registration flow at an upstream provider. `data.provider` identifies the + * provider and `data.status` reflects the new state. Combined, + * `provider_registration.*` events represent the full onboarding lifecycle on + * each provider. + */ + post: operations["webhookProvRegActionRequired"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "provider_registration.approved": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Provider registration approved + * @description Provider registration approved. Emitted during a customer's registration flow + * at an upstream provider. `data.provider` identifies the provider and + * `data.status` reflects the new state. Combined, `provider_registration.*` + * events represent the full onboarding lifecycle on each provider. + */ + post: operations["webhookProvRegApproved"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "provider_registration.awaiting_confirmation": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Provider registration awaiting confirmation + * @description Provider registration awaiting confirmation. Emitted during a customer's + * registration flow at an upstream provider. `data.provider` identifies the + * provider and `data.status` reflects the new state. Combined, + * `provider_registration.*` events represent the full onboarding lifecycle on + * each provider. + */ + post: operations["webhookProvRegAwaitingConfirmation"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "provider_registration.documents_uploaded": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Provider registration documents uploaded + * @description Provider registration documents uploaded. Emitted during a customer's + * registration flow at an upstream provider. `data.provider` identifies the + * provider and `data.status` reflects the new state. Combined, + * `provider_registration.*` events represent the full onboarding lifecycle on + * each provider. + */ + post: operations["webhookProvRegDocumentsUploaded"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "provider_registration.processing": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Provider registration processing + * @description Provider registration processing. Emitted during a customer's registration + * flow at an upstream provider. `data.provider` identifies the provider and + * `data.status` reflects the new state. Combined, `provider_registration.*` + * events represent the full onboarding lifecycle on each provider. + */ + post: operations["webhookProvRegProcessing"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "provider_registration.rejected": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Provider registration rejected + * @description Provider registration rejected. Emitted during a customer's registration flow + * at an upstream provider. `data.provider` identifies the provider and + * `data.status` reflects the new state. Combined, `provider_registration.*` + * events represent the full onboarding lifecycle on each provider. + */ + post: operations["webhookProvRegRejected"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "provider_registration.restricted": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Provider registration restricted + * @description Provider registration restricted. Emitted during a customer's registration + * flow at an upstream provider. `data.provider` identifies the provider and + * `data.status` reflects the new state. Combined, `provider_registration.*` + * events represent the full onboarding lifecycle on each provider. + */ + post: operations["webhookProvRegRestricted"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "provider_registration.submitted": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Provider registration submitted + * @description Provider registration submitted. Emitted during a customer's registration + * flow at an upstream provider. `data.provider` identifies the provider and + * `data.status` reflects the new state. Combined, `provider_registration.*` + * events represent the full onboarding lifecycle on each provider. + */ + post: operations["webhookProvRegSubmitted"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "provider_registration.updated": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Provider registration updated + * @description Provider registration updated. Emitted during a customer's registration flow + * at an upstream provider. `data.provider` identifies the provider and + * `data.status` reflects the new state. Combined, `provider_registration.*` + * events represent the full onboarding lifecycle on each provider. + */ + post: operations["webhookProvRegUpdated"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "provider_registration.verification_expired": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Provider registration verification expired + * @description Provider registration verification expired. Emitted during a customer's + * registration flow at an upstream provider. `data.provider` identifies the + * provider and `data.status` reflects the new state. Combined, + * `provider_registration.*` events represent the full onboarding lifecycle on + * each provider. + */ + post: operations["webhookProvRegVerificationExpired"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "customer.sync.failed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Customer sync failed + * @description Customer sync failed. Emitted while a customer's profile is being + * synchronized to an upstream provider. `data.attempt_id` identifies a single + * sync attempt across started/succeeded/failed. Retries reuse the same + * `attempt_id`. + */ + post: operations["webhookCustomerSyncFailed"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "customer.sync.started": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Customer sync started + * @description Customer sync started. Emitted while a customer's profile is being + * synchronized to an upstream provider. `data.attempt_id` identifies a single + * sync attempt across started/succeeded/failed. Retries reuse the same + * `attempt_id`. + */ + post: operations["webhookCustomerSyncStarted"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "customer.sync.succeeded": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Customer sync succeeded + * @description Customer sync succeeded. Emitted while a customer's profile is being + * synchronized to an upstream provider. `data.attempt_id` identifies a single + * sync attempt across started/succeeded/failed. Retries reuse the same + * `attempt_id`. + */ + post: operations["webhookCustomerSyncSucceeded"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment_method.approved": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Payment method approved + * @description Payment method approved. Emitted during the payment-method lifecycle on a + * customer (card, bank account, wallet). Payload is a + * `WebhookPaymentMethodData`; `data.type` narrows the shape of + * provider-specific fields. + */ + post: operations["webhookPaymentMethodApproved"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment_method.created": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Payment method created + * @description Payment method created. Emitted during the payment-method lifecycle on a + * customer (card, bank account, wallet). Payload is a + * `WebhookPaymentMethodData`; `data.type` narrows the shape of + * provider-specific fields. + */ + post: operations["webhookPaymentMethodCreated"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment_method.rejected": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Payment method rejected + * @description Payment method rejected. Emitted during the payment-method lifecycle on a + * customer (card, bank account, wallet). Payload is a + * `WebhookPaymentMethodData`; `data.type` narrows the shape of + * provider-specific fields. + */ + post: operations["webhookPaymentMethodRejected"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment_method.updated": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Payment method updated + * @description Payment method updated. Emitted during the payment-method lifecycle on a + * customer (card, bank account, wallet). Payload is a + * `WebhookPaymentMethodData`; `data.type` narrows the shape of + * provider-specific fields. + */ + post: operations["webhookPaymentMethodUpdated"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payment_method.verified": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Payment method verified + * @description Payment method verified. Emitted during the payment-method lifecycle on a + * customer (card, bank account, wallet). Payload is a + * `WebhookPaymentMethodData`; `data.type` narrows the shape of + * provider-specific fields. + */ + post: operations["webhookPaymentMethodVerified"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "transfer.failed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Transfer failed + * @description Transfer failed. Emitted when an internal transfer (between + * CrowdSplit-managed wallets) completes or fails. Payload is a + * `WebhookTransactionData` with `type: transfer`. `data.source` and + * `data.destination` identify the legs. + */ + post: operations["webhookTransferFailed"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "transfer.succeeded": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Transfer succeeded + * @description Transfer succeeded. Emitted when an internal transfer (between + * CrowdSplit-managed wallets) completes or fails. Payload is a + * `WebhookTransactionData` with `type: transfer`. `data.source` and + * `data.destination` identify the legs. + */ + post: operations["webhookTransferSucceeded"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "payout.succeeded": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Payout succeeded + * @description Payout succeeded. Emitted when an outbound payout to a customer-owned account + * succeeds at the upstream provider. Final state — no further events are + * expected for the payout. Payload is the payout's `WebhookTransactionData`. + */ + post: operations["webhookPayoutSucceeded"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "buy.awaiting_confirmation": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Buy awaiting confirmation + * @description Buy awaiting confirmation. Emitted during a buy (fiat → stablecoin) flow. + * Payload is a `WebhookTransactionData` with `type: buy`; `data.source` is the + * fiat leg and `data.destination` is the crypto leg. + * `buy.awaiting_confirmation` is the intermediate state while waiting on + * on-chain confirmation. + */ + post: operations["webhookBuyAwaitingConfirmation"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "buy.completed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Buy completed + * @description Buy completed. Emitted during a buy (fiat → stablecoin) flow. Payload is a + * `WebhookTransactionData` with `type: buy`; `data.source` is the fiat leg and + * `data.destination` is the crypto leg. `buy.awaiting_confirmation` is the + * intermediate state while waiting on on-chain confirmation. + */ + post: operations["webhookBuyCompleted"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "buy.succeeded": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Buy succeeded + * @description Buy succeeded. Emitted during a buy (fiat → stablecoin) flow. Payload is a + * `WebhookTransactionData` with `type: buy`; `data.source` is the fiat leg and + * `data.destination` is the crypto leg. `buy.awaiting_confirmation` is the + * intermediate state while waiting on on-chain confirmation. + */ + post: operations["webhookBuySucceeded"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "sell.failed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Sell failed + * @description Sell failed. Emitted during a sell (stablecoin → fiat) flow. Payload is a + * `WebhookTransactionData` with `type: sell`. Use `data.provider_response` to + * access provider-specific status codes on failure. + */ + post: operations["webhookSellFailed"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "sell.succeeded": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Sell succeeded + * @description Sell succeeded. Emitted during a sell (stablecoin → fiat) flow. Payload is a + * `WebhookTransactionData` with `type: sell`. Use `data.provider_response` to + * access provider-specific status codes on failure. + */ + post: operations["webhookSellSucceeded"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "external.deposit.awaiting_confirmation": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * External deposit awaiting confirmation + * @description External deposit awaiting confirmation. Emitted during the external-deposit + * lifecycle (funds arriving from outside CrowdSplit, e.g., a bank wire or + * on-chain transfer to a virtual account). Payload is a + * `WebhookTransactionData` with `type: external_deposit`; reconcile against + * bank or on-chain records. + */ + post: operations["webhookExternalDepositAwaitingConfirmation"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "external.deposit.completed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * External deposit completed + * @description External deposit completed. Emitted during the external-deposit lifecycle + * (funds arriving from outside CrowdSplit, e.g., a bank wire or on-chain + * transfer to a virtual account). Payload is a `WebhookTransactionData` with + * `type: external_deposit`; reconcile against bank or on-chain records. + */ + post: operations["webhookExternalDepositCompleted"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "external.deposit.received": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * External deposit received + * @description External deposit received. Emitted during the external-deposit lifecycle + * (funds arriving from outside CrowdSplit, e.g., a bank wire or on-chain + * transfer to a virtual account). Payload is a `WebhookTransactionData` with + * `type: external_deposit`; reconcile against bank or on-chain records. + */ + post: operations["webhookExternalDepositReceived"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "external.deposit.succeeded": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * External deposit succeeded + * @description External deposit succeeded. Emitted during the external-deposit lifecycle + * (funds arriving from outside CrowdSplit, e.g., a bank wire or on-chain + * transfer to a virtual account). Payload is a `WebhookTransactionData` with + * `type: external_deposit`; reconcile against bank or on-chain records. + */ + post: operations["webhookExternalDepositSucceeded"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "external.virtual_account.funded": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * External virtual account funded + * @description External virtual account funded. Emitted when a virtual account assigned to a + * customer receives a deposit from outside the CrowdSplit platform. Payload + * identifies the receiving customer and the funding amount. This does not by + * itself credit a wallet — use `external.deposit.*` events for the subsequent + * credit lifecycle. + */ + post: operations["webhookExternalVirtualAccountFunded"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export interface components { + schemas: { + ApiResponse: { + /** @description Human-readable message describing the result */ + msg: string; + /** @description Response payload (null on error) */ + data: unknown; + }; + ApiErrorResponse: { + /** @description Human-readable error message */ + msg: string; + /** @description Always null for errors */ + data: null; + /** @description Optional error message from the underlying payment provider */ + provider_message?: string; + }; + BillingAddress: { + house_number?: string; + street_number?: string; + street_name?: string; + postal_code?: string; + city?: string; + state?: string; + country_code?: string; + }; + /** + * @description Payment creation request. The exact schema varies by provider and payment method type. + * Fields like provider, currency, and type are case-insensitive (uppercased internally). + */ + CreatePaymentRequest: { + /** + * @description Payment provider + * @enum {string} + */ + provider: "stripe"; + source: { + /** @description Amount in smallest currency unit (e.g. cents) */ + amount: number; + /** + * @description ISO 4217 currency code + * @enum {string} + */ + currency: "usd" | "brl" | "cop" | "jpy"; + customer?: { + /** + * Format: uuid + * @description CrowdSplit customer ID + */ + id?: string; + }; + payment_method: { + /** @enum {string} */ + type: "card"; + /** + * Format: uuid + * @description Saved payment method ID (for card payments) + */ + id?: string; + /** @description One-time card token from provider */ + card_token?: string; + /** + * Format: date-time + * @description PIX QR code expiry (required for PIX payments) + */ + expiry_date?: string; + billing_address?: components["schemas"]["BillingAddress"]; + }; + /** + * @description Whether to capture immediately or manually later + * @enum {string} + */ + capture_method?: "automatic" | "manual"; + fraud_check?: { + /** @description Fraud check is currently not available. Must be set to false. */ + enabled?: boolean; + }; + /** @description Number of installments (Stripe) */ + installments?: number; + /** @description Float rate for installment payments (Stripe) */ + float_rate?: number; + }; + /** @description Destination details for cross-currency or split payments */ + destination?: { + amount?: number; + currency?: string; + customer?: { + /** Format: uuid */ + id?: string; + }; + }; + /** @description Fee configuration for destination flow */ + fee?: { + /** + * @default platform + * @enum {string} + */ + bearer: "platform" | "connected_account"; + }; + /** + * @description Payment flow type (Stripe) + * @default platform + * @enum {string} + */ + flow: "platform" | "destination"; + /** @description Fund allocation splits (for platform flow) */ + allocations?: { + /** @default uncategorized */ + type: string; + receiver?: { + /** @enum {string} */ + type?: "platform" | "connected_account"; + /** Format: uuid */ + id?: string; + }; + amount?: number; + }[]; + /** @description Total installment count */ + total_installments?: number; + /** @description Whether to auto-confirm the payment */ + confirm?: boolean; + /** + * @description Merchant-supplied arbitrary metadata, stored with the + * transaction and echoed back on the response and on any + * derived webhook deliveries. Shape is defined by the merchant. + */ + metadata?: { + [key: string]: unknown; + }; + }; + /** @description Payment transaction response object */ + PaymentResponse: { + /** + * Format: uuid + * @description Transaction unique ID + */ + id?: string; + /** + * @description Current transaction status (lowercased in response) + * @enum {string} + */ + status?: "initiated" | "awaiting_confirmation" | "processing" | "captured" | "succeeded" | "failed" | "canceled"; + /** + * @description Transaction type (lowercased in response) + * @enum {string} + */ + type?: "payment" | "refund"; + /** @description Payment provider (lowercased in response) */ + provider?: string; + /** @description Source details (amount + currency + customer + payment method). */ + source?: { + /** @description Amount in minor units (cents). */ + amount?: number; + /** @description ISO 4217 currency code (lowercased on the wire). */ + currency?: string; + /** @description Payment method used on the source leg. Extra keys vary by provider. */ + payment_method?: { + /** + * Format: uuid + * @description Payment method unique ID. + */ + id?: string; + /** @description Payment method type (lowercased — card, pix, bank, …). */ + type?: string; + /** @description Blockchain network when the method is on-chain (lowercased). */ + chain?: string; + } & { + [key: string]: unknown; + }; + customer?: { + /** + * Format: uuid + * @description Customer (subject) unique ID. + */ + id?: string; + }; + }; + /** Format: date-time */ + created_at?: string; + /** Format: date-time */ + updated_at?: string; + /** Format: date-time */ + settlement_date?: string | null; + /** + * @description Merchant-supplied arbitrary metadata, echoed back unchanged on + * the response and on any derived webhook deliveries. Shape is + * defined by the merchant at request time. + */ + metadata?: { + [key: string]: unknown; + } | null; + }; + CreateMerchantRequest: { + /** @description Legal business name */ + legal_name: string; + /** @description Business address */ + address: string; + /** @description Official website URL */ + website: string; + /** @description Tax identification number */ + tax_number: string; + }; + MerchantResponse: { + id?: number; + legalName?: string; + address?: string; + website?: string; + taxNumber?: string; + /** @description Generated application ID */ + appId?: string; + isApproved?: boolean; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + }; + GrantTokenRequest: { + /** @description App ID from merchant creation */ + client_id: string; + /** @description Secret key (shown only at creation) */ + client_secret: string; + /** @enum {string} */ + grant_type: "client_credentials"; + }; + MerchantTokenResponse: { + /** @description JWT access token for merchant API calls */ + access_token: string; + /** @enum {string} */ + token_type: "Bearer"; + /** @description Token expiry time in milliseconds */ + expires_in: number; + }; + TransferDateRequest: { + /** + * Format: date + * @description ISO 8601 date string + */ + settlementDate: string; + /** @description 2-letter ISO 3166-1 alpha-2 country code */ + region: string; + config: { + /** @description Day numbers (0=Sunday, 6=Saturday) */ + weekends: number[]; + /** @description ISO 8601 date strings */ + holidays: string[]; + }; + /** @description Holiday provider name */ + provider: string; + }; + WebhookResponse: { + /** Format: uuid */ + id?: string; + url?: string; + description?: string; + isActive?: boolean; + /** @description Webhook signing secret (only returned on registration) */ + secret?: string; + }; + RegisterWebhookRequest: { + /** @description Webhook endpoint URL */ + url: string; + /** @description Webhook description */ + description?: string; + }; + UpdateWebhookRequest: { + url?: string; + description?: string; + }; + WebhookNotification: { + /** Format: uuid */ + notificationId?: string; + data?: { + /** @description Event type (e.g. payment.succeeded) */ + type?: string; + /** Format: uuid */ + id?: string; + }; + isAcknowledged?: boolean; + }; + CustomerResponse: { + /** Format: uuid */ + id?: string; + document_number?: string; + tax_id?: string; + /** @description Returned lowercased */ + document_type?: string; + /** Format: email */ + email?: string; + social_name?: string; + first_name?: string; + last_name?: string; + /** Format: date-time */ + dob?: string; + house_number?: string | null; + street_number?: string | null; + street_name?: string | null; + postal_code?: string | null; + city?: string | null; + state?: string | null; + phone_country_code?: string; + phone_area_code?: string; + phone_number?: string; + monthly_net_income?: string | null; + /** @description Returned lowercased */ + gender?: string | null; + /** @description Returned lowercased */ + country_code?: string; + roles?: string[]; + wallet_address?: string | null; + }; + /** + * @description Customer registration request. Fields document_type, country_code, and gender + * are case-insensitive (uppercased internally, returned lowercased). + */ + CreateCustomerRequest: { + /** @description Government document number */ + document_number?: string; + /** + * @description Document type (case-insensitive) + * @enum {string} + */ + document_type?: "cpf" | "cnpj" | "passport" | "ssn" | "itin" | "cc" | "curp" | "personal_tax_id" | "company_tax_id"; + /** Format: email */ + email?: string; + first_name?: string; + last_name?: string; + tax_id?: string; + /** + * Format: date + * @description Date of birth (ISO 8601) + */ + dob?: string; + phone_country_code?: string; + phone_area_code?: string; + phone_number?: string; + street_number?: string; + street_name?: string; + house_number?: string; + postal_code?: string; + city?: string; + state?: string; + /** @description ISO country code (case-insensitive) */ + country_code?: string; + /** @description ISO subdivision code */ + subdivision?: string; + /** + * @description Gender (case-insensitive) + * @enum {string} + */ + gender?: "male" | "female" | "other"; + mother_name?: string; + monthly_net_income?: string; + owner_legal_name?: string; + owner_document_number?: string; + owner_document_type?: string; + company_name?: string; + company_start_date?: string; + wallet_address?: string; + }; + SyncCustomerRequest: { + /** @description Single provider name to sync with */ + providers: string[]; + /** @description Fields to sync (from syncable fields list) */ + fields: string[]; + }; + BalanceResponse: { + /** Format: date-time */ + as_of?: string; + filters?: { + /** Format: uuid */ + customer_id?: string; + provider?: string; + role?: string; + }; + balances?: { + provider?: string; + role?: string; + available_balance?: number; + pending_balance?: number; + currency?: string; + }[]; + }; + PaymentMethodResponse: { + /** Format: uuid */ + id?: string; + /** @description Returned lowercased */ + type?: string; + /** @description Returned lowercased */ + status?: string; + /** @description Returned lowercased */ + provider?: string; + chain?: string | null; + currency?: string | null; + source_currency?: string | null; + destination_currency?: string | null; + bank_account_type?: string | null; + bankDetails?: { + /** @description Masked account number */ + accountNumber?: string; + accountName?: string; + accountType?: string; + bankName?: string; + branchCode?: string; + ispb?: string; + } | null; + }; + /** + * @description Create a payment method for a customer. Fields type, provider, currency, + * chain, and bank_account_type are case-insensitive. + */ + CreatePaymentMethodRequest: { + /** + * @description Payment method type (case-insensitive) + * @enum {string} + */ + type: "bank" | "card" | "pix" | "customer_wallet" | "liquidation_address"; + /** @description Provider name (defaults to crowd_split for bank/pix/customer_wallet) */ + provider?: string; + /** @description Currency code */ + currency?: string; + source_currency?: string; + destination_currency?: string; + payment_rail?: string; + /** @enum {string} */ + bank_account_type?: "checking" | "savings" | "payment"; + /** @description Blockchain chain */ + chain?: string; + /** @description Required for liquidation_address type */ + external_account_id?: string; + /** @description Required for bank type */ + bank_details?: { + pix_string?: string; + branch_code?: string; + account_number?: string; + account_name?: string; + bank_name?: string; + /** @enum {string} */ + account_type?: "checking" | "savings" | "payment"; + bank_code?: string; + ispb?: string; + country?: string; + routing_number?: string; + }; + }; + /** @description Transaction object (field casing in response is lowercased for status, type, provider, currency) */ + TransactionResponse: { + /** Format: uuid */ + id?: string; + /** @description Transaction status (lowercased) */ + status?: string; + /** @description Transaction type (lowercased) */ + type?: string; + /** @description Provider name (lowercased) */ + provider?: string; + /** @description Source details (amount + currency + customer + payment method). */ + source?: { + /** @description Amount in minor units (cents). */ + amount?: number; + /** @description ISO 4217 currency code (lowercased). */ + currency?: string; + customer?: { + /** + * Format: uuid + * @description Customer (subject) unique ID. + */ + id?: string; + }; + /** @description Payment method used on the source leg. */ + payment_method?: { + /** Format: uuid */ + id?: string; + /** @description Payment method type (lowercased). */ + type?: string; + /** @description Blockchain network (lowercased). */ + chain?: string; + } & { + [key: string]: unknown; + }; + }; + /** @description Destination details (when applicable — e.g. for transfers, buy/sell, refunds). */ + destination?: { + /** @description Amount in minor units (cents). */ + amount?: number; + /** @description ISO 4217 currency code (lowercased). */ + currency?: string; + customer?: { + /** + * Format: uuid + * @description Destination customer (subject) unique ID. + */ + id?: string; + }; + /** @description Payment method used on the destination leg. */ + payment_method?: { + /** Format: uuid */ + id?: string; + /** @description Payment method type (lowercased). */ + type?: string; + /** @description Blockchain network (lowercased). */ + chain?: string; + } & { + [key: string]: unknown; + }; + } | null; + /** + * @description Merchant-supplied arbitrary metadata, echoed back unchanged. + * Shape is defined by the merchant at request time. + */ + metadata?: { + [key: string]: unknown; + } | null; + /** Format: date-time */ + created_at?: string; + /** Format: date-time */ + updated_at?: string; + /** Format: date-time */ + settlement_date?: string | null; + }; + DisputeResponse: { + /** Format: uuid */ + id?: string; + status?: string; + /** Format: uuid */ + transaction_id?: string; + amount?: number; + currency?: string; + reason?: string; + /** Format: date-time */ + created_at?: string; + /** Format: date-time */ + updated_at?: string; + }; + PlanResponse: { + hash_id?: string; + name?: string; + description?: string; + frequency?: number; + price?: number; + overridden_price?: number; + is_active?: boolean; + /** Format: date-time */ + start_time?: string; + /** Format: date-time */ + end_time?: string; + is_auto_renewable?: boolean; + currency?: string; + allow_amount_override?: boolean; + campaign_id?: string; + campaign_name?: string; + created_by?: string; + /** Format: date-time */ + created_at?: string; + /** Format: date-time */ + updated_at?: string; + }; + CreatePlanRequest: { + name: string; + description: string; + /** @description Billing frequency in days */ + frequency: number; + /** @description Price amount */ + price: number; + currency: string; + created_by: string; + overridden_price?: number; + /** Format: date */ + start_time?: string; + /** Format: date */ + end_time?: string; + is_auto_renewable?: boolean; + allow_amount_override?: boolean; + campaign_id?: string; + campaign_name?: string; + }; + SubscriptionResponse: { + hash_id?: string; + /** Format: date-time */ + start_time?: string; + /** Format: date-time */ + end_time?: string; + plan_hash_id?: string; + auto_renew?: boolean; + /** @enum {string} */ + status?: "active" | "pending_activation" | "canceled" | "expired" | "queued"; + /** Format: date-time */ + created_at?: string; + /** Format: date-time */ + updated_at?: string; + }; + /** + * @description A country known to CrowdSplit, with a flag indicating whether KYC is + * currently supported (Stripe Connect card-payments capability + Bridge + * allowed). Source list: RCS-453. + */ + CountryResponse: { + /** + * @description ISO 3166-1 alpha-2 country code (two uppercase letters). + * @example US + */ + iso_alpha_2: string; + /** + * @description Country display name. + * @example United States + */ + name: string; + /** + * @description True when CrowdSplit currently accepts this country for KYC + * (mutual Stripe ∩ Bridge support). + */ + kyc_supported: boolean; + }; + /** + * @description Base envelope delivered to merchant webhook endpoints. Every webhook + * request body has the shape `{ id, api_version, event, category, data }`; + * concrete event schemas compose this base via `allOf` and refine `event` / + * `category` to their `const` values and `data` to a specific schema. + * + * Each delivery carries three headers: + * CrowdSplit-Signature `t={unix-ts},v1={hex-hmac-sha256}` + * CrowdSplit-Timestamp seconds since epoch + * CrowdSplit-Notification-Id unique per delivery (idempotency) + * + * Merchants MUST verify the HMAC of the raw body using the webhook + * secret returned at registration, and SHOULD reject the delivery if + * `CrowdSplit-Timestamp` differs from the current time by more than + * 5 minutes. Duplicate `CrowdSplit-Notification-Id` values indicate a + * retry of a previously dispatched delivery and should be deduplicated. + */ + WebhookEnvelope: { + /** + * Format: uuid + * @description Unique notification ID (stable across retry attempts). + */ + id: string; + /** + * @description API version of the payload builder that produced this event + * (currently always `v1`). Reports the version of the code that built + * `data` — not a globally-bumped number — so when one event type later + * moves to v2, only that event reports `v2`. Branch on this field if + * you need to handle multiple payload shapes for the same event. + * @example v1 + */ + api_version: string; + /** @description Event name; specialized via `allOf` per operation (e.g. `payment.succeeded`). */ + event: string; + /** @description Event category; specialized via `allOf` per operation (e.g. `payment_lifecycle`). */ + category: string; + /** @description Event-specific payload; each operation refines this via `allOf` to a concrete schema. */ + data: Record; + }; + /** + * @description CrowdSplit-managed upstream provider identifier. Lowercased in every + * merchant-facing payload. + * + * Note: merchant-facing `*.provider` fields emit `avenia` in place + * of `brla` in the lowercased form; consumers should treat the two + * as equivalent for now. + * @enum {string} + */ + ProviderName: "stripe" | "bridge" | "wallet_service" | "crowd_split"; + /** + * @description Transaction lifecycle status (lowercased on the wire). + * @enum {string} + */ + TransactionStatus: "created" | "awaiting_confirmation" | "processing" | "captured" | "succeeded" | "failed" | "canceled_after_completion" | "canceled"; + /** @description Data payload for payment, refund, transfer, buy, sell, payout, and external events */ + WebhookTransactionData: { + /** + * Format: uuid + * @description Transaction unique ID + */ + id: string; + provider: components["schemas"]["ProviderName"]; + status: components["schemas"]["TransactionStatus"]; + /** @description Source details (amount, currency, customer, payment_method). */ + source: { + /** @description Amount in minor units (cents). */ + amount?: number; + /** @description ISO 4217 currency code (lowercased on the wire). */ + currency?: string; + customer?: { + /** + * Format: uuid + * @description Customer (subject) unique ID. + */ + id?: string; + }; + /** @description Payment method used on the source leg. Extra keys vary by provider. */ + payment_method?: { + /** + * Format: uuid + * @description Payment method unique ID. + */ + id?: string; + /** @description Payment method type (lowercased — card, pix, bank, …). */ + type?: string; + /** @description Blockchain network when the method is on-chain (lowercased). */ + chain?: string; + } & { + [key: string]: unknown; + }; + /** @description Capture strategy (lowercased — e.g. automatic, manual). */ + capture_method?: string; + /** + * @description Fraud-check configuration and result (when applicable). Present + * only on transaction types that route through fraud screening. + */ + fraud_check?: { + /** @description Fraud-check provider identifier (lowercased). */ + provider?: string; + config?: { + /** @description Fraud score threshold applied (lowercased identifier). */ + threshold?: string; + /** @description Sequence strategy identifier (lowercased). */ + sequence?: string; + }; + } & { + [key: string]: unknown; + }; + }; + /** @description Destination details (if applicable). */ + destination?: { + /** @description Amount in minor units (cents). */ + amount?: number; + /** @description ISO 4217 currency code (lowercased). */ + currency?: string; + customer?: { + /** + * Format: uuid + * @description Destination customer (subject) unique ID. + */ + id?: string; + }; + /** @description Payment method used on the destination leg. */ + payment_method?: { + /** Format: uuid */ + id?: string; + /** @description Payment method type (lowercased). */ + type?: string; + /** @description Blockchain network (lowercased). */ + chain?: string; + } & { + [key: string]: unknown; + }; + } | null; + /** + * @description Platform / provider fee charged on this transaction, when present. + * Shape originates from the merchant's request body. + */ + fee?: ({ + /** + * @description Who bears the fee. Stripe connected-account flows require + * `connected_account`; may be absent for other providers or + * flow types. + */ + bearer?: string; + /** @description Fee amount in minor units (cents). */ + amount?: number; + /** @description Fee currency (lowercased). */ + currency?: string; + } & { + [key: string]: unknown; + }) | null; + /** + * Format: uuid + * @description Parent transaction ID (for refunds) + */ + reference_transaction_id?: string | null; + total_installments?: number | null; + installments?: { + number?: number; + amount?: number; + status?: string; + /** Format: date-time */ + settlement_date?: string; + }[] | null; + /** Format: date-time */ + settlement_date?: string | null; + /** + * @description Merchant-supplied arbitrary metadata, echoed back unchanged. + * Shape is defined by the merchant at request time. + */ + metadata?: { + [key: string]: unknown; + } | null; + /** + * @description Raw excerpt of the upstream provider's response. Key set varies + * by provider and by call — do not rely on specific keys. Useful + * for debugging and for forwarding provider-specific error codes. + */ + provider_response?: { + [key: string]: unknown; + } | null; + /** Format: date-time */ + created_at: string; + /** Format: date-time */ + updated_at: string; + }; + /** @description Data payload for dispute events */ + WebhookDisputeData: { + /** + * Format: uuid + * @description Dispute unique ID + */ + id: string; + status: string; + /** Format: date-time */ + evidence_due_by?: string | null; + /** + * @description Attached dispute evidence records. Shape follows the merchant's + * submission via `POST /api/v1/disputes/{dispute_id}/evidence`; + * keys match those accepted by that endpoint. + */ + evidences?: { + [key: string]: unknown; + }[]; + /** @description Associated payment transaction (summary fields only). */ + payment?: { + /** + * Format: uuid + * @description Payment transaction UID. + */ + id?: string; + /** @description Transaction type (lowercased — e.g. `payment`). */ + type?: string; + /** @description Payment's current status (lowercased). */ + status?: string; + }; + }; + /** + * @description A single normalized rejection reason in a `failure_reasons` array. + * Codes are stable across providers; new codes may be added as more + * provider rejection types are mapped — treat unrecognised codes as + * `unknown`. + */ + NormalizedFailureReason: { + /** + * @description Stable normalized failure code. Current values: + * `verification_failed`, `expired_document`, `duplicate_data`, + * `invalid_input`, `compliance_review`, `provider_rejected`, + * `requirements_incomplete`, `missing_required_information`, + * `unknown`. + */ + code: string; + /** @description Human-readable explanation from the provider. */ + message?: string; + /** + * @description Affected field names (e.g. `identity_document`, `address`). + * Only present when the code identifies specific fields. + */ + fields?: string[]; + /** + * @description Endorsement name (e.g. `base`, `sepa`). Only present when + * `code` is `requirements_incomplete` and the failure relates + * to an endorsement requirement. + */ + endorsement?: string; + }; + /** @description Data payload for customer, KYC, and provider registration events */ + WebhookProviderRegistrationData: { + /** + * Format: uuid + * @description Customer unique ID + */ + id: string; + /** @description Registration status (lowercased) */ + status: string; + provider?: components["schemas"]["ProviderName"]; + /** @description Target role (lowercased) */ + target_role?: string; + /** + * @description Raw excerpt of the provider's registration response. Shape + * varies by provider — do not rely on specific keys. + */ + provider_response?: { + [key: string]: unknown; + } | null; + /** + * @description Structured rejection reasons. Present when the provider rejects + * a customer or KYC step, or when endorsement requirements are + * incomplete. Null when no failures are recorded. + */ + failure_reasons?: components["schemas"]["NormalizedFailureReason"][] | null; + /** @description Provider readiness status */ + readiness?: string | null; + /** + * @description Field names whose values changed in this event, relative to the + * previous emitted state. Useful for merchants that want to + * react only to specific field transitions. + */ + changes?: string[] | null; + /** + * @description Whether merchant or customer action is required. True for + * recoverable rejections and approvals with pending future + * requirements; false for terminal rejections (e.g. offboarded, + * sanctions-blocked). + */ + action_required?: boolean | null; + /** Format: date-time */ + created_at: string; + /** Format: date-time */ + updated_at: string; + }; + /** @description Data payload for customer sync events */ + WebhookCustomerSyncData: { + /** + * Format: uuid + * @description Customer unique ID + */ + id: string; + provider: components["schemas"]["ProviderName"]; + /** @description Fields being synced */ + fields: string[]; + /** @description Sync attempt identifier */ + attempt_id: string; + /** + * Format: date-time + * @description Timestamp of successful sync (only on success) + */ + synced_at?: string | null; + /** + * @description Data returned from the provider (only on success). Shape varies + * by provider — do not rely on specific keys. + */ + provider_data?: { + [key: string]: unknown; + } | null; + /** @description Error message (only on failure) */ + error?: string | null; + }; + /** + * @description Payment-method type identifier (lowercased). + * @enum {string} + */ + PaymentMethodType: "bank" | "card" | "plaid" | "virtual_account" | "liquidation_address" | "trading_wallet" | "customer_wallet" | "pix" | "evm_address"; + /** @description Data payload for payment method events */ + WebhookPaymentMethodData: { + /** + * Format: uuid + * @description Payment method unique ID + */ + id: string; + /** Format: uuid */ + customer_id: string; + provider: components["schemas"]["ProviderName"]; + /** @description Payment method status (lowercased) */ + status: string; + type: components["schemas"]["PaymentMethodType"]; + country?: string | null; + chain?: string | null; + currency?: string | null; + bank_account_type?: string | null; + /** + * @description Source-leg currency on payment methods that mediate a currency + * conversion (lowercased). Present only for cross-currency methods. + */ + source_currency?: string | null; + /** + * @description Destination-leg currency on payment methods that mediate a + * currency conversion (lowercased). + */ + destination_currency?: string | null; + /** + * @description Merchant-supplied arbitrary metadata for the payment method. + * Shape is defined by the merchant at registration. + */ + metadata?: { + [key: string]: unknown; + } | null; + /** Format: date-time */ + created_at: string; + /** Format: date-time */ + updated_at: string; + } & { + [key: string]: unknown; + }; + }; + responses: { + /** @description Request validation failed */ + ValidationError: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "msg": "Validation error message", + * "data": null + * } + */ + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + /** @description Authentication credentials are missing or invalid */ + UnauthorizedError: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "msg": "Unauthorized", + * "data": null + * } + */ + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + /** @description Authenticated but not authorized for this resource */ + ForbiddenError: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "msg": "Forbidden", + * "data": null + * } + */ + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + /** @description Requested resource not found */ + NotFoundError: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "msg": "Resource not found", + * "data": null + * } + */ + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + }; + parameters: { + /** @description App key for subscription service authentication */ + RefAppKeyHeader: string; + /** @description Set to 'recurring_payment' for subscription payment flows */ + PaymentTypeHeader: "recurring_payment"; + /** @description Number of records to return per page */ + LimitQuery: number; + /** @description Number of records to skip */ + OffsetQuery: number; + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + CrowdSplitSignatureHeader: string; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + CrowdSplitTimestampHeader: number; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + CrowdSplitNotificationIdHeader: string; + }; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + signup: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * Format: email + * @description User's email address + * @example user@example.com + */ + email: string; + /** + * @description User's password + * @example SecurePass123! + */ + password: string; + }; + }; + }; + responses: { + /** @description User created successfully and verification email sent */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "msg": "User Created Successfully!, Created email verification token!, Verification link emailed to user!, User Created Successfully!", + * "data": null + * } + */ + "application/json": components["schemas"]["ApiResponse"]; + }; + }; + /** @description User already exists or email delivery failed */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 422: components["responses"]["ValidationError"]; + }; + }; + verifySignupEmail: { + parameters: { + query: { + /** @description JWT verification token sent to the user's email */ + token: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Email verification successful */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "msg": "Email Verification Successful!", + * "data": null + * } + */ + "application/json": components["schemas"]["ApiResponse"]; + }; + }; + /** @description Invalid or expired token, or email already verified */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + /** @description User not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + }; + }; + resendVerificationEmail: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * Format: email + * @description Email address to resend verification to + * @example user@example.com + */ + email: string; + }; + }; + }; + responses: { + /** @description Verification email resent successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "msg": "Email Verification Token Updated!, Sent Verification Email!", + * "data": null + * } + */ + "application/json": components["schemas"]["ApiResponse"]; + }; + }; + /** @description Email delivery failed or token error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + /** @description User not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + }; + }; + signin: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * Format: email + * @description User's email address + * @example user@example.com + */ + email: string; + /** + * @description User's password + * @example SecurePass123! + */ + password: string; + /** + * @description Role to sign in as + * @example ROLE_PLATFORM_ADMIN + * @enum {string} + */ + role: "ROLE_ADMIN" | "ROLE_PLATFORM_ADMIN" | "ROLE_PLATFORM_USER"; + }; + }; + }; + responses: { + /** @description Sign in successful */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + /** @example Sign In Successful! */ + msg?: unknown; + data?: { + /** @description User ID */ + id?: number; + /** Format: email */ + email?: string; + /** @description JWT access token */ + accessToken?: string; + /** @description JWT refresh token */ + refreshToken?: string; + /** @description User creation timestamp in milliseconds */ + createdMillis?: number; + }; + }; + }; + }; + /** @description Email not verified */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + /** @description Invalid password */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + /** @description User not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 422: components["responses"]["ValidationError"]; + }; + }; + refreshToken: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description JWT refresh token obtained from sign in */ + refresh_token: string; + }; + }; + }; + responses: { + /** @description Tokens refreshed successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + /** @example Successfully Created New Refresh Token! */ + msg?: unknown; + data?: { + /** @description New JWT access token */ + accessToken?: string; + /** @description New JWT refresh token */ + refreshToken?: string; + }; + }; + }; + }; + /** @description Refresh token expired or invalid */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + /** @description User not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + }; + }; + validateToken: { + parameters: { + query?: { + /** @description Access token (alternative to x-access-token header) */ + x_access_token?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Token is valid */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + /** @example Token is Valid! */ + msg?: unknown; + data?: { + /** @description User ID associated with the token */ + id?: number; + }; + }; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + }; + }; + resetPassword: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * Format: email + * @description Email address associated with the account + * @example user@example.com + */ + email: string; + }; + }; + }; + responses: { + /** @description Password reset link sent */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "msg": "Reset password link emailed to user!", + * "data": null + * } + */ + "application/json": components["schemas"]["ApiResponse"]; + }; + }; + /** @description Email delivery failed or reset already requested recently */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + /** @description User not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 422: components["responses"]["ValidationError"]; + }; + }; + verifyResetPasswordToken: { + parameters: { + query: { + /** @description Reset password token sent to user's email */ + resetPasswordToken: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Token is valid */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "msg": "Token is Valid!", + * "data": null + * } + */ + "application/json": components["schemas"]["ApiResponse"]; + }; + }; + /** @description Invalid or expired token */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + /** @description User not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + }; + }; + updatePassword: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description The new password to set */ + new_password: string; + /** @description Reset password token from email */ + reset_password_token: string; + }; + }; + }; + responses: { + /** @description Password updated successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "msg": "Password Updated Successfully!", + * "data": null + * } + */ + "application/json": components["schemas"]["ApiResponse"]; + }; + }; + /** @description Invalid token or server error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + /** @description User not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 422: components["responses"]["ValidationError"]; + }; + }; + createPayment: { + parameters: { + query?: never; + header?: { + /** @description App key for subscription service authentication */ + "Ref-App-Key"?: components["parameters"]["RefAppKeyHeader"]; + /** @description Set to 'recurring_payment' for subscription payment flows */ + "Payment-Type"?: components["parameters"]["PaymentTypeHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreatePaymentRequest"]; + }; + }; + responses: { + /** @description Payment created successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + /** @example Transaction was Initiated Successfully! */ + msg?: unknown; + data?: components["schemas"]["PaymentResponse"]; + }; + }; + }; + /** @description Validation error or invalid state */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + /** @description Customer not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 422: components["responses"]["ValidationError"]; + }; + }; + confirmPayment: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Payment transaction ID (UUID) */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Payment confirmed */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + /** @example Transaction is in Progress! */ + msg?: unknown; + data?: components["schemas"]["PaymentResponse"]; + }; + }; + }; + /** @description Invalid state for confirmation */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + /** @description Transaction not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + }; + }; + capturePayment: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Payment transaction ID (UUID) */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Payment captured */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + /** @example Payment captured successfully */ + msg?: unknown; + data?: components["schemas"]["PaymentResponse"]; + }; + }; + }; + /** @description Error capturing payment */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + }; + }; + cancelPayment: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Payment transaction ID (UUID) */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Payment cancelled */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + /** @example Transaction cancellation successful */ + msg?: unknown; + data?: components["schemas"]["PaymentResponse"]; + }; + }; + }; + /** @description Invalid state or wrong transaction type */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + /** @description Transaction not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + }; + }; + refundPayment: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Payment transaction ID (UUID) */ + id: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + /** + * @example { + * "amount": 2500, + * "metadata": { + * "reason": "Customer request" + * } + * } + */ + "application/json": { + /** @description Refund amount in smallest currency unit. Omit for full refund. */ + amount?: number; + /** @description Additional metadata for the refund */ + metadata?: { + [key: string]: unknown; + }; + }; + }; + }; + responses: { + /** @description Refund initiated */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + /** @example Transaction was Initiated Successfully! */ + msg?: unknown; + data?: components["schemas"]["PaymentResponse"]; + }; + }; + }; + /** @description Refund amount exceeds original or invalid state */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + /** @description Transaction not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 422: components["responses"]["ValidationError"]; + }; + }; + createMerchant: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "legal_name": "Acme Corp", + * "address": "123 Business St, Suite 100", + * "website": "https://acme.example.com", + * "tax_number": "12345678901" + * } + */ + "application/json": components["schemas"]["CreateMerchantRequest"]; + }; + }; + responses: { + /** @description Merchant created successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["MerchantResponse"]; + }; + }; + }; + /** @description Validation error or merchant already exists */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 422: components["responses"]["ValidationError"]; + }; + }; + createMerchantKey: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Keys generated successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: { + /** @description Secret key (only shown once, store securely) */ + secretKey?: string; + /** @description Public key */ + publicKey?: string; + }; + }; + }; + }; + /** @description Error generating keys */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + }; + }; + grantMerchantToken: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "client_id": "app_abc123", + * "client_secret": "sk_live_xyz789", + * "grant_type": "client_credentials" + * } + */ + "application/json": components["schemas"]["GrantTokenRequest"]; + }; + }; + responses: { + /** @description Token granted */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "access_token": "eyJhbGciOiJIUzI1NiIs...", + * "token_type": "Bearer", + * "expires_in": 3600000 + * } + */ + "application/json": components["schemas"]["MerchantTokenResponse"]; + }; + }; + /** @description Merchant not found or invalid credentials */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 422: components["responses"]["ValidationError"]; + }; + }; + refreshMerchantToken: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description Refresh token from previous grant or refresh */ + refresh_token: string; + }; + }; + }; + responses: { + /** @description Tokens refreshed */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: { + accessToken?: string; + refreshToken?: string; + }; + }; + }; + }; + /** @description Token expired or invalid */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + /** @description Merchant not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + }; + }; + calculateTransferDate: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TransferDateRequest"]; + }; + }; + responses: { + /** @description Transfer date calculated */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: { + /** Format: date-time */ + transferDate?: string; + }; + }; + }; + }; + /** @description Validation error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + }; + }; + listWebhooks: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Webhook list */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["WebhookResponse"][]; + }; + }; + }; + }; + }; + registerWebhook: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "url": "https://example.com/webhooks/crowdsplit", + * "description": "Production webhook endpoint" + * } + */ + "application/json": components["schemas"]["RegisterWebhookRequest"]; + }; + }; + responses: { + /** @description Webhook registered. Secret is only shown once. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: { + /** @description Signing secret for HMAC-SHA256 verification */ + secret?: string; + }; + }; + }; + }; + /** @description Validation error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + }; + }; + getWebhook: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Webhook details */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["WebhookResponse"]; + }; + }; + }; + 404: components["responses"]["NotFoundError"]; + }; + }; + updateWebhook: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateWebhookRequest"]; + }; + }; + responses: { + /** @description Webhook updated */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["WebhookResponse"]; + }; + }; + }; + 404: components["responses"]["NotFoundError"]; + }; + }; + deleteWebhook: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Webhook deleted */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + 404: components["responses"]["NotFoundError"]; + }; + }; + toggleWebhookStatus: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Webhook status toggled */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["WebhookResponse"]; + }; + }; + }; + 404: components["responses"]["NotFoundError"]; + }; + }; + listWebhookNotifications: { + parameters: { + query?: { + /** @description Number of records to return per page */ + limit?: components["parameters"]["LimitQuery"]; + /** @description Number of records to skip */ + offset?: components["parameters"]["OffsetQuery"]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Notification list */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: { + /** @description Total notification count */ + count?: number; + notification_list?: components["schemas"]["WebhookNotification"][]; + }; + }; + }; + }; + }; + }; + getWebhookNotification: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Notification details */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["WebhookNotification"]; + }; + }; + }; + 404: components["responses"]["NotFoundError"]; + }; + }; + listCustomers: { + parameters: { + query?: { + /** @description Number of records to return per page */ + limit?: components["parameters"]["LimitQuery"]; + /** @description Number of records to skip */ + offset?: components["parameters"]["OffsetQuery"]; + /** @description Comma-separated roles to filter by (case-insensitive) */ + target_role?: string; + /** @description Comma-separated provider registration statuses (case-insensitive) */ + provider_registration_status?: string; + /** @description Comma-separated provider names (case-insensitive) */ + provider?: string; + /** @description Comma-separated email addresses to filter by */ + email?: string; + /** @description Comma-separated document types (case-insensitive) */ + document_type?: string; + /** @description Comma-separated country codes (case-insensitive) */ + country_code?: string; + /** @description Enforce limit restrictions */ + strict?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Customer list */ + 200: { + headers: { + /** @description Requested limit value */ + "X-Limit-Requested"?: number; + /** @description Applied limit value */ + "X-Limit-Applied"?: number; + /** @description Requested offset value */ + "X-Offset-Requested"?: number; + /** @description Applied offset value */ + "X-Offset-Applied"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: { + /** @description Total customer count */ + count?: number; + customer_list?: components["schemas"]["CustomerResponse"][]; + }; + }; + }; + }; + }; + }; + createCustomer: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "document_number": "12345678901", + * "document_type": "cpf", + * "email": "customer@example.com", + * "first_name": "John", + * "last_name": "Doe", + * "dob": "1990-01-15", + * "phone_country_code": "+55", + * "phone_area_code": "11", + * "phone_number": "999999999", + * "country_code": "br" + * } + */ + "application/json": components["schemas"]["CreateCustomerRequest"]; + }; + }; + responses: { + /** @description Customer created */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["CustomerResponse"]; + }; + }; + }; + /** @description Validation error or customer already exists */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + 422: components["responses"]["ValidationError"]; + }; + }; + getCustomer: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Customer details */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["CustomerResponse"]; + }; + }; + }; + 404: components["responses"]["NotFoundError"]; + }; + }; + updateCustomer: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateCustomerRequest"]; + }; + }; + responses: { + /** @description Customer updated */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["CustomerResponse"]; + }; + }; + }; + /** @description Validation error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + 404: components["responses"]["NotFoundError"]; + }; + }; + syncCustomer: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "providers": [ + * "stripe" + * ], + * "fields": [ + * "email", + * "phone" + * ] + * } + */ + "application/json": components["schemas"]["SyncCustomerRequest"]; + }; + }; + responses: { + /** @description Sync scheduled */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "msg": "Sync scheduled", + * "data": null + * } + */ + "application/json": components["schemas"]["ApiResponse"]; + }; + }; + /** @description Invalid provider or fields */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + 404: components["responses"]["NotFoundError"]; + }; + }; + getCustomerBalances: { + parameters: { + query?: { + /** @description Comma-separated provider names (case-insensitive) */ + provider?: string; + /** @description Comma-separated roles (case-insensitive) */ + role?: string; + }; + header?: never; + path: { + customer_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Balance information */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["BalanceResponse"]; + }; + }; + }; + /** @description Invalid customer ID */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + 404: components["responses"]["NotFoundError"]; + }; + }; + getCustomerFiles: { + parameters: { + query?: never; + header?: never; + path: { + customer_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description File list */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: { + fileType?: string; + /** @description Presigned S3 URL (temporary) */ + url?: string; + }[]; + }; + }; + }; + 404: components["responses"]["NotFoundError"]; + }; + }; + uploadCustomerFiles: { + parameters: { + query?: never; + header?: never; + path: { + customer_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "multipart/form-data": { + /** + * Format: binary + * @description Document file (max 5MB) + */ + file?: string; + /** + * @description Document type + * @enum {string} + */ + file_type?: "SSN_BACK" | "SSN_FRONT" | "CPF_DOCUMENT" | "PASSPORT" | "ADDRESS_PROOF"; + }; + }; + }; + responses: { + /** @description File uploaded successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "msg": "file upload successful", + * "data": null + * } + */ + "application/json": components["schemas"]["ApiResponse"]; + }; + }; + /** @description Invalid file or missing data */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + 404: components["responses"]["NotFoundError"]; + }; + }; + listPaymentMethods: { + parameters: { + query?: { + /** @description Filter by payment method type (case-insensitive) */ + type?: string; + /** @description Filter by status (case-insensitive) */ + status?: string; + /** @description Filter by provider (case-insensitive) */ + provider?: string; + }; + header?: never; + path: { + customer_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Payment method list */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["PaymentMethodResponse"][]; + }; + }; + }; + }; + }; + createPaymentMethod: { + parameters: { + query?: never; + header?: never; + path: { + customer_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreatePaymentMethodRequest"]; + }; + }; + responses: { + /** @description Payment method created */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + msg?: string; + data?: components["schemas"]["PaymentMethodResponse"]; + provider_message?: string; + }; + }; + }; + /** @description Validation error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + 404: components["responses"]["NotFoundError"]; + 422: components["responses"]["ValidationError"]; + }; + }; + getPaymentMethod: { + parameters: { + query?: never; + header?: never; + path: { + customer_id: string; + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Payment method details */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["PaymentMethodResponse"]; + }; + }; + }; + 404: components["responses"]["NotFoundError"]; + }; + }; + updatePaymentMethod: { + parameters: { + query?: never; + header?: never; + path: { + customer_id: string; + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreatePaymentMethodRequest"]; + }; + }; + responses: { + /** @description Payment method updated */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["PaymentMethodResponse"]; + }; + }; + }; + /** @description Validation error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + 404: components["responses"]["NotFoundError"]; + }; + }; + deletePaymentMethod: { + parameters: { + query?: never; + header?: never; + path: { + customer_id: string; + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Payment method deleted */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + 404: components["responses"]["NotFoundError"]; + }; + }; + populateKycData: { + parameters: { + query?: never; + header?: never; + path: { + customer_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description Provider name (case-insensitive) */ + provider: string; + /** @description Target role for the provider registration */ + target_role: string; + } & { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Platform data submitted */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + msg?: string; + /** + * @description Registration status for this customer across every + * provider they are registered on, one entry per + * `(provider, target_role)` pair. Same shape as + * `GET /api/v1/provider-registration/{customer_id}/status`. + */ + data?: ({ + /** @description Provider name (lowercased). */ + provider?: string; + /** @description Target role on this provider (lowercased). */ + target_role?: string; + /** @description Current registration status (lowercased). */ + status?: string; + /** @description Provider-specific readiness flags (JSONB pass-through). */ + readiness?: { + [key: string]: unknown; + } | null; + /** @description Human-readable rejection reason when rejection-like. */ + rejection_reason?: string | null; + } & { + [key: string]: unknown; + })[]; + }; + }; + }; + /** @description Validation error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + 404: components["responses"]["NotFoundError"]; + 422: components["responses"]["ValidationError"]; + }; + }; + listTransactions: { + parameters: { + query?: { + /** @description Number of records to return per page */ + limit?: components["parameters"]["LimitQuery"]; + /** @description Number of records to skip */ + offset?: components["parameters"]["OffsetQuery"]; + /** @description Comma-separated transaction types (case-insensitive) */ + type_list?: string; + /** @description Filter by status (case-insensitive) */ + status?: string; + /** @description Filter by payment method type (case-insensitive) */ + payment_method?: string; + /** @description Filter by source currency (case-insensitive) */ + source_currency?: string; + /** @description Filter by destination currency (case-insensitive) */ + destination_currency?: string; + /** @description Filter by provider (case-insensitive) */ + provider?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Transaction list */ + 200: { + headers: { + "X-Limit-Requested"?: number; + "X-Limit-Applied"?: number; + "X-Offset-Requested"?: number; + "X-Offset-Applied"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: { + count?: number; + transaction_list?: components["schemas"]["TransactionResponse"][]; + }; + }; + }; + }; + }; + }; + getTransaction: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Transaction details */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["TransactionResponse"]; + }; + }; + }; + 404: components["responses"]["NotFoundError"]; + }; + }; + settleTransaction: { + parameters: { + query?: never; + header: { + /** @description Application key for settlement authorization */ + "app-key": string; + }; + path: { + /** @description Transaction UID */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + data: { + /** @description Provider charge identifier */ + charge_id: string; + /** @description Settlement amount in smallest currency unit */ + amount: number; + /** @example SETTLED */ + status: string; + }; + }; + }; + }; + responses: { + /** @description Transaction settled */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["TransactionResponse"]; + }; + }; + }; + /** @description Settlement failed or invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 404: components["responses"]["NotFoundError"]; + 422: components["responses"]["ValidationError"]; + }; + }; + createPayout: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "payment_method_id": "660e8400-e29b-41d4-a716-446655440000", + * "amount": 50000, + * "currency": "BRL", + * "customer_id": "550e8400-e29b-41d4-a716-446655440000" + * } + */ + "application/json": { + /** + * Format: uuid + * @description Target payment method ID + */ + payment_method_id: string; + /** @description Amount in smallest currency unit */ + amount: number; + /** + * @description Currency code + * @enum {string} + */ + currency: "USD" | "BRL"; + /** + * Format: uuid + * @description Customer ID + */ + customer_id: string; + /** @description Custom metadata */ + metadata?: { + [key: string]: unknown; + }; + }; + }; + }; + responses: { + /** @description Payout initiated */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["TransactionResponse"]; + }; + }; + }; + /** @description Invalid request or payment method not found */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + /** @description Customer or payment method not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 422: components["responses"]["ValidationError"]; + }; + }; + createTransfer: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description Provider for single-provider transfers (case-insensitive) */ + provider?: string; + /** @description Source account details */ + source: { + /** @description Source provider (for inter-platform transfers) */ + provider?: string; + currency?: string; + customer?: { + /** Format: uuid */ + id?: string; + }; + amount?: number; + }; + /** @description Destination account details */ + destination: { + /** @description Destination provider (for inter-platform transfers) */ + provider?: string; + customer?: { + /** Format: uuid */ + id?: string; + }; + payment_method?: { + type?: string; + /** Format: uuid */ + id?: string; + currency?: string; + chain?: string; + }; + }; + /** @description Custom metadata */ + metadata?: { + [key: string]: unknown; + }; + }; + }; + }; + responses: { + /** @description Transfer initiated */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["TransactionResponse"]; + }; + }; + }; + /** @description Invalid request or platform error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + 404: components["responses"]["NotFoundError"]; + 422: components["responses"]["ValidationError"]; + }; + }; + sendTransferWebhook: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * Format: uuid + * @description Transfer transaction UUID + */ + transfer_id: string; + /** @description Status to set */ + status: string; + }; + }; + }; + responses: { + /** @description Webhook sent */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + /** @example webhook sent successfully */ + msg?: unknown; + data?: null; + }; + }; + }; + /** @description Error sending webhook */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + }; + }; + createBuy: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description Provider name (case-insensitive) */ + provider: string; + source: { + amount?: number; + currency?: string; + customer?: { + /** Format: uuid */ + id?: string; + }; + payment_method?: { + type?: string; + }; + }; + destination?: { + currency?: string; + payment_method?: { + type?: string; + chain?: string; + }; + }; + /** + * @description Merchant-supplied arbitrary metadata, stored with the + * transaction and echoed back on derived webhook deliveries. + * Shape is defined by the merchant at request time. + */ + metadata?: { + [key: string]: unknown; + }; + }; + }; + }; + responses: { + /** @description Buy transaction initiated */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["TransactionResponse"]; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + 422: components["responses"]["ValidationError"]; + }; + }; + getWalletBalance: { + parameters: { + query?: never; + header?: never; + path: { + customer_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Wallet balance */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + /** @description Trading wallet balance information */ + data?: Record; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + }; + }; + createWalletBuy: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + amount: number; + provider: string; + payment_method: string; + currency: string; + /** Format: uuid */ + customer_id: string; + /** + * @description Merchant-supplied arbitrary metadata, stored with the + * transaction and echoed back on derived webhook deliveries. + * Shape is defined by the merchant at request time. + */ + metadata?: { + [key: string]: unknown; + }; + }; + }; + }; + responses: { + /** @description Buy trade initiated */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["TransactionResponse"]; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + 422: components["responses"]["ValidationError"]; + }; + }; + listDisputes: { + parameters: { + query?: { + /** @description Number of records to return per page */ + limit?: components["parameters"]["LimitQuery"]; + /** @description Number of records to skip */ + offset?: components["parameters"]["OffsetQuery"]; + /** @description Enforce limit restrictions */ + strict?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Dispute list */ + 200: { + headers: { + "X-Limit-Requested"?: number; + "X-Limit-Applied"?: number; + "X-Offset-Requested"?: number; + "X-Offset-Applied"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: { + count?: number; + dispute_list?: components["schemas"]["DisputeResponse"][]; + }; + }; + }; + }; + }; + }; + updateDisputeEvidence: { + parameters: { + query?: never; + header?: never; + path: { + dispute_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + file_evidences?: { + /** + * Format: uuid + * @description File ID + */ + id: string; + /** @enum {string} */ + type: "receipt" | "customer_signature" | "shipping_documentation" | "service_documentation" | "refund_policy" | "cancellation_policy" | "uncategorized_file"; + }[]; + text_evidences?: { + /** @enum {string} */ + key: "customer_name" | "customer_email_address" | "customer_purchase_ip" | "product_description" | "duplicate_charge_id" | "enhanced_evidence" | "customer_communication" | "refund_policy" | "refund_policy_disclosure" | "refund_refusal_explanation" | "service_date" | "shipping_address" | "shipping_carrier" | "shipping_date" | "shipping_tracking_number" | "shipping_tracking_url" | "duplicate_charge_explanation" | "cancellation_policy" | "cancellation_rebuttal" | "uncategorized_text"; + value: string | null; + }[]; + }; + }; + }; + responses: { + /** @description Evidence uploaded */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["DisputeResponse"]; + }; + }; + }; + /** @description Invalid evidence data */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + 404: components["responses"]["NotFoundError"]; + 422: components["responses"]["ValidationError"]; + }; + }; + submitDispute: { + parameters: { + query?: never; + header?: never; + path: { + dispute_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Dispute submitted */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["DisputeResponse"]; + }; + }; + }; + 404: components["responses"]["NotFoundError"]; + }; + }; + closeDispute: { + parameters: { + query?: never; + header?: never; + path: { + dispute_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Dispute closed */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["DisputeResponse"]; + }; + }; + }; + 404: components["responses"]["NotFoundError"]; + }; + }; + listPlans: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Plan list */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["PlanResponse"][]; + }; + }; + }; + }; + }; + createPlan: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "name": "Monthly Pro", + * "description": "Monthly professional plan", + * "frequency": 30, + * "price": 2999, + * "currency": "USD", + * "created_by": "admin@example.com" + * } + */ + "application/json": components["schemas"]["CreatePlanRequest"]; + }; + }; + responses: { + /** @description Plan created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + /** @example plan created */ + msg?: unknown; + /** @description Plan hash ID */ + data?: string; + }; + }; + }; + }; + }; + getPlan: { + parameters: { + query?: never; + header?: never; + path: { + planId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Plan details */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["PlanResponse"]; + }; + }; + }; + 404: components["responses"]["NotFoundError"]; + }; + }; + deletePlan: { + parameters: { + query?: never; + header?: never; + path: { + planId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Plan deleted */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + /** @example plan deleted */ + msg?: unknown; + data?: string; + }; + }; + }; + 404: components["responses"]["NotFoundError"]; + }; + }; + updatePlan: { + parameters: { + query?: never; + header?: never; + path: { + planId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreatePlanRequest"]; + }; + }; + responses: { + /** @description Plan updated */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + /** @example plan updated */ + msg?: unknown; + /** @description Plan hash ID */ + data?: string; + }; + }; + }; + }; + }; + publishPlan: { + parameters: { + query?: never; + header?: never; + path: { + planId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Plan published */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + /** @example plan published */ + msg?: unknown; + data?: string; + }; + }; + }; + }; + }; + subscribe: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description Plan hash ID */ + plan_id: string; + /** Format: uuid */ + source_customer_id: string; + /** Format: uuid */ + destination_customer_id: string; + /** Format: uuid */ + payment_method_id: string; + /** @enum {string} */ + payment_method_type: "CARD"; + payment_method_provider: string; + /** @enum {string} */ + fee_bearer: "connected_account"; + }; + }; + }; + responses: { + /** @description Subscription created */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + /** @example Subscription created successfully */ + msg?: unknown; + data?: { + /** @description Subscription hash ID */ + id?: string; + /** @example pending_activation */ + status?: string; + /** @example payment_init */ + sub_status?: string; + }; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + }; + }; + cancelSubscription: { + parameters: { + query?: never; + header?: never; + path: { + subscriptionId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Subscription cancelled */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example Subscription cancelled successfully */ + msg?: string; + /** + * @description Pass-through response from the upstream subscription + * service. Shape is not contractually stable; treat as + * opaque acknowledgement. + */ + data?: { + [key: string]: unknown; + }; + }; + }; + }; + 404: components["responses"]["NotFoundError"]; + }; + }; + listSubscriptions: { + parameters: { + query: { + customer_id: string; + /** @description Comma-separated statuses (active, pending_activation, canceled, expired, queued) */ + status?: string; + per_page?: number; + page_no?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Subscription list */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: { + data?: components["schemas"]["SubscriptionResponse"][]; + pagination?: { + per_page?: number; + page_no?: number; + total?: number; + }; + }; + }; + }; + }; + /** @description Validation error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + }; + }; + getSubscription: { + parameters: { + query?: never; + header?: never; + path: { + subscriptionId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Subscription details */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: components["schemas"]["SubscriptionResponse"]; + }; + }; + }; + 404: components["responses"]["NotFoundError"]; + }; + }; + initiateSubscriptionPayment: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Subscription hash ID. */ + subscriptionId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** Format: uuid */ + customer_id: string; + /** Format: uuid */ + payment_method_id: string; + /** @enum {string} */ + payment_method_type: "CARD"; + payment_method_provider: string; + }; + }; + }; + responses: { + /** @description Payment initiated */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: { + id?: string; + status?: string; + sub_status?: string; + }; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + 404: components["responses"]["NotFoundError"]; + }; + }; + getKycSchema: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description KYC schema */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + /** @description Provider-specific KYC schema definition */ + data?: Record; + }; + }; + }; + }; + }; + submitKyc: { + parameters: { + query?: never; + header?: never; + path: { + customer_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description Provider name (case-insensitive) */ + provider: string; + /** @description Target role for registration */ + target_role: string; + } & { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Registration submitted */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: Record; + }; + }; + }; + /** @description Validation error or submission failed */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + 404: components["responses"]["NotFoundError"]; + 422: components["responses"]["ValidationError"]; + }; + }; + getKycStatus: { + parameters: { + query?: never; + header?: never; + path: { + customer_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Registration status */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + /** @description Provider registration status per platform */ + data?: Record; + }; + }; + }; + 404: components["responses"]["NotFoundError"]; + }; + }; + calculateTaxes: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description Provider name (case-insensitive) */ + provider: string; + } & { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Tax calculation result */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + msg?: string; + /** + * @description Tax calculation result. Echoes the request body fields + * and adds a `provider_response` object containing the + * raw tax-provider reply. Exact keys inside + * `provider_response` vary per provider. + */ + data?: { + /** @description Provider that computed the tax (lowercased). */ + provider?: string; + /** @description Raw upstream tax-provider response. Shape varies. */ + provider_response?: { + [key: string]: unknown; + }; + } & { + [key: string]: unknown; + }; + }; + }; + }; + /** @description Calculation error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + }; + }; + listFiles: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description File list */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: Record[]; + }; + }; + }; + }; + }; + uploadFiles: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "multipart/form-data": { + /** + * Format: binary + * @description File to upload (use repeated `file` fields to upload multiple). + */ + file?: string; + }; + }; + }; + responses: { + /** @description Files uploaded */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: Record; + }; + }; + }; + /** @description Upload error (size / MIME-type violation, storage failure). */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + }; + }; + getFile: { + parameters: { + query?: never; + header?: never; + path: { + file_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description File details */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: Record; + }; + }; + }; + 404: components["responses"]["NotFoundError"]; + }; + }; + deleteFile: { + parameters: { + query?: never; + header?: never; + path: { + file_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description File deleted */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + 404: components["responses"]["NotFoundError"]; + }; + }; + genericProviderProxy: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Provider name */ + provider: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Provider response (pass-through) */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + /** @description Request not whitelisted or provider error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorResponse"]; + }; + }; + 401: components["responses"]["UnauthorizedError"]; + 403: components["responses"]["ForbiddenError"]; + 404: components["responses"]["NotFoundError"]; + }; + }; + healthCheck: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description All systems healthy */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description One or more systems unhealthy */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example error */ + status?: string; + /** @example RabbitMQ is not available */ + message?: string; + }; + }; + }; + }; + }; + listCountries: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Country list */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse"] & { + data?: { + countries: components["schemas"]["CountryResponse"][]; + }; + }; + }; + }; + }; + }; + webhookPaymentAwaitingConfirmation: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment.awaiting_confirmation"; + /** @constant */ + category?: "payment_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookPaymentCancelled: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment.cancelled"; + /** @constant */ + category?: "payment_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookPaymentCaptured: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment.captured"; + /** @constant */ + category?: "payment_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookPaymentFailed: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment.failed"; + /** @constant */ + category?: "payment_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookInstallmentFailed: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment.installment.failed"; + /** @constant */ + category?: "payment_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookInstallmentSucceeded: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment.installment.succeeded"; + /** @constant */ + category?: "payment_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookPaymentProcessing: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment.processing"; + /** @constant */ + category?: "payment_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookPaymentRefunded: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment.refunded"; + /** @constant */ + category?: "payment_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookPaymentSucceeded: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment.succeeded"; + /** @constant */ + category?: "payment_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookPaymentUpdated: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment.updated"; + /** @constant */ + category?: "payment_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookTransactionScUpdated: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "transaction.sc_updated"; + /** @constant */ + category?: "payment_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookTransactionUpdated: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "transaction.updated"; + /** @constant */ + category?: "payment_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookRefundCreated: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment.refund.created"; + /** @constant */ + category?: "refund_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookRefundFailed: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment.refund.failed"; + /** @constant */ + category?: "refund_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookRefundUpdated: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment.refund.updated"; + /** @constant */ + category?: "refund_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookStandaloneRefundFailed: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "refund.failed"; + /** @constant */ + category?: "refund_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookStandaloneRefundSucceeded: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "refund.succeeded"; + /** @constant */ + category?: "refund_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookDisputeClosed: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment.dispute.closed"; + /** @constant */ + category?: "dispute_lifecycle"; + data?: components["schemas"]["WebhookDisputeData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookDisputeFundsReinstated: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment.dispute.funds_reinstated"; + /** @constant */ + category?: "dispute_lifecycle"; + data?: components["schemas"]["WebhookDisputeData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookDisputeFundsWithdrawn: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment.dispute.funds_withdrawn"; + /** @constant */ + category?: "dispute_lifecycle"; + data?: components["schemas"]["WebhookDisputeData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookDisputeUpdated: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment.dispute.updated"; + /** @constant */ + category?: "dispute_lifecycle"; + data?: components["schemas"]["WebhookDisputeData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookDisputeCreated: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment.disputed"; + /** @constant */ + category?: "dispute_lifecycle"; + data?: components["schemas"]["WebhookDisputeData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookCustomerActionRequired: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "customer.action_required"; + /** @constant */ + category?: "provider_registration_lifecycle"; + data?: components["schemas"]["WebhookProviderRegistrationData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookCustomerCreated: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "customer.created"; + /** @constant */ + category?: "provider_registration_lifecycle"; + data?: components["schemas"]["WebhookProviderRegistrationData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookCustomerProcessing: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "customer.processing"; + /** @constant */ + category?: "provider_registration_lifecycle"; + data?: components["schemas"]["WebhookProviderRegistrationData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookCustomerRejected: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "customer.rejected"; + /** @constant */ + category?: "provider_registration_lifecycle"; + data?: components["schemas"]["WebhookProviderRegistrationData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookCustomerUpdated: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "customer.updated"; + /** @constant */ + category?: "provider_registration_lifecycle"; + data?: components["schemas"]["WebhookProviderRegistrationData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookCustomerVerified: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "customer.verified"; + /** @constant */ + category?: "provider_registration_lifecycle"; + data?: components["schemas"]["WebhookProviderRegistrationData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookKycActionRequired: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "kyc.action_required"; + /** @constant */ + category?: "provider_registration_lifecycle"; + data?: components["schemas"]["WebhookProviderRegistrationData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookKycApproved: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "kyc.approved"; + /** @constant */ + category?: "provider_registration_lifecycle"; + data?: components["schemas"]["WebhookProviderRegistrationData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookKycRejected: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "kyc.rejected"; + /** @constant */ + category?: "provider_registration_lifecycle"; + data?: components["schemas"]["WebhookProviderRegistrationData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookProvRegActionRequired: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "provider_registration.action_required"; + /** @constant */ + category?: "provider_registration_lifecycle"; + data?: components["schemas"]["WebhookProviderRegistrationData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookProvRegApproved: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "provider_registration.approved"; + /** @constant */ + category?: "provider_registration_lifecycle"; + data?: components["schemas"]["WebhookProviderRegistrationData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookProvRegAwaitingConfirmation: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "provider_registration.awaiting_confirmation"; + /** @constant */ + category?: "provider_registration_lifecycle"; + data?: components["schemas"]["WebhookProviderRegistrationData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookProvRegDocumentsUploaded: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "provider_registration.documents_uploaded"; + /** @constant */ + category?: "provider_registration_lifecycle"; + data?: components["schemas"]["WebhookProviderRegistrationData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookProvRegProcessing: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "provider_registration.processing"; + /** @constant */ + category?: "provider_registration_lifecycle"; + data?: components["schemas"]["WebhookProviderRegistrationData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookProvRegRejected: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "provider_registration.rejected"; + /** @constant */ + category?: "provider_registration_lifecycle"; + data?: components["schemas"]["WebhookProviderRegistrationData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookProvRegRestricted: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "provider_registration.restricted"; + /** @constant */ + category?: "provider_registration_lifecycle"; + data?: components["schemas"]["WebhookProviderRegistrationData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookProvRegSubmitted: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "provider_registration.submitted"; + /** @constant */ + category?: "provider_registration_lifecycle"; + data?: components["schemas"]["WebhookProviderRegistrationData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookProvRegUpdated: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "provider_registration.updated"; + /** @constant */ + category?: "provider_registration_lifecycle"; + data?: components["schemas"]["WebhookProviderRegistrationData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookProvRegVerificationExpired: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "provider_registration.verification_expired"; + /** @constant */ + category?: "provider_registration_lifecycle"; + data?: components["schemas"]["WebhookProviderRegistrationData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookCustomerSyncFailed: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "customer.sync.failed"; + /** @constant */ + category?: "customer_sync_lifecycle"; + data?: components["schemas"]["WebhookCustomerSyncData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookCustomerSyncStarted: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "customer.sync.started"; + /** @constant */ + category?: "customer_sync_lifecycle"; + data?: components["schemas"]["WebhookCustomerSyncData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookCustomerSyncSucceeded: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "customer.sync.succeeded"; + /** @constant */ + category?: "customer_sync_lifecycle"; + data?: components["schemas"]["WebhookCustomerSyncData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookPaymentMethodApproved: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment_method.approved"; + /** @constant */ + category?: "payment_method_lifecycle"; + data?: components["schemas"]["WebhookPaymentMethodData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookPaymentMethodCreated: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment_method.created"; + /** @constant */ + category?: "payment_method_lifecycle"; + data?: components["schemas"]["WebhookPaymentMethodData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookPaymentMethodRejected: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment_method.rejected"; + /** @constant */ + category?: "payment_method_lifecycle"; + data?: components["schemas"]["WebhookPaymentMethodData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookPaymentMethodUpdated: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment_method.updated"; + /** @constant */ + category?: "payment_method_lifecycle"; + data?: components["schemas"]["WebhookPaymentMethodData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookPaymentMethodVerified: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payment_method.verified"; + /** @constant */ + category?: "payment_method_lifecycle"; + data?: components["schemas"]["WebhookPaymentMethodData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookTransferFailed: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "transfer.failed"; + /** @constant */ + category?: "transfer_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookTransferSucceeded: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "transfer.succeeded"; + /** @constant */ + category?: "transfer_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookPayoutSucceeded: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "payout.succeeded"; + /** @constant */ + category?: "payout_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookBuyAwaitingConfirmation: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "buy.awaiting_confirmation"; + /** @constant */ + category?: "buy_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookBuyCompleted: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "buy.completed"; + /** @constant */ + category?: "buy_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookBuySucceeded: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "buy.succeeded"; + /** @constant */ + category?: "buy_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookSellFailed: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "sell.failed"; + /** @constant */ + category?: "sell_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookSellSucceeded: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "sell.succeeded"; + /** @constant */ + category?: "sell_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookExternalDepositAwaitingConfirmation: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "external.deposit.awaiting_confirmation"; + /** @constant */ + category?: "externally_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookExternalDepositCompleted: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "external.deposit.completed"; + /** @constant */ + category?: "externally_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookExternalDepositReceived: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "external.deposit.received"; + /** @constant */ + category?: "externally_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookExternalDepositSucceeded: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "external.deposit.succeeded"; + /** @constant */ + category?: "externally_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webhookExternalVirtualAccountFunded: { + parameters: { + query?: never; + header: { + /** + * @description HMAC-SHA256 signature over the raw request body, signed with the + * merchant's webhook secret. Format: `t={unix-ts},v1={hex-hmac}`. + */ + "CrowdSplit-Signature": components["parameters"]["CrowdSplitSignatureHeader"]; + /** + * @description Unix timestamp (seconds since epoch) at dispatch. Merchants should + * reject the delivery if the skew against their own clock exceeds + * 5 minutes, to limit replay windows. + */ + "CrowdSplit-Timestamp": components["parameters"]["CrowdSplitTimestampHeader"]; + /** + * @description Unique delivery ID. Stable across retries of the same logical event + * — use it to deduplicate idempotently on receipt. + */ + "CrowdSplit-Notification-Id": components["parameters"]["CrowdSplitNotificationIdHeader"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["WebhookEnvelope"] & { + /** @constant */ + event?: "external.virtual_account.funded"; + /** @constant */ + category?: "externally_lifecycle"; + data?: components["schemas"]["WebhookTransactionData"]; + }; + }; + }; + responses: { + /** @description Webhook acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9de7e828..e701397f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,7 +45,10 @@ importers: version: 10.9.2(@types/node@20.19.33)(typescript@5.9.3) tsup: specifier: ^8.5.1 - version: 8.5.1(typescript@5.9.3) + version: 8.5.1(tsx@4.23.1)(typescript@5.9.3) + tsx: + specifier: ^4.20.0 + version: 4.23.1 typescript: specifier: ^5.5.4 version: 5.9.3 @@ -67,6 +70,9 @@ importers: nock: specifier: ^14.0.10 version: 14.0.11 + openapi-typescript: + specifier: 7.13.0 + version: 7.13.0(typescript@5.9.3) ts-jest: specifier: ^29.4.6 version: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(esbuild@0.27.3)(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.33)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)))(typescript@5.9.3) @@ -325,156 +331,312 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.27.3': resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.27.3': resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.27.3': resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.27.3': resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.27.3': resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.27.3': resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.27.3': resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.27.3': resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.27.3': resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.27.3': resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.27.3': resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.27.3': resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.27.3': resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.27.3': resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.27.3': resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.27.3': resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.27.3': resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.27.3': resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.27.3': resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.27.3': resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.27.3': resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.27.3': resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.27.3': resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.27.3': resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.27.3': resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@inquirer/external-editor@1.0.3': resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} engines: {node: '>=18'} @@ -651,6 +813,16 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@redocly/ajv@8.11.2': + resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==} + + '@redocly/config@0.22.0': + resolution: {integrity: sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==} + + '@redocly/openapi-core@1.34.17': + resolution: {integrity: sha512-wsV2keCt6B806XpSdezbWZ9aFJYf14YVh+XQf0ESt7M90yqVuxH9//PxvtC70sgj9OCkRM3nRaLfu4MsGQZRig==} + engines: {node: '>=18.17.0', npm: '>=9.5.0'} + '@rollup/rollup-android-arm-eabi@4.59.0': resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} cpu: [arm] @@ -969,6 +1141,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -1107,6 +1283,9 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} @@ -1147,6 +1326,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -1237,6 +1419,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1268,6 +1455,9 @@ packages: extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -1362,6 +1552,10 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + human-id@4.1.3: resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} hasBin: true @@ -1387,6 +1581,10 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + index-to-position@1.2.0: + resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} + engines: {node: '>=18'} + is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -1588,6 +1786,10 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} + js-levenshtein@1.1.6: + resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} + engines: {node: '>=0.10.0'} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1599,6 +1801,10 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + js-yaml@4.2.0: + resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} + hasBin: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -1607,6 +1813,9 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} @@ -1738,6 +1947,12 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + openapi-typescript@7.13.0: + resolution: {integrity: sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==} + hasBin: true + peerDependencies: + typescript: ^5.x + outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} @@ -1786,6 +2001,10 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-json@8.3.0: + resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} + engines: {node: '>=18'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1831,6 +2050,10 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + postcss-load-config@6.0.1: resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} engines: {node: '>= 18'} @@ -1886,6 +2109,10 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-cwd@3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} @@ -2002,6 +2229,10 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -2113,6 +2344,11 @@ packages: typescript: optional: true + tsx@4.23.1: + resolution: {integrity: sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==} + engines: {node: '>=18.0.0'} + hasBin: true + type-detect@4.0.8: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} @@ -2154,6 +2390,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + uri-js-replace@1.0.1: + resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==} + v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -2211,6 +2450,9 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml-ast-parser@0.0.43: + resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -2252,7 +2494,7 @@ snapshots: '@babel/types': 7.29.0 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -2411,7 +2653,7 @@ snapshots: '@babel/parser': 7.29.0 '@babel/template': 7.28.6 '@babel/types': 7.29.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -2589,81 +2831,159 @@ snapshots: '@esbuild/aix-ppc64@0.27.3': optional: true + '@esbuild/aix-ppc64@0.28.1': + optional: true + '@esbuild/android-arm64@0.27.3': optional: true + '@esbuild/android-arm64@0.28.1': + optional: true + '@esbuild/android-arm@0.27.3': optional: true + '@esbuild/android-arm@0.28.1': + optional: true + '@esbuild/android-x64@0.27.3': optional: true + '@esbuild/android-x64@0.28.1': + optional: true + '@esbuild/darwin-arm64@0.27.3': optional: true + '@esbuild/darwin-arm64@0.28.1': + optional: true + '@esbuild/darwin-x64@0.27.3': optional: true + '@esbuild/darwin-x64@0.28.1': + optional: true + '@esbuild/freebsd-arm64@0.27.3': optional: true + '@esbuild/freebsd-arm64@0.28.1': + optional: true + '@esbuild/freebsd-x64@0.27.3': optional: true + '@esbuild/freebsd-x64@0.28.1': + optional: true + '@esbuild/linux-arm64@0.27.3': optional: true + '@esbuild/linux-arm64@0.28.1': + optional: true + '@esbuild/linux-arm@0.27.3': optional: true + '@esbuild/linux-arm@0.28.1': + optional: true + '@esbuild/linux-ia32@0.27.3': optional: true + '@esbuild/linux-ia32@0.28.1': + optional: true + '@esbuild/linux-loong64@0.27.3': optional: true + '@esbuild/linux-loong64@0.28.1': + optional: true + '@esbuild/linux-mips64el@0.27.3': optional: true + '@esbuild/linux-mips64el@0.28.1': + optional: true + '@esbuild/linux-ppc64@0.27.3': optional: true + '@esbuild/linux-ppc64@0.28.1': + optional: true + '@esbuild/linux-riscv64@0.27.3': optional: true + '@esbuild/linux-riscv64@0.28.1': + optional: true + '@esbuild/linux-s390x@0.27.3': optional: true + '@esbuild/linux-s390x@0.28.1': + optional: true + '@esbuild/linux-x64@0.27.3': optional: true + '@esbuild/linux-x64@0.28.1': + optional: true + '@esbuild/netbsd-arm64@0.27.3': optional: true + '@esbuild/netbsd-arm64@0.28.1': + optional: true + '@esbuild/netbsd-x64@0.27.3': optional: true + '@esbuild/netbsd-x64@0.28.1': + optional: true + '@esbuild/openbsd-arm64@0.27.3': optional: true + '@esbuild/openbsd-arm64@0.28.1': + optional: true + '@esbuild/openbsd-x64@0.27.3': optional: true + '@esbuild/openbsd-x64@0.28.1': + optional: true + '@esbuild/openharmony-arm64@0.27.3': optional: true + '@esbuild/openharmony-arm64@0.28.1': + optional: true + '@esbuild/sunos-x64@0.27.3': optional: true + '@esbuild/sunos-x64@0.28.1': + optional: true + '@esbuild/win32-arm64@0.27.3': optional: true + '@esbuild/win32-arm64@0.28.1': + optional: true + '@esbuild/win32-ia32@0.27.3': optional: true + '@esbuild/win32-ia32@0.28.1': + optional: true + '@esbuild/win32-x64@0.27.3': optional: true + '@esbuild/win32-x64@0.28.1': + optional: true + '@inquirer/external-editor@1.0.3(@types/node@20.19.33)': dependencies: chardet: 2.1.1 @@ -2959,6 +3279,29 @@ snapshots: '@pkgr/core@0.2.9': {} + '@redocly/ajv@8.11.2': + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js-replace: 1.0.1 + + '@redocly/config@0.22.0': {} + + '@redocly/openapi-core@1.34.17(supports-color@10.2.2)': + dependencies: + '@redocly/ajv': 8.11.2 + '@redocly/config': 0.22.0 + colorette: 1.4.0 + https-proxy-agent: 7.0.6(supports-color@10.2.2) + js-levenshtein: 1.1.6 + js-yaml: 4.2.0 + minimatch: 10.2.2 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - supports-color + '@rollup/rollup-android-arm-eabi@4.59.0': optional: true @@ -3193,6 +3536,8 @@ snapshots: acorn@8.16.0: {} + agent-base@7.1.4: {} + ansi-colors@4.1.3: {} ansi-escapes@4.3.2: @@ -3334,6 +3679,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + change-case@5.4.4: {} + char-regex@1.0.2: {} chardet@2.1.1: {} @@ -3364,6 +3711,8 @@ snapshots: color-name@1.1.4: {} + colorette@1.4.0: {} + commander@4.1.1: {} confbox@0.1.8: {} @@ -3380,9 +3729,11 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - debug@4.4.3: + debug@4.4.3(supports-color@10.2.2): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 10.2.2 dedent@1.7.1: {} @@ -3448,6 +3799,35 @@ snapshots: '@esbuild/win32-ia32': 0.27.3 '@esbuild/win32-x64': 0.27.3 + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + escalade@3.2.0: {} escape-string-regexp@2.0.0: {} @@ -3481,6 +3861,8 @@ snapshots: extendable-error@0.1.7: {} + fast-deep-equal@3.1.3: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3583,6 +3965,13 @@ snapshots: html-escaper@2.0.2: {} + https-proxy-agent@7.0.6(supports-color@10.2.2): + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + human-id@4.1.3: {} human-signals@2.1.0: {} @@ -3600,6 +3989,8 @@ snapshots: imurmurhash@0.1.4: {} + index-to-position@1.2.0: {} + is-arrayish@0.2.1: {} is-extglob@2.1.1: {} @@ -3651,7 +4042,7 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: '@jridgewell/trace-mapping': 0.3.31 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -3981,6 +4372,8 @@ snapshots: joycon@3.1.1: {} + js-levenshtein@1.1.6: {} + js-tokens@4.0.0: {} js-yaml@3.14.2: @@ -3992,10 +4385,16 @@ snapshots: dependencies: argparse: 2.0.1 + js-yaml@4.2.0: + dependencies: + argparse: 2.0.1 + jsesc@3.1.0: {} json-parse-even-better-errors@2.3.1: {} + json-schema-traverse@1.0.0: {} + json-stringify-safe@5.0.1: {} json5@2.2.3: {} @@ -4104,6 +4503,16 @@ snapshots: dependencies: mimic-fn: 2.1.0 + openapi-typescript@7.13.0(typescript@5.9.3): + dependencies: + '@redocly/openapi-core': 1.34.17(supports-color@10.2.2) + ansi-colors: 4.1.3 + change-case: 5.4.4 + parse-json: 8.3.0 + supports-color: 10.2.2 + typescript: 5.9.3 + yargs-parser: 21.1.1 + outdent@0.5.0: {} outvariant@1.4.3: {} @@ -4156,6 +4565,12 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-json@8.3.0: + dependencies: + '@babel/code-frame': 7.29.0 + index-to-position: 1.2.0 + type-fest: 4.41.0 + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -4189,9 +4604,13 @@ snapshots: mlly: 1.8.1 pathe: 2.0.3 - postcss-load-config@6.0.1: + pluralize@8.0.0: {} + + postcss-load-config@6.0.1(tsx@4.23.1): dependencies: lilconfig: 3.1.3 + optionalDependencies: + tsx: 4.23.1 prettier@2.8.8: {} @@ -4222,6 +4641,8 @@ snapshots: require-directory@2.1.1: {} + require-from-string@2.0.2: {} + resolve-cwd@3.0.0: dependencies: resolve-from: 5.0.0 @@ -4348,6 +4769,8 @@ snapshots: tinyglobby: 0.2.15 ts-interface-checker: 0.1.13 + supports-color@10.2.2: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -4435,18 +4858,18 @@ snapshots: tslib@2.8.1: optional: true - tsup@8.5.1(typescript@5.9.3): + tsup@8.5.1(tsx@4.23.1)(typescript@5.9.3): dependencies: bundle-require: 5.1.0(esbuild@0.27.3) cac: 6.7.14 chokidar: 4.0.3 consola: 3.4.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) esbuild: 0.27.3 fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1 + postcss-load-config: 6.0.1(tsx@4.23.1) resolve-from: 5.0.0 rollup: 4.59.0 source-map: 0.7.6 @@ -4462,6 +4885,12 @@ snapshots: - tsx - yaml + tsx@4.23.1: + dependencies: + esbuild: 0.28.1 + optionalDependencies: + fsevents: 2.3.3 + type-detect@4.0.8: {} type-fest@0.21.3: {} @@ -4509,6 +4938,8 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + uri-js-replace@1.0.1: {} + v8-compile-cache-lib@3.0.1: {} v8-to-istanbul@9.3.0: @@ -4567,6 +4998,8 @@ snapshots: yallist@3.1.1: {} + yaml-ast-parser@0.0.43: {} + yargs-parser@21.1.1: {} yargs@17.7.2: diff --git a/scripts/sync-crowdsplit.mjs b/scripts/sync-crowdsplit.mjs new file mode 100644 index 00000000..ccca3b01 --- /dev/null +++ b/scripts/sync-crowdsplit.mjs @@ -0,0 +1,291 @@ +#!/usr/bin/env node +/** + * Syncs the Crowdsplit OpenAPI spec into the payments SDK. + * + * Bundles the multi-file spec from a crowdsplit checkout (at a release tag), + * snapshots it into packages/payments/openapi/, regenerates the internal + * generated-types layer, and prepares changeset + PR-body material. All writes + * are deterministic (no timestamps) so re-runs for the same tag are + * byte-identical. + * + * Usage: + * node scripts/sync-crowdsplit.mjs --crowdsplit-dir --tag vX.Y.Z [--sha ] [--skip-diff] + * + * Requires Node >= 20 (openapi-typescript engine floor). No runtime deps: + * shells out to `npx @redocly/cli` (version taken from the crowdsplit checkout), + * `pnpm exec openapi-typescript` (pinned in packages/payments), and optionally + * `oasdiff` when present on PATH. + * + * Run from the SDK repo root. Used by .github/workflows/crowdsplit-sync.yml and + * runnable locally for dry-runs/seeding. + */ + +import { spawnSync } from "node:child_process"; +import { createHash } from "node:crypto"; +import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import { parseArgs } from "node:util"; + +const REPO_ROOT = process.cwd(); +const PAYMENTS_DIR = join(REPO_ROOT, "packages", "payments"); +const SNAPSHOT_PATH = join(PAYMENTS_DIR, "openapi", "openapi.bundled.yaml"); +const META_PATH = join(PAYMENTS_DIR, "openapi", ".sync-meta.json"); +const GENERATED_TYPES_RELATIVE = join("src", "generated", "api.ts"); +const TAG_PATTERN = /^v\d+\.\d+\.\d+$/; +const MAX_CHANGELOG_IN_BODY = 50_000; + +function fail(message) { + console.error(`error: ${message}`); + process.exit(1); +} + +// 64 MB stdout budget: oasdiff changelogs for large releases can exceed the +// spawnSync default of 1 MB, which would surface as a spurious ENOBUFS error. +const MAX_SPAWN_BUFFER = 64 * 1024 * 1024; + +function run(command, args, options = {}) { + const result = spawnSync(command, args, { encoding: "utf8", maxBuffer: MAX_SPAWN_BUFFER, ...options }); + if (result.error) { + throw new Error(`${command} failed to start: ${result.error.message}`); + } + return result; +} + +function runOrFail(command, args, options = {}) { + const result = run(command, args, { stdio: ["ignore", "inherit", "inherit"], ...options }); + if (result.status !== 0) { + fail(`${command} ${args.join(" ")} exited with status ${result.status}`); + } + return result; +} + +function sha256(content) { + return createHash("sha256").update(content).digest("hex"); +} + +function readJson(path) { + return JSON.parse(readFileSync(path, "utf8")); +} + +function emitOutput(name, value) { + console.log(`output: ${name}=${value}`); + if (process.env.GITHUB_OUTPUT) { + appendFileSync(process.env.GITHUB_OUTPUT, `${name}=${value}\n`); + } +} + +/** Extracts "METHOD /path" operation ids from a bundled OpenAPI JSON document. */ +function listEndpoints(spec) { + const methods = ["get", "put", "post", "delete", "options", "head", "patch", "trace"]; + const endpoints = []; + for (const [path, item] of Object.entries(spec.paths ?? {})) { + for (const method of methods) { + if (item && typeof item === "object" && method in item) { + endpoints.push(`${method.toUpperCase()} ${path}`); + } + } + } + return endpoints.sort(); +} + +function parseCliArgs() { + try { + return parseArgs({ + options: { + "crowdsplit-dir": { type: "string" }, + tag: { type: "string" }, + sha: { type: "string" }, + "skip-diff": { type: "boolean", default: false }, + }, + }); + } catch (error) { + fail(error instanceof Error ? error.message : String(error)); + } +} + +function main() { + const { values } = parseCliArgs(); + + const crowdsplitDir = values["crowdsplit-dir"] && resolve(values["crowdsplit-dir"]); + const tag = values.tag; + if (!crowdsplitDir || !tag) { + fail("usage: node scripts/sync-crowdsplit.mjs --crowdsplit-dir --tag vX.Y.Z [--sha ] [--skip-diff]"); + } + if (!TAG_PATTERN.test(tag)) { + fail(`tag "${tag}" does not match stable release pattern vX.Y.Z`); + } + const specEntry = join(crowdsplitDir, "openapi", "openapi.yaml"); + if (!existsSync(specEntry)) { + fail(`crowdsplit spec not found at ${specEntry}`); + } + if (!existsSync(join(PAYMENTS_DIR, "package.json"))) { + fail("run this script from the SDK repo root"); + } + + // Bundler version tracks whatever the spec authors declare. + const crowdsplitPkg = readJson(join(crowdsplitDir, "package.json")); + const redoclyVersion = (crowdsplitPkg.devDependencies?.["@redocly/cli"] ?? "latest").replace(/^[\^~]/, ""); + const paymentsPkg = readJson(join(PAYMENTS_DIR, "package.json")); + const openapiTsVersion = paymentsPkg.devDependencies?.["openapi-typescript"]; + if (!openapiTsVersion) { + fail("openapi-typescript must be a devDependency of @oaknetwork/payments-sdk"); + } + + const workDir = join(process.env.RUNNER_TEMP ?? tmpdir(), `crowdsplit-sync-${tag}`); + mkdirSync(workDir, { recursive: true }); + const newYamlPath = join(workDir, "openapi.bundled.yaml"); + const newJsonPath = join(workDir, "openapi.bundled.json"); + + console.log(`Bundling spec with @redocly/cli@${redoclyVersion} ...`); + runOrFail("npx", ["-y", `@redocly/cli@${redoclyVersion}`, "bundle", "openapi/openapi.yaml", "-o", newYamlPath], { + cwd: crowdsplitDir, + }); + runOrFail( + "npx", + ["-y", `@redocly/cli@${redoclyVersion}`, "bundle", "openapi/openapi.yaml", "--ext", "json", "-o", newJsonPath], + { cwd: crowdsplitDir }, + ); + + const newYaml = readFileSync(newYamlPath, "utf8"); + const newSpecSha = sha256(newYaml); + const previousYaml = existsSync(SNAPSHOT_PATH) ? readFileSync(SNAPSHOT_PATH, "utf8") : null; + const previousMeta = existsSync(META_PATH) ? readJson(META_PATH) : null; + + if (previousYaml !== null && sha256(previousYaml) === newSpecSha) { + console.log(`Spec content is unchanged since ${previousMeta?.tag ?? "the last sync"}, nothing to do.`); + emitOutput("changed", "false"); + return; + } + + // Endpoint-level diff, computed natively from the bundled JSON documents. + const newEndpoints = listEndpoints(readJson(newJsonPath)); + const previousEndpoints = previousMeta?.endpoints ?? []; + const addedEndpoints = newEndpoints.filter((e) => !previousEndpoints.includes(e)); + const removedEndpoints = previousEndpoints.filter((e) => !newEndpoints.includes(e)); + + // Detailed changelog + breaking verdict via oasdiff, when available. + let changelogText = ""; + let oasdiffBreaking = null; + const previousYamlPath = join(workDir, "previous.yaml"); + if (!values["skip-diff"] && previousYaml !== null) { + writeFileSync(previousYamlPath, previousYaml, "utf8"); + // Probe directly with spawnSync: an absent binary sets result.error + // (ENOENT), which must mean "skip the diff", not "crash the sync". + const probe = spawnSync("oasdiff", ["--version"], { encoding: "utf8" }); + if (!probe.error && probe.status === 0) { + const changelog = run("oasdiff", ["changelog", previousYamlPath, newYamlPath], { stdio: "pipe" }); + changelogText = changelog.status === 0 ? changelog.stdout.trim() : ""; + if (changelog.status !== 0) { + console.warn("oasdiff changelog failed; the PR body will link the compare view instead."); + } + const breaking = run("oasdiff", ["breaking", "--fail-on", "WARN", previousYamlPath, newYamlPath], { + stdio: "pipe", + }); + oasdiffBreaking = breaking.status !== 0; + } else { + console.warn("oasdiff not found on PATH; skipping detailed changelog."); + } + } + const breaking = oasdiffBreaking === null ? removedEndpoints.length > 0 : oasdiffBreaking; + + // Generate types into the temp dir first. The working tree is only touched + // once every generation step has succeeded. + console.log("Generating types with openapi-typescript ..."); + const openapiTsBin = join(PAYMENTS_DIR, "node_modules", ".bin", "openapi-typescript"); + if (!existsSync(openapiTsBin)) { + fail("openapi-typescript is not installed; run pnpm install first"); + } + const generatedTypesPath = join(workDir, "api.ts"); + runOrFail(openapiTsBin, [newYamlPath, "-o", generatedTypesPath], { cwd: PAYMENTS_DIR }); + + // Write snapshot, metadata, generated types, changeset. + mkdirSync(join(PAYMENTS_DIR, "openapi"), { recursive: true }); + writeFileSync(SNAPSHOT_PATH, newYaml, "utf8"); + const meta = { + tag, + commit: values.sha ?? null, + previousTag: previousMeta?.tag ?? null, + specSha256: newSpecSha, + redoclyVersion, + openapiTypescriptVersion: openapiTsVersion, + endpoints: newEndpoints, + }; + writeFileSync(META_PATH, `${JSON.stringify(meta, null, 2)}\n`, "utf8"); + mkdirSync(join(PAYMENTS_DIR, "src", "generated"), { recursive: true }); + writeFileSync(join(PAYMENTS_DIR, GENERATED_TYPES_RELATIVE), readFileSync(generatedTypesPath, "utf8"), "utf8"); + + const changesetPath = join(REPO_ROOT, ".changeset", `crowdsplit-sync-${tag.replace(/\./g, "-")}.md`); + writeFileSync( + changesetPath, + `---\n"@oaknetwork/payments-sdk": patch\n---\n\nSync generated API types and spec snapshot with Crowdsplit ${tag}\n`, + "utf8", + ); + + // PR body. + const previousTag = previousMeta?.tag ?? null; + const compareLink = previousTag + ? `https://github.com/oak-network/crowdsplit/compare/${previousTag}...${tag}` + : `https://github.com/oak-network/crowdsplit/releases/tag/${tag}`; + const commitLine = values.sha + ? `**Crowdsplit commit:** [\`${values.sha.slice(0, 7)}\`](https://github.com/oak-network/crowdsplit/commit/${values.sha})` + : ""; + const endpointSection = [ + addedEndpoints.length > 0 + ? `### New endpoints (need service methods + tests)\n\n${addedEndpoints.map((e) => `- [ ] \`${e}\``).join("\n")}` + : "", + removedEndpoints.length > 0 + ? `### Removed endpoints (deprecate/remove service methods)\n\n${removedEndpoints.map((e) => `- [ ] \`${e}\``).join("\n")}` + : "", + ] + .filter(Boolean) + .join("\n\n"); + const truncatedChangelog = + changelogText.length > MAX_CHANGELOG_IN_BODY + ? `${changelogText.slice(0, MAX_CHANGELOG_IN_BODY)}\n… (truncated; full changelog in the workflow run artifact)` + : changelogText; + const body = [ + `Automated sync of the Crowdsplit OpenAPI contract at **${tag}**.`, + "", + commitLine, + `**Compare:** ${compareLink}`, + "", + breaking + ? "> [!WARNING]\n> This release contains **breaking API changes**. Review the changelog below and escalate the changeset from `patch` if the SDK surface is affected." + : "", + "### What this PR contains (all generated)", + "", + "- `packages/payments/openapi/openapi.bundled.yaml`: spec snapshot", + "- `packages/payments/openapi/.sync-meta.json`: sync metadata", + "- `packages/payments/src/generated/api.ts`: regenerated types (internal, not exported)", + "- changeset (`patch` by default; reviewer escalates if warranted)", + "", + endpointSection, + changelogText + ? `
\nAPI changelog (oasdiff)\n\n\`\`\`\n${truncatedChangelog}\n\`\`\`\n\n
` + : "_Detailed changelog unavailable; see the compare link above._", + "", + "### Reviewer checklist", + "", + "- [ ] Generated diff matches the release notes / compare view", + "- [ ] New endpoints have follow-up issues or service methods planned", + "- [ ] Changeset bump level is appropriate (`patch` default)", + "", + "_Auto-generated by the Crowdsplit API Sync workflow._", + ] + .filter((line) => line !== "") + .join("\n"); + const bodyPath = join(workDir, "pr-body.md"); + writeFileSync(bodyPath, `${body}\n`, "utf8"); + const changelogPath = join(workDir, "api-changelog.txt"); + writeFileSync(changelogPath, `${changelogText}\n`, "utf8"); + + emitOutput("changed", "true"); + emitOutput("breaking", String(breaking)); + emitOutput("prev_tag", previousTag ?? ""); + emitOutput("body_path", bodyPath); + emitOutput("changelog_path", changelogPath); + console.log(`Done. Snapshot, metadata, generated types, and changeset written for ${tag}.`); +} + +main();