diff --git a/.changeset/flat-teams-design.md b/.changeset/flat-teams-design.md deleted file mode 100644 index 9f31810d..00000000 --- a/.changeset/flat-teams-design.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@oaknetwork/api": patch ---- - -chore: enhance httpClient to include Oak-Version in headers#18 diff --git a/.changeset/fruity-clouds-serve.md b/.changeset/fruity-clouds-serve.md deleted file mode 100644 index 62d49605..00000000 --- a/.changeset/fruity-clouds-serve.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@oaknetwork/api': minor ---- - -Add typed environment configuration and @SandboxOnly decorator diff --git a/.changeset/heavy-eggs-sell.md b/.changeset/heavy-eggs-sell.md deleted file mode 100644 index b58541e9..00000000 --- a/.changeset/heavy-eggs-sell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@oaknetwork/api': patch ---- - -Add TSDoc documentation to SDK public API diff --git a/.changeset/hot-lions-bow.md b/.changeset/hot-lions-bow.md deleted file mode 100644 index c552d50f..00000000 --- a/.changeset/hot-lions-bow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@oaknetwork/api': minor ---- - -Add integration tests for WebhookService covering CRUD operations, toggle, and notifications endpoints diff --git a/.changeset/quick-eyes-rest.md b/.changeset/quick-eyes-rest.md deleted file mode 100644 index 47c1aa71..00000000 --- a/.changeset/quick-eyes-rest.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@oaknetwork/api': minor ---- - -Fix httpClient to return ApiError for non-JSON API error responses diff --git a/.changeset/seven-hornets-shout.md b/.changeset/seven-hornets-shout.md deleted file mode 100644 index 2fc0fb2e..00000000 --- a/.changeset/seven-hornets-shout.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@oaknetwork/api": minor ---- - -added sync and balance API diff --git a/.changeset/soft-plums-say.md b/.changeset/soft-plums-say.md deleted file mode 100644 index 6f3a0641..00000000 --- a/.changeset/soft-plums-say.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@oaknetwork/api": major ---- - -Updated types of all request response diff --git a/.changeset/solid-rules-run.md b/.changeset/solid-rules-run.md deleted file mode 100644 index c7add307..00000000 --- a/.changeset/solid-rules-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@oaknetwork/api": major ---- - -updated customer test for US clients, added test for transfer diff --git a/.changeset/some-bottles-sleep.md b/.changeset/some-bottles-sleep.md deleted file mode 100644 index e616cd83..00000000 --- a/.changeset/some-bottles-sleep.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@oaknetwork/api': minor ---- - -Add Payment Method Service Integration Tests diff --git a/.changeset/yellow-snails-divide.md b/.changeset/yellow-snails-divide.md deleted file mode 100644 index 9d1ba669..00000000 --- a/.changeset/yellow-snails-divide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@oaknetwork/api": major ---- - -Refactor httpClient to return Result and centralize error handling (breaking change). diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..5f0889ce --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "npm" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1edd7d42..325ab01c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,37 +9,37 @@ on: jobs: build-and-test: runs-on: ubuntu-latest - + strategy: matrix: node-version: [18.x, 20.x, 24.x] - + steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - + - name: Setup pnpm uses: pnpm/action-setup@v4 with: run_install: false - + - name: Setup Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - cache: 'pnpm' - + cache: "pnpm" + - name: Install dependencies run: pnpm install --frozen-lockfile - name: Calculate next versions from changesets run: pnpm changeset:status - + - name: Build all packages run: pnpm -r --workspace-concurrency=Infinity --filter=!@oaknetwork/contracts build - + - name: Run tests with coverage (enforces 100% threshold) run: pnpm -r --workspace-concurrency=Infinity --filter=!@oaknetwork/contracts test --coverage env: @@ -47,7 +47,7 @@ jobs: CLIENT_ID: ${{ secrets.CLIENT_ID }} CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} OAK_ENVIRONMENT: sandbox - + - name: Upload coverage reports uses: actions/upload-artifact@v4 if: always() @@ -56,6 +56,6 @@ jobs: path: | packages/*/coverage/ retention-days: 30 - + - name: Run lint run: pnpm -r --workspace-concurrency=Infinity --filter=!@oaknetwork/contracts lint diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 32161fbb..76091b9b 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -4,7 +4,7 @@ on: workflow_run: workflows: [CI] types: [completed] - branches: [main, develop] + branches: [main] concurrency: group: codecov-${{ github.event.workflow_run.id }} @@ -19,6 +19,11 @@ jobs: actions: read steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} + - name: Download coverage artifact uses: actions/download-artifact@v4 with: @@ -27,9 +32,12 @@ jobs: github-token: ${{ github.token }} - name: Upload to Codecov - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de with: token: ${{ secrets.CODECOV_TOKEN }} - files: packages/api/coverage/lcov.info + files: api/coverage/lcov.info + override_commit: ${{ github.event.workflow_run.head_sha }} + override_branch: ${{ github.event.workflow_run.head_branch }} + override_build: ${{ github.event.workflow_run.run_number }} + override_build_url: ${{ github.event.workflow_run.html_url }} fail_ci_if_error: false - continue-on-error: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ce7b472c..a3bda145 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,7 +52,7 @@ jobs: - name: Update npm for OIDC support if: steps.changesets.outputs.hasChangesets == 'false' run: | - npm install -g npm@10.9.2 + npm install -g npm@11.11.0 npm --version - name: Publish packages diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f27005d4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Oak Network + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 7871cfc0..faf2c45d 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,36 @@ -# Oak SDK Monorepo +# Oak Network SDK -> **Status**: Pre-launch development (Expected launch: March 2026) +[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/oak-network/sdk/badge)](https://scorecard.dev/viewer/?uri=github.com/oak-network/sdk) [![Codecov](https://codecov.io/github/oak-network/sdk/graph/badge.svg)](https://app.codecov.io/github/oak-network/sdk) [![CodeQL](https://github.com/oak-network/sdk/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/oak-network/sdk/actions/workflows/github-code-scanning/codeql) -[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/oak-network/sdk/badge)](https://scorecard.dev/viewer/?uri=github.com/oak-network/sdk) [![Codecov](https://codecov.io/github/oak-network/sdk/graph/badge.svg)](https://app.codecov.io/github/oak-network/sdk) [![CodeQL](https://img.shields.io/github/actions/workflow/status/oak-network/sdk/codeql.yml?label=CodeQL&logo=github)](https://github.com/oak-network/sdk/actions/workflows/codeql.yml) +TypeScript SDK for the [Oak Network](https://www.oaknetwork.org/) payment API. Build secure payment applications with type-safe interfaces, a `Result` error handling pattern, automatic OAuth 2.0 authentication, and built-in retries. -TypeScript SDK for the Oak Network Crowdsplit API. Build secure payment applications with type-safe interfaces, comprehensive error handling, and OAuth 2.0 authentication. +> **Full documentation** — [oaknetwork.org/docs/sdk/api-sdk/overview](https://www.oaknetwork.org/docs/sdk/api-sdk/overview) --- -## 📦 Packages +## Getting credentials -- **[@oaknetwork/api](./packages/api)** - Core SDK for Crowdsplit API -- **@oaknetwork/contracts** - Smart contracts (placeholder, not in active development) +To use the SDK you need a **Client ID** and **Client Secret**. Contact **[support@oaknetwork.org](mailto:support@oaknetwork.org)** to get your sandbox credentials. + +Create a `.env` file in your project root: + +```bash +CLIENT_ID=your-client-id +CLIENT_SECRET=your-client-secret +``` + +Install `dotenv` to load these automatically: `pnpm add dotenv`, then add `import 'dotenv/config'` at the top of your entry file. + +| Variable | Required | Description | +|---|---|---| +| `CLIENT_ID` | Yes | Your merchant client ID | +| `CLIENT_SECRET` | Yes | Your merchant client secret | + +> Use different credentials for sandbox and production. Never commit `.env` files or log secrets. --- -## 🚀 Quick Start +## Quick start ### Installation @@ -27,365 +42,363 @@ npm install @oaknetwork/api yarn add @oaknetwork/api ``` -### Basic Usage +**Requirements:** Node.js 18+, TypeScript 5.x recommended. + +### Basic usage ```typescript -import { createOakClient } from "@oaknetwork/api"; -import { Crowdsplit } from "@oaknetwork/api/products/crowdsplit"; +import 'dotenv/config'; +import { createOakClient, createCustomerService } from '@oaknetwork/api'; -// Create client const client = createOakClient({ - environment: "sandbox", // or "production" - clientId: process.env.OAK_CLIENT_ID, - clientSecret: process.env.OAK_CLIENT_SECRET, + environment: 'sandbox', + clientId: process.env.CLIENT_ID!, + clientSecret: process.env.CLIENT_SECRET!, }); -// Initialize Crowdsplit product -const crowdsplit = Crowdsplit(client); +const customers = createCustomerService(client); -// Create a customer -const customerResult = await crowdsplit.customers.create({ - email: "user@example.com", - first_name: "John", - last_name: "Doe", -}); +const result = await customers.list(); -if (customerResult.ok) { - console.log("Customer created:", customerResult.value.data.customer_id); +if (result.ok) { + console.log(result.value.data); } else { - console.error("Error:", customerResult.error.message); + console.error(result.error.message); } ``` +> See the full [Quickstart guide](https://www.oaknetwork.org/docs/sdk/api-sdk/quickstart) for a step-by-step walkthrough. + --- -## 🔐 Authentication +## Services + +The SDK ships 10 service modules. Import the factory function for each service you need. + +| Service | Factory | What it does | +|---|---|---| +| [Customers](https://www.oaknetwork.org/docs/sdk/api-sdk/customers) | `createCustomerService(client)` | Create, get, list, update, sync, and check balances | +| [Payments](https://www.oaknetwork.org/docs/sdk/api-sdk/payments) | `createPaymentService(client)` | Create, confirm, cancel payments | +| [Payment Methods](https://www.oaknetwork.org/docs/sdk/api-sdk/payment-methods) | `createPaymentMethodService(client)` | Add, list, get, delete payment methods | +| [Webhooks](https://www.oaknetwork.org/docs/sdk/api-sdk/webhooks) | `createWebhookService(client)` | Register, manage, and monitor webhooks | +| [Transactions](https://www.oaknetwork.org/docs/sdk/api-sdk/transactions) | `createTransactionService(client)` | List, get, and settle transactions | +| [Transfers](https://www.oaknetwork.org/docs/sdk/api-sdk/transfers) | `createTransferService(client)` | Create provider transfers (Stripe, PagarMe, BRLA) | +| [Plans](https://www.oaknetwork.org/docs/sdk/api-sdk/plans) | `createPlanService(client)` | CRUD subscription plans | +| [Refunds](https://www.oaknetwork.org/docs/sdk/api-sdk/refunds) | `createRefundService(client)` | Refund a payment (full or partial) | +| [Buy](https://www.oaknetwork.org/docs/sdk/api-sdk/buy-and-sell) | `createBuyService(client)` | Crypto on-ramp via Bridge | +| [Sell](https://www.oaknetwork.org/docs/sdk/api-sdk/buy-and-sell) | `createSellService(client)` | Crypto off-ramp via Avenia | + +--- -The SDK uses OAuth 2.0 client credentials flow with automatic token management. +## Usage examples + +### Customers ```typescript -// Tokens are automatically fetched and cached -const result = await crowdsplit.customers.list(); +import { createCustomerService } from '@oaknetwork/api'; -// Manual token operations (rarely needed) -const tokenResult = await client.getAccessToken(); -if (tokenResult.ok) { - console.log("Token:", tokenResult.value); -} -``` +const customers = createCustomerService(client); -**Security Best Practices:** +// Create +const result = await customers.create({ + email: 'user@example.com', + first_name: 'John', + last_name: 'Doe', + country_code: 'US', +}); -- ✅ Store credentials in environment variables -- ✅ Never commit `.env` files -- ✅ Use different credentials for sandbox and production -- ❌ Never log `clientSecret` or access tokens +// List +await customers.list({ limit: 10, offset: 0 }); ---- +// Get +await customers.get('customer_id'); -## 📡 Available Services +// Update +await customers.update('customer_id', { email: 'new@example.com' }); +``` -### Customers +### Providers ```typescript -// Create customer -await crowdsplit.customers.create({ - email: "user@example.com", - first_name: "John", - document_type: "personal_tax_id", - document_number: "123456789", -}); +import { createProviderService } from '@oaknetwork/api'; -// Get customer -await crowdsplit.customers.get("customer_id"); +const providers = createProviderService(client); -// List customers -await crowdsplit.customers.list({ limit: 10, offset: 0 }); +// Register as Stripe customer (buyer) +await providers.submitRegistration(customerId, { + provider: 'stripe', + target_role: 'customer', +}); -// Update customer -await crowdsplit.customers.update("customer_id", { - email: "newemail@example.com", +// Register as Stripe connected account (seller) +await providers.submitRegistration(customerId, { + provider: 'stripe', + target_role: 'connected_account', + provider_data: { + account_type: 'express', + transfers_requested: true, + card_payments_requested: true, + tax_reporting_us_1099_k_requested: false, + payouts_debit_negative_balances: false, + external_account_collection_requested: false, + }, }); + +// Check registration status +const status = await providers.getRegistrationStatus(customerId); ``` ### Payments ```typescript -// Create payment -await crowdsplit.payments.create({ - provider: "stripe", +import { createPaymentService } from '@oaknetwork/api'; + +const payments = createPaymentService(client); + +// Create and capture a payment +const result = await payments.create({ + provider: 'stripe', source: { - amount: 1000, // Amount in cents - currency: "usd", - customer: { id: "customer_id" }, - payment_method: { type: "card", id: "pm_123" }, - capture_method: "automatic", + amount: 5000, + currency: 'usd', + customer: { id: customerId }, + payment_method: { type: 'card', id: paymentMethodId }, + capture_method: 'automatic', }, confirm: true, }); -// Confirm payment -await crowdsplit.payments.confirm("payment_id"); - -// Cancel payment -await crowdsplit.payments.cancel("payment_id"); +// Confirm / cancel +await payments.confirm('payment_id'); +await payments.cancel('payment_id'); ``` -### Payment Methods +### Payment methods ```typescript -// Create payment method -await crowdsplit.paymentMethods.create("customer_id", { - type: "card", - provider: "stripe", - provider_data: { - token: "tok_visa", - }, -}); - -// List customer payment methods -await crowdsplit.paymentMethods.list("customer_id"); +import { createPaymentMethodService } from '@oaknetwork/api'; -// Delete payment method -await crowdsplit.paymentMethods.delete("customer_id", "pm_id"); -``` +const paymentMethods = createPaymentMethodService(client); -### Refunds +// Add a card +await paymentMethods.add(customerId, { + type: 'card', + provider: 'stripe', +}); -```typescript -// Create refund -await crowdsplit.refunds.create({ - transaction_id: "txn_123", - amount: 500, // Partial refund - reason: "customer_request", +// Add a bank account +await paymentMethods.add(customerId, { + type: 'bank', + provider: 'stripe', + currency: 'usd', + bank_name: 'Chase', + bank_account_number: '000123456789', + bank_routing_number: '021000021', + bank_account_type: 'checking', + bank_account_name: 'John Doe', }); + +// List / get / delete +await paymentMethods.list(customerId); +await paymentMethods.get(customerId, 'pm_id'); +await paymentMethods.delete(customerId, 'pm_id'); ``` ### Transfers ```typescript -// Create transfer -await crowdsplit.transfers.create({ - provider: "stripe", +import { createTransferService } from '@oaknetwork/api'; + +const transfers = createTransferService(client); + +await transfers.create({ + provider: 'stripe', source: { amount: 1000, - currency: "usd", - customer: { id: "customer_id" }, + currency: 'usd', + customer: { id: customerId }, }, destination: { - customer: { id: "customer_id" }, - payment_method: { id: "pm_123", type: "bank" }, + customer: { id: customerId }, + payment_method: { type: 'bank', id: bankPmId }, }, }); ``` -### Webhooks +### Refunds ```typescript -// Register webhook -await crowdsplit.webhooks.register({ - url: "https://your-app.com/webhooks/oak", - events: ["payment.created", "payment.succeeded"], -}); +import { createRefundService } from '@oaknetwork/api'; -// List webhooks -await crowdsplit.webhooks.list(); +const refunds = createRefundService(client); -// Update webhook -await crowdsplit.webhooks.update("webhook_id", { - url: "https://your-app.com/webhooks/oak-v2", -}); +// Full refund +await refunds.create(paymentId, {}); -// Toggle webhook status -await crowdsplit.webhooks.toggleStatus("webhook_id", "inactive"); - -// Delete webhook -await crowdsplit.webhooks.delete("webhook_id"); -``` - -### Providers - -```typescript -// List available providers -await crowdsplit.providers.list(); - -// Get provider details -await crowdsplit.providers.get("stripe"); +// Partial refund +await refunds.create(paymentId, { amount: 500 }); ``` ### Plans ```typescript -// List plans -await crowdsplit.plans.list(); +import { createPlanService } from '@oaknetwork/api'; + +const plans = createPlanService(client); + +await plans.create({ + name: 'Pro Plan', + description: 'Monthly pro subscription', + price: 2999, + currency: 'USD', + frequency: 30, + start_date: '2026-03-01', + is_auto_renewable: true, + allow_amount_override: false, + created_by: customerId, +}); -// Get plan details -await crowdsplit.plans.get("plan_id"); +await plans.list(); +await plans.details('plan_id'); ``` ### Transactions ```typescript -// List transactions -await crowdsplit.transactions.list({ - limit: 20, - offset: 0, -}); +import { createTransactionService } from '@oaknetwork/api'; -// Get transaction details -await crowdsplit.transactions.get("txn_id"); +const transactions = createTransactionService(client); + +await transactions.list({ limit: 20 }); +await transactions.get('txn_id'); ``` --- -## 🔔 Webhook Verification - -**New in v0.2.0**: Secure webhook signature verification using HMAC-SHA256 with timing-safe comparison. +## Webhooks -### Express.js Example +Register endpoints to receive real-time event notifications, and verify incoming payloads with HMAC-SHA256 signature verification. ```typescript -import express from "express"; -import { verifyWebhookSignature, parseWebhookPayload } from "@oaknetwork/api"; - -const app = express(); -app.use(express.json()); - -app.post("/webhooks/oak", async (req, res) => { - const signature = req.headers["x-oak-signature"] as string; - const payload = JSON.stringify(req.body); - - // Option 1: Verify signature only - const isValid = verifyWebhookSignature( - payload, - signature, - process.env.WEBHOOK_SECRET!, - ); - - if (!isValid) { - return res.status(401).send("Invalid signature"); - } - - const event = req.body; - console.log("Webhook event:", event.type); - - // Option 2: Verify and parse in one step (preferred) - const result = parseWebhookPayload<{ - type: string; - data: unknown; - }>(payload, signature, process.env.WEBHOOK_SECRET!); - - if (!result.ok) { - console.error("Webhook verification failed:", result.error.message); - return res.status(401).send(result.error.message); - } +import { + createWebhookService, + verifyWebhookSignature, + parseWebhookPayload, +} from '@oaknetwork/api'; + +const webhooks = createWebhookService(client); + +// Register +const wh = await webhooks.register({ + url: 'https://your-server.com/webhooks/oak', + description: 'Payment events', +}); - // Handle verified event - const verifiedEvent = result.value; - switch (verifiedEvent.type) { - case "payment.created": - // Handle payment created - break; - case "payment.succeeded": - // Handle payment succeeded - break; - default: - console.log("Unhandled event:", verifiedEvent.type); - } +if (wh.ok) { + console.log('Secret:', wh.value.data.secret); // store securely +} - res.sendStatus(200); -}); +// List / update / delete +await webhooks.list(); +await webhooks.update('webhook_id', { url: 'https://new-url.com/webhooks' }); +await webhooks.toggle('webhook_id'); +await webhooks.delete('webhook_id'); ``` -### Next.js API Route Example +### Verify and parse incoming events ```typescript -import type { NextApiRequest, NextApiResponse } from "next"; -import { parseWebhookPayload } from "@oaknetwork/api"; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { - if (req.method !== "POST") { - return res.status(405).end(); - } +import { parseWebhookPayload } from '@oaknetwork/api'; +import express from 'express'; - const signature = req.headers["x-oak-signature"] as string; - const payload = JSON.stringify(req.body); +const app = express(); +app.post('/webhooks/oak', express.raw({ type: 'application/json' }), (req, res) => { const result = parseWebhookPayload( - payload, - signature, + req.body.toString(), + req.headers['x-oak-signature'] as string, process.env.WEBHOOK_SECRET!, ); if (!result.ok) { - return res.status(401).json({ error: result.error.message }); + return res.status(401).json({ error: 'Invalid signature' }); } - // Process verified webhook - const event = result.value; - console.log("Received:", event); + switch (result.value.event) { + case 'payment.succeeded': + break; + case 'payment.failed': + break; + case 'provider_registration.approved': + break; + } - res.status(200).json({ received: true }); -} + res.json({ received: true }); +}); ``` -**Security Notes:** - -- Always verify signatures before processing webhooks -- Use timing-safe comparison (built into SDK) -- Store webhook secret securely (environment variables) -- Never expose webhook endpoints without verification +> Full webhook reference — [oaknetwork.org/docs/sdk/api-sdk/webhooks](https://www.oaknetwork.org/docs/sdk/api-sdk/webhooks) --- -## 🎯 Error Handling +## Error handling -The SDK uses a `Result` type pattern for predictable error handling: +Every method returns `Result` — no uncaught exceptions. Check `result.ok` to branch on success or failure. ```typescript -const result = await crowdsplit.customers.create(customerData); +const result = await customers.create({ email: 'user@example.com', first_name: 'John' }); if (result.ok) { - // Success - result.value contains the response const customer = result.value.data; - console.log("Created:", customer.customer_id); + console.log('Created:', customer.id); } else { - // Error - result.error contains the OakError - console.error("Failed:", result.error.message); - console.error("Status:", result.error.statusCode); - console.error("Code:", result.error.code); + console.error('Failed:', result.error.message); + console.error('Status:', result.error.statusCode); + console.error('Code:', result.error.code); } ``` -### Error Types +| Error type | Description | +|---|---| +| `ApiError` | HTTP errors from the API (4xx, 5xx) | +| `NetworkError` | Network failures, timeouts | +| `ParseError` | Invalid JSON responses | +| `AbortError` | Request aborted | +| `EnvironmentViolationError` | Sandbox-only method called in production | -- `ApiError` - HTTP errors from the API (4xx, 5xx) -- `NetworkError` - Network failures, timeouts -- `ParseError` - Invalid JSON responses -- `AbortError` - Request aborted -- `OakError` - Base error class +> Full error handling guide — [oaknetwork.org/docs/sdk/api-sdk/error-handling](https://www.oaknetwork.org/docs/sdk/api-sdk/error-handling) --- -## ⚙️ Configuration +## Configuration -### Environment Options +### Environments + +| Environment | API Base URL | Description | +|---|---|---| +| `sandbox` | `https://api-stage.usecrowdpay.xyz` | Testing — all operations allowed | +| `production` | `https://app.usecrowdpay.xyz` | Live — test operations blocked | ```typescript -type OakEnvironment = "sandbox" | "production" | "custom"; +const client = createOakClient({ + environment: 'sandbox', + clientId: process.env.CLIENT_ID!, + clientSecret: process.env.CLIENT_SECRET!, + + // Optional: point to a custom API server + customUrl: 'https://my-dev-server.example.com', +}); +``` -createOakClient({ - environment: "sandbox", // Use sandbox for testing - clientId: "your_client_id", - clientSecret: "your_client_secret", +### Retry configuration - // Optional: Custom URL for development - customUrl: "http://localhost:3000", +The SDK automatically retries failed requests with exponential backoff and jitter. - // Optional: Retry configuration +```typescript +const client = createOakClient({ + environment: 'sandbox', + clientId: process.env.CLIENT_ID!, + clientSecret: process.env.CLIENT_SECRET!, retryOptions: { maxNumberOfRetries: 3, delay: 1000, @@ -395,233 +408,95 @@ createOakClient({ }); ``` -### Retry Configuration - -The SDK automatically retries failed requests with exponential backoff: - -- **Retry on**: 408, 429, 500, 502, 503, 504 -- **Max retries**: 3 (configurable) -- **Backoff**: Exponential with jitter to prevent thundering herd - -```typescript -retryOptions: { - maxNumberOfRetries: 3, // Number of retry attempts - delay: 1000, // Initial delay in ms - backoffFactor: 2, // Multiplier for each retry - maxDelay: 30000, // Maximum delay cap - retryOnStatus: [408, 429, 500, 502, 503, 504], - retryOnError: (error) => error.isNetworkError, -} -``` - ---- - -## 📝 TypeScript Support - -The SDK is written in TypeScript with full type definitions: - -```typescript -import type { - Customer, - Payment, - PaymentMethod, - Transaction, - Transfer, - Result, -} from "@oaknetwork/api"; - -// Type-safe customer creation -const customerData: Customer.Request = { - email: "user@example.com", - first_name: "John", -}; - -// Type-safe result handling -const result: Result = await crowdsplit.customers.create( - customerData, -); - -if (result.ok) { - const customer: Customer.Data = result.value.data; -} -``` +Retried status codes: `408`, `429`, `500`, `502`, `503`, `504`. --- -## 🔄 Migration Guide (v0.1 → v0.2) - -### Breaking Changes - -#### 1. `clientSecret` No Longer Exposed - -**Before (v0.1):** - -```typescript -console.log(client.config.clientSecret); // ✅ Works in v0.1 -``` - -**After (v0.2):** - -```typescript -console.log(client.config.clientSecret); // ❌ undefined in v0.2 -// Store separately if needed: -const secret = process.env.CLIENT_SECRET; -``` - -#### 2. `createAuthService()` Removed +## TypeScript support -**Before (v0.1):** +The SDK ships full type declarations. All service methods, request payloads, and responses are typed. ```typescript -import { createAuthService } from "@oaknetwork/api"; -const auth = createAuthService(client); -await auth.getAccessToken(); +import type { Result } from '@oaknetwork/api'; ``` -**After (v0.2):** - -```typescript -// Use client directly -await client.getAccessToken(); -``` - -See [CHANGELOG.md](./CHANGELOG.md) for full migration guide. - --- -## 🛠️ Development +## Development -### Package Manager +### Package manager This project uses **pnpm** exclusively: ```bash pnpm install # Install dependencies -pnpm build # Build all packages -pnpm test # Run tests -pnpm lint # Lint code +pnpm build # Build all packages +pnpm test # Run tests +pnpm lint # Lint code ``` -**DO NOT** use npm or yarn. The repository enforces pnpm >= 10.0.0. - -### Changesets Workflow - -We use Changesets to manage versions and changelogs: - -1. **After making changes**, run: - - ```bash - pnpm changeset - ``` - -2. **Select impact** (Major/Minor/Patch) for affected packages - -3. **Commit** the generated file in `.changeset/` +**Do not** use npm or yarn. The repository enforces pnpm >= 10.0.0. -4. **CI automatically**: - - Calculates next versions - - Generates changelogs - - Creates release PR - -### Running Tests +### Running tests ```bash -# Unit tests -pnpm test:unit - -# Integration tests (requires credentials) -pnpm test:integration - -# All tests with coverage -pnpm test:all - -# Watch mode -pnpm test:watch +pnpm test:unit # Unit tests +pnpm test:integration # Integration tests (requires credentials) +pnpm test:all # All tests with coverage +pnpm test:watch # Watch mode ``` -### Environment Variables for Testing +### Changesets workflow -Create `.env` file in `packages/api`: +We use Changesets to manage versions and changelogs: -```env -CLIENT_ID=your_sandbox_client_id -CLIENT_SECRET=your_sandbox_client_secret -OAK_ENVIRONMENT=sandbox -``` +1. After making changes, run `pnpm changeset` +2. Select impact (Major / Minor / Patch) for affected packages +3. Commit the generated file in `.changeset/` +4. CI automatically calculates versions, generates changelogs, and creates a release PR ### Code coverage -Coverage is reported to [Codecov](https://about.codecov.io) after each successful CI run. The Codecov workflow runs separately with minimal permissions and uploads coverage from the API package. For uploads on pushes and pull requests from this repository, a `CODECOV_TOKEN` secret (from your organization or Codecov dashboard) may be required; when the token is not set, the upload step is skipped and the workflow still succeeds. - ---- - -## 📖 Documentation +Coverage is reported to [Codecov](https://about.codecov.io) after each successful CI run. -- **API Reference**: See [packages/api/README.md](./packages/api/README.md) -- **Type Definitions**: Included with package, supports IDE autocomplete -- **Examples**: See [examples/](./examples/) directory (coming soon) -- **Changelog**: See [CHANGELOG.md](./CHANGELOG.md) +### Development guidelines ---- - -### Development Guidelines - -See [CLAUDE.md](./CLAUDE.md) for comprehensive coding standards including: - -- Architecture principles (Result types, factory pattern) -- Security rules (never expose secrets, timing-safe comparisons) -- Testing requirements (no silent skips, >90% coverage) -- Type system rules (use `unknown`, named interfaces) -- Anti-patterns to avoid - -### Code Review Checklist +See [CLAUDE.md](./CLAUDE.md) for coding standards including architecture principles, security rules, testing requirements, and anti-patterns. -Before submitting PR: +### Code review checklist -- [ ] Run `pnpm build` successfully -- [ ] Run `pnpm test` with >90% coverage -- [ ] Run `pnpm lint` without errors -- [ ] Create changeset with `pnpm changeset` -- [ ] Update documentation if needed -- [ ] Follow patterns in [CLAUDE.md](./CLAUDE.md) +- [ ] `pnpm build` succeeds +- [ ] `pnpm test` passes with >90% coverage +- [ ] `pnpm lint` has no errors +- [ ] Changeset created with `pnpm changeset` +- [ ] Documentation updated if needed --- -## 📄 License - -MIT +## Documentation +- **Full docs** — [oaknetwork.org/docs/sdk/api-sdk/overview](https://www.oaknetwork.org/docs/sdk/api-sdk/overview) +- **Quickstart** — [oaknetwork.org/docs/sdk/api-sdk/quickstart](https://www.oaknetwork.org/docs/sdk/api-sdk/quickstart) +- **API package README** — [packages/api/README.md](./packages/api/README.md) +- **Changelog** — [CHANGELOG.md](./CHANGELOG.md) --- -## 🔗 Links - -- [Oak Network Website](https://oaknetwork.org) -- [API Documentation](https://www.oaknetwork.org/docs/intro) -- [GitHub Repository](https://github.com/oak-network/sdk) -- [Issue Tracker](https://github.com/oak-network/sdk/issues) -- [npm Package](https://www.npmjs.com/package/@oaknetwork/api) - ---- +## License -## 🎯 Roadmap +[MIT](LICENSE) -**Pre-Launch (Current → March 2026)** +## Security -- ✅ Core API services implemented -- ✅ Comprehensive type safety -- ✅ Webhook verification utilities -- ✅ Full test coverage -- ⏳ Production hardening -- ⏳ Performance optimization -- ⏳ Example applications +[Security Policy](SECURITY.md) -**Post-Launch** +## Links -- Advanced retry strategies -- Request/response middleware -- CLI tools -- TBD (Being evaluated based on user feedback) +- [Oak Network](https://www.oaknetwork.org/) +- [Documentation](https://www.oaknetwork.org/docs/sdk/api-sdk/overview) +- [GitHub](https://github.com/oak-network/sdk) +- [Issues](https://github.com/oak-network/sdk/issues) +- [npm](https://www.npmjs.com/package/@oaknetwork/api) --- -**Questions?** Open an issue or contact support@oaknetwork.org +**Questions?** Open an issue or contact [support@oaknetwork.org](mailto:support@oaknetwork.org) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..afe95113 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,92 @@ +# Security Policy + +## Supported Versions + +We actively maintain and provide security updates for the following versions of `@oaknetwork/api`: + +| Version | Supported | +| ------- | ------------------ | +| 1.x | ✅ Active support | + +## Reporting a Vulnerability + +**Please do not report security vulnerabilities through public GitHub issues.** + +If you discover a vulnerability in the Oak SDK, we ask that you follow responsible disclosure practices and report it privately so we can address it before any public disclosure. + +### How to Report + +- **Email**: [security@oaknetwork.org](mailto:security@oaknetwork.org) +- **General support**: [support@oaknetwork.org](mailto:support@oaknetwork.org) +- **GitHub Private Advisories**: [Report a vulnerability](https://github.com/oak-network/sdk/security/advisories/new) + +When reporting, please include as much detail as possible: + +- A clear description of the vulnerability +- Steps to reproduce the issue +- The potential impact and attack surface +- Any proof-of-concept code (if available) +- The SDK version(s) affected + +### What to Expect + +| Timeline | Action | +| -------- | ------ | +| **Within 48 hours** | Acknowledgment of your report | +| **Within 7 days** | Initial severity assessment and triage | +| **Within 30 days** | A patch or mitigation plan for confirmed vulnerabilities | +| **Within 90 days** | Public disclosure (coordinated with reporter) | + +We follow **coordinated vulnerability disclosure (CVD)** principles. We will keep you informed throughout the process and aim to resolve confirmed vulnerabilities within 30 days. For complex issues, we may request an extension and will communicate transparently about the timeline. + +## Scope + +The following are **in scope** for vulnerability reports: + +- Authentication and token management flaws (`OAuth 2.0` client credentials flow) +- Webhook signature verification bypass or timing-attack vulnerabilities +- Credential or secret exposure through SDK APIs +- Dependency vulnerabilities with direct security impact on SDK consumers +- Type-safety issues that could lead to injection or data leakage + +The following are **out of scope**: + +- Vulnerabilities in the Oak Network backend API itself (please contact Oak Network directly) +- Issues in third-party dependencies without a clear exploit path through this SDK +- Denial-of-service attacks that require significant resources or special access +- Social engineering attacks + +## Security Best Practices for SDK Consumers + +To use the Oak SDK securely in your applications: + +- **Never hardcode credentials** — store `clientId` and `clientSecret` in environment variables +- **Never commit `.env` files** — add them to `.gitignore` +- **Always verify webhook signatures** — use `verifyWebhookSignature()` or `parseWebhookPayload()` before processing any webhook event +- **Use separate credentials** for sandbox and production environments +- **Do not log or expose** `clientSecret` or access tokens in application logs +- **Keep the SDK updated** — subscribe to [GitHub releases](https://github.com/oak-network/sdk/releases) to stay informed about security patches + +## Security Features in This SDK + +- **OAuth 2.0 client credentials flow** with automatic token caching +- **`clientSecret` is not exposed** on the client config object (as of v0.2) +- **HMAC-SHA256 webhook signature verification** with timing-safe comparison to prevent timing attacks +- **Exponential backoff with jitter** on retries to mitigate thundering herd scenarios +- **Strict TypeScript types** with `unknown` for unvalidated external data + +## Disclosure Policy + +We are committed to working with security researchers and the community to ensure the safety of our SDK. Once a vulnerability is confirmed and a fix is available, we will: + +1. Release a patched version as soon as possible +2. Publish a [GitHub Security Advisory](https://github.com/oak-network/sdk/security/advisories) +3. Update the [CHANGELOG.md](./CHANGELOG.md) with details about the fix +4. Credit the reporter (unless they prefer to remain anonymous) + +We ask that reporters allow us up to **90 days** from initial report to public disclosure to give users time to update. + +## Contact + +For security-related inquiries, reach out to [security@oaknetwork.org](mailto:security@oaknetwork.org). +For general questions, visit [oaknetwork.org](https://oaknetwork.org) or open a [GitHub Discussion](https://github.com/oak-network/sdk/issues). diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..3e2c3a61 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,5 @@ +codecov: + branch: main + +fixes: + - "src/::packages/api/src/" diff --git a/packages/api/CHANGELOG.md b/packages/api/CHANGELOG.md index 95994936..0b1cd15f 100644 --- a/packages/api/CHANGELOG.md +++ b/packages/api/CHANGELOG.md @@ -1,20 +1,18 @@ # @oaknetwork/api -## 1.1.0 +## 1.0.1 -### Minor Changes +### Patch Changes -### Minor Changes +### Patch Changes -- testing changeset for releases +- Initial release of SDK v1.0.1. ## 1.0.0 ### Major Changes -### Major Changes - -- Adopt Result responses across API services and auth flows. +- Initial release of Oak SDK ## 0.1.0 diff --git a/packages/api/package.json b/packages/api/package.json index 9e726bc0..9f86af04 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@oaknetwork/api", - "version": "0.1.0", + "version": "1.0.1", "description": "TypeScript SDK for Crowdsplit API", "publishConfig": { "access": "public"