diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 325ab01c..4d835738 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: run: pnpm changeset:status - name: Build all packages - run: pnpm -r --workspace-concurrency=Infinity --filter=!@oaknetwork/contracts build + run: pnpm -r --workspace-concurrency=Infinity build - name: Run tests with coverage (enforces 100% threshold) run: pnpm -r --workspace-concurrency=Infinity --filter=!@oaknetwork/contracts test --coverage diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a3bda145..49a1f121 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,7 +47,7 @@ jobs: - name: Build packages if: steps.changesets.outputs.hasChangesets == 'false' - run: pnpm --filter=!@oaknetwork/contracts build + run: pnpm build - name: Update npm for OIDC support if: steps.changesets.outputs.hasChangesets == 'false' diff --git a/.vscode/settings.json b/.vscode/settings.json index 6722d814..d2145582 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,9 +17,9 @@ "jest.runMode": "on-demand", "jest.virtualFolders": [ { - "name": "api", - "rootPath": "packages/api", - "jestCommandLine": "node packages/api/scripts/jest-run-exact.js --", + "name": "payments", + "rootPath": "packages/payments", + "jestCommandLine": "node packages/payments/scripts/jest-run-exact.js --", "pathToConfig": "jest.config.js" }, { diff --git a/CHANGELOG.md b/CHANGELOG.md index 52cda1a4..655ca791 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **Webhook Verification Utilities**: New `verifyWebhookSignature()` and `parseWebhookPayload()` functions for secure webhook handling using HMAC-SHA256 with timing-safe comparison -- **RefundService**: Added to Crowdsplit product facade, exposing refund functionality that was previously available but not exposed +- **RefundService**: Exposed refund functionality that was previously available but not exported - **Helper Utilities**: - `withAuth()`: Higher-order function for wrapping HTTP operations with authentication (eliminates 35+ duplications) - `buildUrl()`: Centralized URL construction with consistent trailing slash handling (standardizes 36+ URL constructions) @@ -29,7 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **BREAKING**: Removed `createAuthService()` wrapper - use `client.getAccessToken()` and `client.grantToken()` directly - **Type System Improvements**: - Replaced `any` with `unknown` in httpClient methods (`post`, `put`, `patch`) and retryHandler for better type safety - - Converted `ReturnType` to direct interface imports in Crowdsplit facade + - Converted `ReturnType` to direct interface imports in service types - Converted intersection types to standalone interfaces in Payment and Transfer types - **Dependency Updates**: - Moved `nock` and `dotenv` from dependencies to devDependencies (reduces production bundle size) @@ -97,7 +97,7 @@ const client = createOakClient({ **Before:** ```typescript -import { createAuthService } from "@oaknetwork/api"; +import { createAuthService } from "@oaknetwork/payments-sdk"; const auth = createAuthService(client); const token = await auth.getAccessToken(); @@ -136,7 +136,10 @@ httpClient.post(url, requestData as RequestType, config); #### Webhook Verification ```typescript -import { verifyWebhookSignature, parseWebhookPayload } from "@oaknetwork/api"; +import { + verifyWebhookSignature, + parseWebhookPayload, +} from "@oaknetwork/payments-sdk"; // Option 1: Verify signature only app.post("/webhook", (req, res) => { @@ -173,13 +176,15 @@ app.post("/webhook", (req, res) => { #### RefundService Now Available ```typescript -import { Crowdsplit } from "@oaknetwork/api/products/crowdsplit"; +import { createOakClient, createRefundService } from "@oaknetwork/payments-sdk"; -const crowdsplit = Crowdsplit(client); +const client = createOakClient({ + /* config */ +}); +const refunds = createRefundService(client); -// Refund service is now exposed -const result = await crowdsplit.refunds.create({ - transaction_id: "txn_123", +// Create a refund +const result = await refunds.create("pay_123", { amount: 1000, }); ``` @@ -189,23 +194,20 @@ const result = await crowdsplit.refunds.create({ 1. **Update Package**: ```bash - pnpm update @oaknetwork/api@latest + pnpm update @oaknetwork/payments-sdk@latest ``` 2. **Remove `clientSecret` Access**: - - Search codebase for `client.config.clientSecret` - Store separately if needed for non-SDK purposes - Update to use environment variables 3. **Replace `createAuthService()`**: - - Search for `createAuthService` - Replace with direct `client.getAccessToken()` or `client.grantToken()` calls - Remove import 4. **Add Type Assertions** (if needed): - - TypeScript may require type assertions for HTTP client methods - Add `as RequestType` where compiler indicates `unknown` cannot be assigned @@ -219,7 +221,7 @@ const result = await crowdsplit.refunds.create({ ### Added - Initial release of Oak SDK -- Support for Crowdsplit API +- Support for Oak Network API - Customer, Payment, PaymentMethod, Transaction services - Transfer, Webhook, Plan, Buy, Sell services - OAuth 2.0 client credentials flow diff --git a/CLAUDE.md b/CLAUDE.md index c91d57d3..d9bc19b2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -143,13 +143,13 @@ function processData(data: any): any { ```typescript // ✅ CORRECT - export interface CrowdsplitProduct { + export interface OakServices { customers: CustomerService; payments: PaymentService; } // ❌ WRONG - export interface CrowdsplitProduct { + export interface OakServices { customers: ReturnType; } ``` @@ -230,7 +230,6 @@ create(customer: Customer.Request): Promise>; ``` 4. **NEVER log sensitive data**: - - No passwords, tokens, or API keys in logs - Sanitize error messages before logging - Use structured logging with sensitive field filtering @@ -632,7 +631,7 @@ When introducing breaking changes: 1. Remove references to `client.config.clientSecret` 2. Store secret in environment variables -3. Update to latest `@oaknetwork/api` version +3. Update to latest `@oaknetwork/payments-sdk` version ``` ### TSDoc Examples diff --git a/README.md b/README.md index faf2c45d..0507d530 100644 --- a/README.md +++ b/README.md @@ -1,502 +1,50 @@ -# Oak Network SDK +# Oak Network SDKs -[![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) +[![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) -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. +This repository contains Oak Network's TypeScript SDKs for payments and smart contracts. +Oak Network helps teams build global payment products with secure APIs, typed developer tooling, and blockchain-integrated financial infrastructure. -> **Full documentation** — [oaknetwork.org/docs/sdk/api-sdk/overview](https://www.oaknetwork.org/docs/sdk/api-sdk/overview) +## SDK packages ---- +### Payments SDK -## Getting credentials +Primary SDK for Oak payments APIs (customers, payments, webhooks, transfers, refunds, and more). -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. +- Package: `@oaknetwork/payments-sdk` +- Folder: [`packages/payments`](./packages/payments) +- Public folder link: [github.com/oak-network/sdk/tree/main/packages/payments](https://github.com/oak-network/sdk/tree/main/packages/payments) +- Documentation: [oaknetwork.org/docs/sdk/overview](https://www.oaknetwork.org/docs/sdk/overview) +- npm: [npmjs.com/package/@oaknetwork/payments-sdk](https://www.npmjs.com/package/@oaknetwork/payments-sdk) -Create a `.env` file in your project root: +### Contracts SDK -```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 - -### Installation - -```bash -pnpm add @oaknetwork/api -# or -npm install @oaknetwork/api -# or -yarn add @oaknetwork/api -``` - -**Requirements:** Node.js 18+, TypeScript 5.x recommended. - -### Basic usage - -```typescript -import 'dotenv/config'; -import { createOakClient, createCustomerService } from '@oaknetwork/api'; - -const client = createOakClient({ - environment: 'sandbox', - clientId: process.env.CLIENT_ID!, - clientSecret: process.env.CLIENT_SECRET!, -}); - -const customers = createCustomerService(client); - -const result = await customers.list(); - -if (result.ok) { - console.log(result.value.data); -} else { - 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. - ---- - -## 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 | - ---- - -## Usage examples - -### Customers - -```typescript -import { createCustomerService } from '@oaknetwork/api'; - -const customers = createCustomerService(client); - -// Create -const result = await customers.create({ - email: 'user@example.com', - first_name: 'John', - last_name: 'Doe', - country_code: 'US', -}); - -// List -await customers.list({ limit: 10, offset: 0 }); - -// Get -await customers.get('customer_id'); - -// Update -await customers.update('customer_id', { email: 'new@example.com' }); -``` - -### Providers - -```typescript -import { createProviderService } from '@oaknetwork/api'; - -const providers = createProviderService(client); - -// Register as Stripe customer (buyer) -await providers.submitRegistration(customerId, { - provider: 'stripe', - target_role: 'customer', -}); - -// 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 -import { createPaymentService } from '@oaknetwork/api'; - -const payments = createPaymentService(client); - -// Create and capture a payment -const result = await payments.create({ - provider: 'stripe', - source: { - amount: 5000, - currency: 'usd', - customer: { id: customerId }, - payment_method: { type: 'card', id: paymentMethodId }, - capture_method: 'automatic', - }, - confirm: true, -}); - -// Confirm / cancel -await payments.confirm('payment_id'); -await payments.cancel('payment_id'); -``` - -### Payment methods - -```typescript -import { createPaymentMethodService } from '@oaknetwork/api'; - -const paymentMethods = createPaymentMethodService(client); - -// Add a card -await paymentMethods.add(customerId, { - type: 'card', - provider: 'stripe', -}); - -// 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 -import { createTransferService } from '@oaknetwork/api'; - -const transfers = createTransferService(client); - -await transfers.create({ - provider: 'stripe', - source: { - amount: 1000, - currency: 'usd', - customer: { id: customerId }, - }, - destination: { - customer: { id: customerId }, - payment_method: { type: 'bank', id: bankPmId }, - }, -}); -``` - -### Refunds - -```typescript -import { createRefundService } from '@oaknetwork/api'; - -const refunds = createRefundService(client); - -// Full refund -await refunds.create(paymentId, {}); - -// Partial refund -await refunds.create(paymentId, { amount: 500 }); -``` - -### Plans - -```typescript -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, -}); - -await plans.list(); -await plans.details('plan_id'); -``` - -### Transactions - -```typescript -import { createTransactionService } from '@oaknetwork/api'; - -const transactions = createTransactionService(client); - -await transactions.list({ limit: 20 }); -await transactions.get('txn_id'); -``` - ---- - -## Webhooks - -Register endpoints to receive real-time event notifications, and verify incoming payloads with HMAC-SHA256 signature verification. - -```typescript -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', -}); - -if (wh.ok) { - console.log('Secret:', wh.value.data.secret); // store securely -} - -// 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'); -``` - -### Verify and parse incoming events - -```typescript -import { parseWebhookPayload } from '@oaknetwork/api'; -import express from 'express'; - -const app = express(); - -app.post('/webhooks/oak', express.raw({ type: 'application/json' }), (req, res) => { - const result = parseWebhookPayload( - req.body.toString(), - req.headers['x-oak-signature'] as string, - process.env.WEBHOOK_SECRET!, - ); - - if (!result.ok) { - return res.status(401).json({ error: 'Invalid signature' }); - } - - switch (result.value.event) { - case 'payment.succeeded': - break; - case 'payment.failed': - break; - case 'provider_registration.approved': - break; - } - - res.json({ received: true }); -}); -``` - -> Full webhook reference — [oaknetwork.org/docs/sdk/api-sdk/webhooks](https://www.oaknetwork.org/docs/sdk/api-sdk/webhooks) - ---- - -## Error handling - -Every method returns `Result` — no uncaught exceptions. Check `result.ok` to branch on success or failure. - -```typescript -const result = await customers.create({ email: 'user@example.com', first_name: 'John' }); - -if (result.ok) { - const customer = result.value.data; - console.log('Created:', customer.id); -} else { - console.error('Failed:', result.error.message); - console.error('Status:', result.error.statusCode); - console.error('Code:', result.error.code); -} -``` - -| 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 | - -> Full error handling guide — [oaknetwork.org/docs/sdk/api-sdk/error-handling](https://www.oaknetwork.org/docs/sdk/api-sdk/error-handling) - ---- - -## Configuration - -### Environments +TypeScript SDK for Oak smart contracts and contract integrations. -| Environment | API Base URL | Description | -|---|---|---| -| `sandbox` | `https://api-stage.usecrowdpay.xyz` | Testing — all operations allowed | -| `production` | `https://app.usecrowdpay.xyz` | Live — test operations blocked | +- Package: `@oaknetwork/contracts` +- Folder: [`packages/contracts`](./packages/contracts) +- Documentation: [oaknetwork.org](https://www.oaknetwork.org/) -```typescript -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', -}); -``` - -### Retry configuration - -The SDK automatically retries failed requests with exponential backoff and jitter. - -```typescript -const client = createOakClient({ - environment: 'sandbox', - clientId: process.env.CLIENT_ID!, - clientSecret: process.env.CLIENT_SECRET!, - retryOptions: { - maxNumberOfRetries: 3, - delay: 1000, - backoffFactor: 2, - maxDelay: 30000, - }, -}); -``` - -Retried status codes: `408`, `429`, `500`, `502`, `503`, `504`. - ---- - -## TypeScript support - -The SDK ships full type declarations. All service methods, request payloads, and responses are typed. - -```typescript -import type { Result } from '@oaknetwork/api'; -``` - ---- - -## Development - -### Package manager - -This project uses **pnpm** exclusively: +## Monorepo quick start ```bash -pnpm install # Install dependencies -pnpm build # Build all packages -pnpm test # Run tests -pnpm lint # Lint code +pnpm install +pnpm build +pnpm test ``` -**Do not** use npm or yarn. The repository enforces pnpm >= 10.0.0. +## Development standards -### Running tests +- This repository uses `pnpm` only. +- Follow engineering and security standards in [`CLAUDE.md`](./CLAUDE.md). -```bash -pnpm test:unit # Unit tests -pnpm test:integration # Integration tests (requires credentials) -pnpm test:all # All tests with coverage -pnpm test:watch # Watch mode -``` +## Support -### Changesets workflow - -We use Changesets to manage versions and changelogs: - -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. - -### Development guidelines - -See [CLAUDE.md](./CLAUDE.md) for coding standards including architecture principles, security rules, testing requirements, and anti-patterns. - -### Code review checklist - -- [ ] `pnpm build` succeeds -- [ ] `pnpm test` passes with >90% coverage -- [ ] `pnpm lint` has no errors -- [ ] Changeset created with `pnpm changeset` -- [ ] Documentation updated if needed - ---- - -## 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) ---- +- Website: [oaknetwork.org](https://www.oaknetwork.org/) +- Issues: [github.com/oak-network/sdk/issues](https://github.com/oak-network/sdk/issues) +- Email: [support@oaknetwork.org](mailto:support@oaknetwork.org) ## License -[MIT](LICENSE) - -## Security - -[Security Policy](SECURITY.md) - -## Links - -- [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](mailto:support@oaknetwork.org) +[MIT](./LICENSE) diff --git a/SECURITY.md b/SECURITY.md index afe95113..2183667a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,11 +2,11 @@ ## Supported Versions -We actively maintain and provide security updates for the following versions of `@oaknetwork/api`: +We actively maintain and provide security updates for the following versions of `@oaknetwork/payments-sdk`: -| Version | Supported | -| ------- | ------------------ | -| 1.x | ✅ Active support | +| Version | Supported | +| ------- | ----------------- | +| 1.x | ✅ Active support | ## Reporting a Vulnerability @@ -30,12 +30,12 @@ When reporting, please include as much detail as possible: ### 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) | +| 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. diff --git a/codecov.yml b/codecov.yml index 3e2c3a61..2d13d9b4 100644 --- a/codecov.yml +++ b/codecov.yml @@ -2,4 +2,4 @@ codecov: branch: main fixes: - - "src/::packages/api/src/" + - "src/::packages/payments/src/" diff --git a/package.json b/package.json index c4fa0e5b..3a9b337f 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "crowdsplit-sdk-monorepo", + "name": "oak-sdk-monorepo", "version": "0.0.0", "private": true, - "description": "Crowdsplit SDK Monorepo", + "description": "Oak Network SDK Monorepo", "pnpm": { "overrides": { "minimatch@<10.2.1": ">=10.2.1", diff --git a/packages/api/CHANGELOG.md b/packages/api/CHANGELOG.md deleted file mode 100644 index 0b1cd15f..00000000 --- a/packages/api/CHANGELOG.md +++ /dev/null @@ -1,31 +0,0 @@ -# @oaknetwork/api - -## 1.0.1 - -### Patch Changes - -### Patch Changes - -- Initial release of SDK v1.0.1. - -## 1.0.0 - -### Major Changes - -- Initial release of Oak SDK - -## 0.1.0 - -### Minor Changes - -### Minor Changes - -- update changeset -- Fix release CI -- Testing NPM integraton - -### Patch Changes - -### Patch Changes - -- project setup diff --git a/packages/api/examples/webhooks/verify-signature.js b/packages/api/examples/webhooks/verify-signature.js deleted file mode 100644 index d59087b2..00000000 --- a/packages/api/examples/webhooks/verify-signature.js +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Webhook Signature Verification Example - * - * Demonstrates how to verify webhook signatures to ensure - * the payload is authentic and hasn't been tampered with. - */ - -const { verifyWebhookSignature, parseWebhookPayload } = require('../../dist/utils/webhookVerification'); -const logger = require('../common/logger'); -const crypto = require('crypto'); - -async function main() { - logger.section('Webhook Signature Verification Example'); - - // Example webhook data (simulating what Oak API would send) - const webhookSecret = 'your_webhook_secret_here'; - const payload = JSON.stringify({ - event: 'payment.completed', - data: { - payment_id: 'pay_123456', - amount: 100.00, - currency: 'USD', - customer_id: 'cus_789', - status: 'completed', - timestamp: new Date().toISOString(), - }, - }); - - // Generate a valid signature (Oak API would send this in the header) - const validSignature = crypto - .createHmac('sha256', webhookSecret) - .update(payload) - .digest('hex'); - - logger.step(1, 'Testing valid webhook signature...'); - logger.info('Payload', JSON.parse(payload)); - logger.info('Signature (first 20 chars)', validSignature.substring(0, 20) + '...'); - - const isValid = verifyWebhookSignature(payload, validSignature, webhookSecret); - - if (isValid) { - logger.success('✓ Signature is valid - webhook is authentic'); - } else { - logger.error('✗ Signature is invalid - webhook may be forged'); - } - - // Example 2: Invalid signature - logger.step(2, 'Testing invalid webhook signature...'); - const invalidSignature = 'invalid_signature_12345'; - - const isInvalid = verifyWebhookSignature(payload, invalidSignature, webhookSecret); - - if (!isInvalid) { - logger.success('✓ Correctly rejected invalid signature'); - } else { - logger.error('✗ Incorrectly accepted invalid signature'); - } - - // Example 3: Parse and verify in one step - logger.step(3, 'Using parseWebhookPayload (verify + parse)...'); - - const parseResult = parseWebhookPayload(payload, validSignature, webhookSecret); - - if (parseResult.ok) { - logger.success('Webhook payload verified and parsed successfully'); - logger.info('Parsed event', parseResult.value); - } else { - logger.error('Failed to verify/parse webhook', parseResult.error); - } - - // Example 4: Real-world Express.js webhook endpoint - logger.section('Example: Express.js Webhook Endpoint'); - - console.log(` -const express = require('express'); -const { parseWebhookPayload } = require('@oaknetwork/api'); - -const app = express(); - -app.post('/webhooks/oak', express.raw({ type: 'application/json' }), (req, res) => { - const signature = req.headers['x-oak-signature']; // Check actual header name - const payload = req.body.toString(); - const secret = process.env.WEBHOOK_SECRET; - - const result = parseWebhookPayload(payload, signature, secret); - - if (!result.ok) { - console.error('Invalid webhook signature'); - return res.status(401).json({ error: 'Invalid signature' }); - } - - // Handle the event - const event = result.value; - console.log('Received event:', event.event); - - switch (event.event) { - case 'payment.completed': - // Handle payment completion - break; - case 'payment.failed': - // Handle payment failure - break; - default: - console.log('Unhandled event type:', event.event); - } - - res.json({ received: true }); -}); - -app.listen(3000); - `); - - logger.section('Security Best Practices'); - console.log(' ✓ Always verify signatures before processing webhooks'); - console.log(' ✓ Use timing-safe comparison (built into verifyWebhookSignature)'); - console.log(' ✓ Store webhook secrets in environment variables'); - console.log(' ✓ Use HTTPS for webhook endpoints in production'); - console.log(' ✓ Validate payload structure before using data'); - console.log(' ✓ Implement idempotency using event IDs'); - -} - -main(); diff --git a/packages/api/src/products/crowdsplit/index.ts b/packages/api/src/products/crowdsplit/index.ts deleted file mode 100644 index 6a477e19..00000000 --- a/packages/api/src/products/crowdsplit/index.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { OakClient } from "../../types"; -import { - createBuyService, - createCustomerService, - createPaymentMethodService, - createPaymentService, - createPlanService, - createProviderService, - createRefundService, - createSellService, - createTransactionService, - createTransferService, - createWebhookService, - type BuyService, - type CustomerService, - type PaymentMethodService, - type PaymentService, - type PlanService, - type ProviderService, - type RefundService, - type SellService, - type TransactionService, - type TransferService, - type WebhookService, -} from "../../services"; - -export interface CrowdsplitProduct { - customers: CustomerService; - payments: PaymentService; - paymentMethods: PaymentMethodService; - providers: ProviderService; - refunds: RefundService; - transactions: TransactionService; - webhooks: WebhookService; - transfers: TransferService; - sell: SellService; - plans: PlanService; - buy: BuyService; -} - -/** - * @param client - Configured OakClient instance - * @returns Object containing all Crowdsplit service instances - */ -export const Crowdsplit = (client: OakClient): CrowdsplitProduct => ({ - customers: createCustomerService(client), - payments: createPaymentService(client), - paymentMethods: createPaymentMethodService(client), - providers: createProviderService(client), - refunds: createRefundService(client), - transactions: createTransactionService(client), - webhooks: createWebhookService(client), - transfers: createTransferService(client), - sell: createSellService(client), - plans: createPlanService(client), - buy: createBuyService(client), -}); diff --git a/packages/api/.env-sample b/packages/payments/.env-sample similarity index 100% rename from packages/api/.env-sample rename to packages/payments/.env-sample diff --git a/packages/api/BACKLOG.md b/packages/payments/BACKLOG.md similarity index 99% rename from packages/api/BACKLOG.md rename to packages/payments/BACKLOG.md index 42b67c66..383c044a 100644 --- a/packages/api/BACKLOG.md +++ b/packages/payments/BACKLOG.md @@ -1,4 +1,4 @@ -# packages/api — Fix Backlog +# packages/payments — Fix Backlog Based on code review (Feb 2026). Ordered by priority. diff --git a/packages/payments/CHANGELOG.md b/packages/payments/CHANGELOG.md new file mode 100644 index 00000000..8509c38b --- /dev/null +++ b/packages/payments/CHANGELOG.md @@ -0,0 +1,83 @@ +# @oaknetwork/payments-sdk + +## 1.1.0 + +### Minor Changes + +### Minor Changes + +- change the name of the SDK folder to payments-sdk +- Remove Crowdsplit product facade and rename CrowdSplit types to Oak + + ### WHAT + + - Removed the `Crowdsplit` product facade (`@oaknetwork/payments-sdk/products/crowdsplit`) + - Removed the `./products/crowdsplit` export path from package.json + - Renamed payment method types: + - `CrowdSplitBankAccount` → `OakBankAccount` + - `CrowdSplitCustomerWallet` → `OakCustomerWallet` + - `CrowdSplitPix` → `OakPix` + + ### WHY + + - Align the SDK codebase with public documentation which uses direct service imports + - Remove legacy "Crowdsplit" branding in favor of "Oak Network" + - Simplify the API surface by having one canonical way to use services + + ### HOW to migrate + + Replace the Crowdsplit facade with direct service imports: + + ```typescript + // Before + import { Crowdsplit } from "@oaknetwork/payments-sdk/products/crowdsplit"; + const { customers, payments } = Crowdsplit(client); + + // After + import { + createCustomerService, + createPaymentService, + } from "@oaknetwork/payments-sdk"; + const customers = createCustomerService(client); + const payments = createPaymentService(client); + ``` + + Update type references: + + ```typescript + // Before + const bankAccount: CrowdSplitBankAccount = { ... }; + + // After + const bankAccount: OakBankAccount = { ... }; + ``` + +## 1.0.1 + +### Patch Changes + +### Patch Changes + +- Initial release of SDK v1.0.1. + +## 1.0.0 + +### Major Changes + +- Initial release of Oak SDK + +## 0.1.0 + +### Minor Changes + +### Minor Changes + +- update changeset +- Fix release CI +- Testing NPM integraton + +### Patch Changes + +### Patch Changes + +- project setup diff --git a/packages/payments/README.md b/packages/payments/README.md new file mode 100644 index 00000000..e12daf1b --- /dev/null +++ b/packages/payments/README.md @@ -0,0 +1,509 @@ +# Oak Payments SDK + +A fully-typed TypeScript SDK for the Oak Network Payments API. Drop-in authentication, automatic retries, and zero boilerplate, production-ready from day one. + +> **Full documentation** - [oaknetwork.org/docs/sdk/overview](https://www.oaknetwork.org/docs/sdk/overview) + +## Getting credentials + +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 + +### Installation + +```bash +pnpm add @oaknetwork/payments-sdk +# or +npm install @oaknetwork/payments-sdk +# or +yarn add @oaknetwork/payments-sdk +``` + +**Requirements:** Node.js 18+, TypeScript 5.x recommended. + +### Basic usage + +```typescript +import "dotenv/config"; +import { + createOakClient, + createCustomerService, +} from "@oaknetwork/payments-sdk"; + +const client = createOakClient({ + environment: "sandbox", + clientId: process.env.CLIENT_ID!, + clientSecret: process.env.CLIENT_SECRET!, +}); + +const customers = createCustomerService(client); + +const result = await customers.list(); + +if (result.ok) { + console.log(result.value.data); +} else { + console.error(result.error.message); +} +``` + +> See the full [Quickstart guide](https://www.oaknetwork.org/docs/sdk/quickstart) for a step-by-step walkthrough. + +--- + +## 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/customers) | `createCustomerService(client)` | Create, get, list, update, sync, and check balances | +| [Payments](https://www.oaknetwork.org/docs/sdk/payments) | `createPaymentService(client)` | Create, confirm, cancel payments | +| [Payment Methods](https://www.oaknetwork.org/docs/sdk/payment-methods) | `createPaymentMethodService(client)` | Add, list, get, delete payment methods | +| [Webhooks](https://www.oaknetwork.org/docs/sdk/webhooks) | `createWebhookService(client)` | Register, manage, and monitor webhooks | +| [Transactions](https://www.oaknetwork.org/docs/sdk/transactions) | `createTransactionService(client)` | List, get, and settle transactions | +| [Transfers](https://www.oaknetwork.org/docs/sdk/transfers) | `createTransferService(client)` | Create provider transfers (Stripe, PagarMe, BRLA) | +| [Plans](https://www.oaknetwork.org/docs/sdk/plans) | `createPlanService(client)` | CRUD subscription plans | +| [Refunds](https://www.oaknetwork.org/docs/sdk/refunds) | `createRefundService(client)` | Refund a payment (full or partial) | +| [Buy](https://www.oaknetwork.org/docs/sdk/buy-and-sell) | `createBuyService(client)` | Crypto on-ramp via Bridge | +| [Sell](https://www.oaknetwork.org/docs/sdk/buy-and-sell) | `createSellService(client)` | Crypto off-ramp via Avenia | + +--- + +## Usage examples + +### Customers + +```typescript +import { createCustomerService } from "@oaknetwork/payments-sdk"; + +const customers = createCustomerService(client); + +// Create +const result = await customers.create({ + email: "user@example.com", + first_name: "John", + last_name: "Doe", + country_code: "US", +}); + +// List +await customers.list({ limit: 10, offset: 0 }); + +// Get +await customers.get("customer_id"); + +// Update +await customers.update("customer_id", { email: "new@example.com" }); +``` + +### Providers + +```typescript +import { createProviderService } from "@oaknetwork/payments-sdk"; + +const providers = createProviderService(client); + +// Register as Stripe customer (buyer) +await providers.submitRegistration(customerId, { + provider: "stripe", + target_role: "customer", +}); + +// 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 +import { createPaymentService } from "@oaknetwork/payments-sdk"; + +const payments = createPaymentService(client); + +// Create and capture a payment +const result = await payments.create({ + provider: "stripe", + source: { + amount: 5000, + currency: "usd", + customer: { id: customerId }, + payment_method: { type: "card", id: paymentMethodId }, + capture_method: "automatic", + }, + confirm: true, +}); + +// Confirm / cancel +await payments.confirm("payment_id"); +await payments.cancel("payment_id"); +``` + +### Payment methods + +```typescript +import { createPaymentMethodService } from "@oaknetwork/payments-sdk"; + +const paymentMethods = createPaymentMethodService(client); + +// Add a card +await paymentMethods.add(customerId, { + type: "card", + provider: "stripe", +}); + +// 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 +import { createTransferService } from "@oaknetwork/payments-sdk"; + +const transfers = createTransferService(client); + +await transfers.create({ + provider: "stripe", + source: { + amount: 1000, + currency: "usd", + customer: { id: customerId }, + }, + destination: { + customer: { id: customerId }, + payment_method: { type: "bank", id: bankPmId }, + }, +}); +``` + +### Refunds + +```typescript +import { createRefundService } from "@oaknetwork/payments-sdk"; + +const refunds = createRefundService(client); + +// Full refund +await refunds.create(paymentId, {}); + +// Partial refund +await refunds.create(paymentId, { amount: 500 }); +``` + +### Plans + +```typescript +import { createPlanService } from "@oaknetwork/payments-sdk"; + +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, +}); + +await plans.list(); +await plans.details("plan_id"); +``` + +### Transactions + +```typescript +import { createTransactionService } from "@oaknetwork/payments-sdk"; + +const transactions = createTransactionService(client); + +await transactions.list({ limit: 20 }); +await transactions.get("txn_id"); +``` + +--- + +## Webhooks + +Register endpoints to receive real-time event notifications, and verify incoming payloads with HMAC-SHA256 signature verification. + +```typescript +import { + createWebhookService, + verifyWebhookSignature, + parseWebhookPayload, +} from "@oaknetwork/payments-sdk"; + +const webhooks = createWebhookService(client); + +// Register +const wh = await webhooks.register({ + url: "https://your-server.com/webhooks/oak", + description: "Payment events", +}); + +if (wh.ok) { + console.log("Secret:", wh.value.data.secret); // store securely +} + +// 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"); +``` + +### Verify and parse incoming events + +```typescript +import { parseWebhookPayload } from "@oaknetwork/payments-sdk"; +import express from "express"; + +const app = express(); + +app.post( + "/webhooks/oak", + express.raw({ type: "application/json" }), + (req, res) => { + const result = parseWebhookPayload( + req.body.toString(), + req.headers["x-oak-signature"] as string, + process.env.WEBHOOK_SECRET!, + ); + + if (!result.ok) { + return res.status(401).json({ error: "Invalid signature" }); + } + + switch (result.value.event) { + case "payment.succeeded": + break; + case "payment.failed": + break; + case "provider_registration.approved": + break; + } + + res.json({ received: true }); + }, +); +``` + +> Full webhook reference — [oaknetwork.org/docs/sdk/webhooks](https://www.oaknetwork.org/docs/sdk/webhooks) + +--- + +## Error handling + +Every method returns `Result` — no uncaught exceptions. Check `result.ok` to branch on success or failure. + +```typescript +const result = await customers.create({ + email: "user@example.com", + first_name: "John", +}); + +if (result.ok) { + const customer = result.value.data; + console.log("Created:", customer.id); +} else { + console.error("Failed:", result.error.message); + console.error("Status:", result.error.statusCode); + console.error("Code:", result.error.code); +} +``` + +| 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 | + +> Full error handling guide — [oaknetwork.org/docs/sdk/error-handling](https://www.oaknetwork.org/docs/sdk/error-handling) + +--- + +## Configuration + +### 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 +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", +}); +``` + +### Retry configuration + +The SDK automatically retries failed requests with exponential backoff and jitter. + +```typescript +const client = createOakClient({ + environment: "sandbox", + clientId: process.env.CLIENT_ID!, + clientSecret: process.env.CLIENT_SECRET!, + retryOptions: { + maxNumberOfRetries: 3, + delay: 1000, + backoffFactor: 2, + maxDelay: 30000, + }, +}); +``` + +Retried status codes: `408`, `429`, `500`, `502`, `503`, `504`. + +--- + +## TypeScript support + +The SDK ships full type declarations. All service methods, request payloads, and responses are typed. + +```typescript +import type { Result } from "@oaknetwork/payments-sdk"; +``` + +--- + +## Development + +### 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 +``` + +**Do not** use npm or yarn. The repository enforces pnpm >= 10.0.0. + +### Running tests + +```bash +pnpm test:unit # Unit tests +pnpm test:integration # Integration tests (requires credentials) +pnpm test:all # All tests with coverage +pnpm test:watch # Watch mode +``` + +### Changesets workflow + +We use Changesets to manage versions and changelogs: + +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. + +### Development guidelines + +See [CLAUDE.md](../../CLAUDE.md) for coding standards including architecture principles, security rules, testing requirements, and anti-patterns. + +### Code review checklist + +- [ ] `pnpm build` succeeds +- [ ] `pnpm test` passes with >90% coverage +- [ ] `pnpm lint` has no errors +- [ ] Changeset created with `pnpm changeset` +- [ ] Documentation updated if needed + +--- + +## Documentation + +- **Full docs** — [oaknetwork.org/docs/sdk/overview](https://www.oaknetwork.org/docs/sdk/overview) +- **Quickstart** — [oaknetwork.org/docs/sdk/quickstart](https://www.oaknetwork.org/docs/sdk/quickstart) +- **Monorepo README** — [README.md](../../README.md) +- **Changelog** — [CHANGELOG.md](./CHANGELOG.md) + +--- + +## License + +[MIT](../../LICENSE) + +## Security + +[Security Policy](../../SECURITY.md) + +## Links + +- [Oak Network](https://www.oaknetwork.org/) +- [Documentation](https://www.oaknetwork.org/docs/sdk/overview) +- [GitHub](https://github.com/oak-network/sdk) +- [Issues](https://github.com/oak-network/sdk/issues) +- [npm](https://www.npmjs.com/package/@oaknetwork/payments-sdk) + +--- + +**Questions?** Open an issue or contact [support@oaknetwork.org](mailto:support@oaknetwork.org) diff --git a/packages/api/__tests__/config.ts b/packages/payments/__tests__/config.ts similarity index 100% rename from packages/api/__tests__/config.ts rename to packages/payments/__tests__/config.ts diff --git a/packages/api/__tests__/integration/authService.test.ts b/packages/payments/__tests__/integration/authService.test.ts similarity index 100% rename from packages/api/__tests__/integration/authService.test.ts rename to packages/payments/__tests__/integration/authService.test.ts diff --git a/packages/api/__tests__/integration/customerService.test.ts b/packages/payments/__tests__/integration/customerService.test.ts similarity index 93% rename from packages/api/__tests__/integration/customerService.test.ts rename to packages/payments/__tests__/integration/customerService.test.ts index 68c0afec..0a7a6da4 100644 --- a/packages/api/__tests__/integration/customerService.test.ts +++ b/packages/payments/__tests__/integration/customerService.test.ts @@ -1,11 +1,11 @@ -import { createOakClient } from "../../src"; -import { Crowdsplit } from "../../src/products/crowdsplit"; +import { createOakClient, createCustomerService } from "../../src"; +import { CustomerService } from "../../src/services/customerService"; import { getConfigFromEnv } from "../config"; const INTEGRATION_TEST_TIMEOUT = 30000; describe("CustomerService - Integration", () => { - let customers: ReturnType["customers"]; + let customers: CustomerService; /** Customer resolved from list so get/update tests don't depend on create succeeding. */ let existingCustomerId: string | undefined; @@ -18,7 +18,7 @@ describe("CustomerService - Integration", () => { backoffFactor: 2, }, }); - customers = Crowdsplit(client).customers; + customers = createCustomerService(client); const listResponse = await customers.list({ limit: 1 }); if (listResponse.ok && listResponse.value.data.customer_list.length > 0) { diff --git a/packages/api/__tests__/integration/paymentMethodService.test.ts b/packages/payments/__tests__/integration/paymentMethodService.test.ts similarity index 94% rename from packages/api/__tests__/integration/paymentMethodService.test.ts rename to packages/payments/__tests__/integration/paymentMethodService.test.ts index a7d23701..8975c3bf 100644 --- a/packages/api/__tests__/integration/paymentMethodService.test.ts +++ b/packages/payments/__tests__/integration/paymentMethodService.test.ts @@ -1,12 +1,13 @@ -import { createOakClient } from "../../src"; -import { Crowdsplit } from "../../src/products/crowdsplit"; +import { createOakClient, createPaymentMethodService, createCustomerService } from "../../src"; +import { PaymentMethodService } from "../../src/services/paymentMethodService"; +import { CustomerService } from "../../src/services/customerService"; import { getConfigFromEnv } from "../config"; const INTEGRATION_TEST_TIMEOUT = 30000; describe("PaymentMethodService - Integration", () => { - let paymentMethods: ReturnType["paymentMethods"]; - let customers: ReturnType["customers"]; + let paymentMethods: PaymentMethodService; + let customers: CustomerService; let testCustomerId: string; let createdPaymentMethodId: string | undefined; @@ -19,9 +20,8 @@ describe("PaymentMethodService - Integration", () => { backoffFactor: 2, }, }); - const crowdsplit = Crowdsplit(client); - paymentMethods = crowdsplit.paymentMethods; - customers = crowdsplit.customers; + paymentMethods = createPaymentMethodService(client); + customers = createCustomerService(client); const listResponse = await customers.list({ limit: 1 }); if (listResponse.ok && listResponse.value.data.customer_list.length > 0) { diff --git a/packages/api/__tests__/integration/paymentService.test.ts b/packages/payments/__tests__/integration/paymentService.test.ts similarity index 94% rename from packages/api/__tests__/integration/paymentService.test.ts rename to packages/payments/__tests__/integration/paymentService.test.ts index 35532926..cbeeee8f 100644 --- a/packages/api/__tests__/integration/paymentService.test.ts +++ b/packages/payments/__tests__/integration/paymentService.test.ts @@ -1,5 +1,6 @@ -import { createOakClient, Payment } from '../../src'; -import { Crowdsplit } from '../../src/products/crowdsplit'; +import { createOakClient, Payment, createPaymentService, createCustomerService } from '../../src'; +import { PaymentService } from '../../src/services/paymentService'; +import { CustomerService } from '../../src/services/customerService'; import { ApiError } from '../../src/utils/errorHandler'; import { getConfigFromEnv } from '../config'; @@ -53,8 +54,8 @@ const buildStripePaymentRequest = ( }; describe('PaymentService - Integration', () => { - let payments: ReturnType['payments']; - let customers: ReturnType['customers']; + let payments: PaymentService; + let customers: CustomerService; /** A KYC-approved customer ID fetched via filtered customer list. */ let approvedCustomerId: string | undefined; @@ -68,9 +69,8 @@ describe('PaymentService - Integration', () => { backoffFactor: 2, }, }); - const cs = Crowdsplit(client); - customers = cs.customers; - payments = cs.payments; + customers = createCustomerService(client); + payments = createPaymentService(client); }); // --------------------------------------------------------------- diff --git a/packages/api/__tests__/integration/providerService.test.ts b/packages/payments/__tests__/integration/providerService.test.ts similarity index 93% rename from packages/api/__tests__/integration/providerService.test.ts rename to packages/payments/__tests__/integration/providerService.test.ts index add0dec4..564f1b5b 100644 --- a/packages/api/__tests__/integration/providerService.test.ts +++ b/packages/payments/__tests__/integration/providerService.test.ts @@ -1,13 +1,14 @@ -import { createOakClient } from '../../src'; -import { Crowdsplit } from '../../src/products/crowdsplit'; +import { createOakClient, createProviderService, createCustomerService } from '../../src'; +import { ProviderService } from '../../src/services/providerService'; +import { CustomerService } from '../../src/services/customerService'; import { getConfigFromEnv } from '../config'; import { ApiError } from '../../src/utils/errorHandler'; const INTEGRATION_TEST_TIMEOUT = 30000; describe('ProviderService - Integration', () => { - let providers: ReturnType['providers']; - let customers: ReturnType['customers']; + let providers: ProviderService; + let customers: CustomerService; /** An existing customer ID fetched from the database. */ let existingCustomerId: string | undefined; @@ -21,9 +22,8 @@ describe('ProviderService - Integration', () => { backoffFactor: 2, }, }); - const cs = Crowdsplit(client); - providers = cs.providers; - customers = cs.customers; + providers = createProviderService(client); + customers = createCustomerService(client); }); // --------------------------------------------------------------- diff --git a/packages/api/__tests__/integration/transactionService.test.ts b/packages/payments/__tests__/integration/transactionService.test.ts similarity index 94% rename from packages/api/__tests__/integration/transactionService.test.ts rename to packages/payments/__tests__/integration/transactionService.test.ts index ec44d8b9..4a5f46e8 100644 --- a/packages/api/__tests__/integration/transactionService.test.ts +++ b/packages/payments/__tests__/integration/transactionService.test.ts @@ -1,14 +1,16 @@ -import { createOakClient } from '../../src'; -import { Crowdsplit } from '../../src/products/crowdsplit'; +import { createOakClient, createTransactionService, createPaymentService, createCustomerService } from '../../src'; +import { TransactionService } from '../../src/services/transactionService'; +import { PaymentService } from '../../src/services/paymentService'; +import { CustomerService } from '../../src/services/customerService'; import { getConfigFromEnv } from '../config'; import { ApiError } from '../../src/utils/errorHandler'; const INTEGRATION_TEST_TIMEOUT = 30000; describe('TransactionService - Integration', () => { - let transactions: ReturnType['transactions']; - let payments: ReturnType['payments']; - let customers: ReturnType['customers']; + let transactions: TransactionService; + let payments: PaymentService; + let customers: CustomerService; /** A KYC-approved customer ID fetched via filtered customer list. */ let approvedCustomerId: string | undefined; @@ -26,10 +28,9 @@ describe('TransactionService - Integration', () => { backoffFactor: 2, }, }); - const cs = Crowdsplit(client); - transactions = cs.transactions; - payments = cs.payments; - customers = cs.customers; + transactions = createTransactionService(client); + payments = createPaymentService(client); + customers = createCustomerService(client); }); // --------------------------------------------------------------- diff --git a/packages/api/__tests__/integration/transferService.test.ts b/packages/payments/__tests__/integration/transferService.test.ts similarity index 81% rename from packages/api/__tests__/integration/transferService.test.ts rename to packages/payments/__tests__/integration/transferService.test.ts index a6580de8..e22b28d5 100644 --- a/packages/api/__tests__/integration/transferService.test.ts +++ b/packages/payments/__tests__/integration/transferService.test.ts @@ -1,13 +1,15 @@ -import { createOakClient } from "../../src"; -import { Crowdsplit } from "../../src/products/crowdsplit"; +import { createOakClient, createTransferService, createCustomerService, createPaymentMethodService } from "../../src"; +import { TransferService } from "../../src/services/transferService"; +import { CustomerService } from "../../src/services/customerService"; +import { PaymentMethodService } from "../../src/services/paymentMethodService"; import { getConfigFromEnv } from "../config"; const INTEGRATION_TEST_TIMEOUT = 30000; describe("TransferService - Integration", () => { - let transfers: ReturnType["transfers"]; - let customers: ReturnType["customers"]; - let paymentMethods: ReturnType["paymentMethods"]; + let transfers: TransferService; + let customers: CustomerService; + let paymentMethods: PaymentMethodService; beforeAll(() => { const client = createOakClient({ @@ -18,9 +20,9 @@ describe("TransferService - Integration", () => { backoffFactor: 2, }, }); - transfers = Crowdsplit(client).transfers; - customers = Crowdsplit(client).customers; - paymentMethods = Crowdsplit(client).paymentMethods; + transfers = createTransferService(client); + customers = createCustomerService(client); + paymentMethods = createPaymentMethodService(client); }); let customerId: string | undefined; diff --git a/packages/api/__tests__/integration/webhookService.test.ts b/packages/payments/__tests__/integration/webhookService.test.ts similarity index 96% rename from packages/api/__tests__/integration/webhookService.test.ts rename to packages/payments/__tests__/integration/webhookService.test.ts index c31305dd..eb9f47ce 100644 --- a/packages/api/__tests__/integration/webhookService.test.ts +++ b/packages/payments/__tests__/integration/webhookService.test.ts @@ -1,11 +1,11 @@ -import { createOakClient } from "../../src"; -import { Crowdsplit } from "../../src/products/crowdsplit"; +import { createOakClient, createWebhookService } from "../../src"; +import { WebhookService } from "../../src/services/webhookService"; import { getConfigFromEnv } from "../config"; const INTEGRATION_TEST_TIMEOUT = 30000; describe("WebhookService - Integration", () => { - let webhooks: ReturnType["webhooks"]; + let webhooks: WebhookService; beforeAll(() => { const client = createOakClient({ @@ -16,7 +16,7 @@ describe("WebhookService - Integration", () => { backoffFactor: 2, }, }); - webhooks = Crowdsplit(client).webhooks; + webhooks = createWebhookService(client); }); let createdWebhookId: string | undefined; diff --git a/packages/api/__tests__/setup.ts b/packages/payments/__tests__/setup.ts similarity index 100% rename from packages/api/__tests__/setup.ts rename to packages/payments/__tests__/setup.ts diff --git a/packages/api/__tests__/unit/authService.test.ts b/packages/payments/__tests__/unit/authService.test.ts similarity index 100% rename from packages/api/__tests__/unit/authService.test.ts rename to packages/payments/__tests__/unit/authService.test.ts diff --git a/packages/api/__tests__/unit/buildUrl.test.ts b/packages/payments/__tests__/unit/buildUrl.test.ts similarity index 100% rename from packages/api/__tests__/unit/buildUrl.test.ts rename to packages/payments/__tests__/unit/buildUrl.test.ts diff --git a/packages/api/__tests__/unit/client.test.ts b/packages/payments/__tests__/unit/client.test.ts similarity index 100% rename from packages/api/__tests__/unit/client.test.ts rename to packages/payments/__tests__/unit/client.test.ts diff --git a/packages/api/__tests__/unit/customerService.test.ts b/packages/payments/__tests__/unit/customerService.test.ts similarity index 95% rename from packages/api/__tests__/unit/customerService.test.ts rename to packages/payments/__tests__/unit/customerService.test.ts index fbc67b7d..6a5d363e 100644 --- a/packages/api/__tests__/unit/customerService.test.ts +++ b/packages/payments/__tests__/unit/customerService.test.ts @@ -1,5 +1,5 @@ -import { createOakClient } from "../../src"; -import { Crowdsplit } from "../../src/products/crowdsplit"; +import { createOakClient, createCustomerService } from "../../src"; +import { CustomerService } from "../../src/services/customerService"; import { httpClient } from "../../src/utils/httpClient"; import { ApiError } from "../../src/utils/errorHandler"; import { RetryOptions } from "../../src/utils/defaultRetryConfig"; @@ -17,7 +17,7 @@ jest.mock("../../src/utils/httpClient", () => ({ })); describe("CustomerService - Unit", () => { - let customers: ReturnType["customers"]; + let customers: CustomerService; let client: ReturnType; let config: OakClientConfig; let retryOptions: RetryOptions; @@ -34,7 +34,7 @@ describe("CustomerService - Unit", () => { retryOptions, }); jest.spyOn(client, "getAccessToken").mockResolvedValue(ok("fake-token")); - customers = Crowdsplit(client).customers; + customers = createCustomerService(client); jest.clearAllMocks(); }); diff --git a/packages/api/__tests__/unit/environment.test.ts b/packages/payments/__tests__/unit/environment.test.ts similarity index 100% rename from packages/api/__tests__/unit/environment.test.ts rename to packages/payments/__tests__/unit/environment.test.ts diff --git a/packages/api/__tests__/unit/services.test.ts b/packages/payments/__tests__/unit/services.test.ts similarity index 99% rename from packages/api/__tests__/unit/services.test.ts rename to packages/payments/__tests__/unit/services.test.ts index 85c8c66b..9d11e858 100644 --- a/packages/api/__tests__/unit/services.test.ts +++ b/packages/payments/__tests__/unit/services.test.ts @@ -121,7 +121,7 @@ const expectTokenFailure = async (call: () => Promise) => { } }; -describe("Crowdsplit services (Unit)", () => { +describe("Oak services (Unit)", () => { beforeEach(() => { jest.clearAllMocks(); }); diff --git a/packages/api/__tests__/unit/utils.test.ts b/packages/payments/__tests__/unit/utils.test.ts similarity index 100% rename from packages/api/__tests__/unit/utils.test.ts rename to packages/payments/__tests__/unit/utils.test.ts diff --git a/packages/api/__tests__/unit/webhookVerification.test.ts b/packages/payments/__tests__/unit/webhookVerification.test.ts similarity index 100% rename from packages/api/__tests__/unit/webhookVerification.test.ts rename to packages/payments/__tests__/unit/webhookVerification.test.ts diff --git a/packages/api/__tests__/unit/withAuth.test.ts b/packages/payments/__tests__/unit/withAuth.test.ts similarity index 100% rename from packages/api/__tests__/unit/withAuth.test.ts rename to packages/payments/__tests__/unit/withAuth.test.ts diff --git a/packages/api/examples/.env.example b/packages/payments/examples/.env.example similarity index 100% rename from packages/api/examples/.env.example rename to packages/payments/examples/.env.example diff --git a/packages/api/examples/QUICK_START.md b/packages/payments/examples/QUICK_START.md similarity index 98% rename from packages/api/examples/QUICK_START.md rename to packages/payments/examples/QUICK_START.md index a1b6f416..50bc8f11 100644 --- a/packages/api/examples/QUICK_START.md +++ b/packages/payments/examples/QUICK_START.md @@ -2,7 +2,7 @@ ## Setup (One-time) -1. **Build the SDK** (from `packages/api` directory): +1. **Build the SDK** (from `packages/payments` directory): ```bash npm run build ``` diff --git a/packages/api/examples/README.md b/packages/payments/examples/README.md similarity index 98% rename from packages/api/examples/README.md rename to packages/payments/examples/README.md index 02e4b069..7974f731 100644 --- a/packages/api/examples/README.md +++ b/packages/payments/examples/README.md @@ -12,7 +12,7 @@ This directory contains comprehensive, modular examples demonstrating how to use ### 1. Install Dependencies -From the `packages/api` directory: +From the `packages/payments` directory: ```bash npm install diff --git a/packages/api/examples/authentication/get-token.js b/packages/payments/examples/authentication/get-token.js similarity index 100% rename from packages/api/examples/authentication/get-token.js rename to packages/payments/examples/authentication/get-token.js diff --git a/packages/api/examples/common/config.js b/packages/payments/examples/common/config.js similarity index 100% rename from packages/api/examples/common/config.js rename to packages/payments/examples/common/config.js diff --git a/packages/api/examples/common/logger.js b/packages/payments/examples/common/logger.js similarity index 100% rename from packages/api/examples/common/logger.js rename to packages/payments/examples/common/logger.js diff --git a/packages/api/examples/customers/create-customer.js b/packages/payments/examples/customers/create-customer.js similarity index 90% rename from packages/api/examples/customers/create-customer.js rename to packages/payments/examples/customers/create-customer.js index 2a14439d..af850c0f 100644 --- a/packages/api/examples/customers/create-customer.js +++ b/packages/payments/examples/customers/create-customer.js @@ -5,7 +5,7 @@ */ const { getOakClient } = require('../common/config'); -const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const { createCustomerService } = require('../../dist/index.js'); const logger = require('../common/logger'); async function main() { @@ -13,7 +13,7 @@ async function main() { try { const client = getOakClient(); - const customers = Crowdsplit(client).customers; + const customers = createCustomerService(client); const email = `customer_${Date.now()}@example.com`; logger.step(1, 'Creating customer (email only)...'); diff --git a/packages/api/examples/customers/get-customer.js b/packages/payments/examples/customers/get-customer.js similarity index 89% rename from packages/api/examples/customers/get-customer.js rename to packages/payments/examples/customers/get-customer.js index 78d3a49e..a4e1fbbd 100644 --- a/packages/api/examples/customers/get-customer.js +++ b/packages/payments/examples/customers/get-customer.js @@ -5,7 +5,7 @@ */ const { getOakClient, resolveCustomerId } = require('../common/config'); -const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const { createCustomerService } = require('../../dist/index.js'); const logger = require('../common/logger'); async function main() { @@ -13,7 +13,7 @@ async function main() { try { const client = getOakClient(); - const customers = Crowdsplit(client).customers; + const customers = createCustomerService(client); logger.step(1, 'Resolving customer...'); const customerId = await resolveCustomerId(customers); diff --git a/packages/api/examples/customers/list-customers.js b/packages/payments/examples/customers/list-customers.js similarity index 94% rename from packages/api/examples/customers/list-customers.js rename to packages/payments/examples/customers/list-customers.js index 4a0ec64b..d7b6d13a 100644 --- a/packages/api/examples/customers/list-customers.js +++ b/packages/payments/examples/customers/list-customers.js @@ -5,7 +5,7 @@ */ const { getOakClient } = require('../common/config'); -const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const { createCustomerService } = require('../../dist/index.js'); const logger = require('../common/logger'); async function main() { @@ -13,7 +13,7 @@ async function main() { try { const client = getOakClient(); - const customers = Crowdsplit(client).customers; + const customers = createCustomerService(client); // Example 1: List first 5 customers logger.step(1, 'Listing first 5 customers...'); diff --git a/packages/api/examples/customers/update-customer.js b/packages/payments/examples/customers/update-customer.js similarity index 91% rename from packages/api/examples/customers/update-customer.js rename to packages/payments/examples/customers/update-customer.js index 216674ac..f09e837d 100644 --- a/packages/api/examples/customers/update-customer.js +++ b/packages/payments/examples/customers/update-customer.js @@ -5,7 +5,7 @@ */ const { getOakClient, resolveCustomerId } = require('../common/config'); -const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const { createCustomerService } = require('../../dist/index.js'); const logger = require('../common/logger'); async function main() { @@ -13,7 +13,7 @@ async function main() { try { const client = getOakClient(); - const customers = Crowdsplit(client).customers; + const customers = createCustomerService(client); logger.step(1, 'Resolving customer...'); const customerId = await resolveCustomerId(customers); diff --git a/packages/api/examples/payment-methods/add-bank-account.js b/packages/payments/examples/payment-methods/add-bank-account.js similarity index 88% rename from packages/api/examples/payment-methods/add-bank-account.js rename to packages/payments/examples/payment-methods/add-bank-account.js index 5f4ed6fc..a311cb06 100644 --- a/packages/api/examples/payment-methods/add-bank-account.js +++ b/packages/payments/examples/payment-methods/add-bank-account.js @@ -6,7 +6,7 @@ */ const { getOakClient, resolveCustomerId } = require('../common/config'); -const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const { createPaymentMethodService, createCustomerService } = require('../../dist/index.js'); const logger = require('../common/logger'); async function main() { @@ -14,7 +14,8 @@ async function main() { try { const client = getOakClient(); - const { paymentMethods, customers } = Crowdsplit(client); + const paymentMethods = createPaymentMethodService(client); + const customers = createCustomerService(client); logger.step(1, 'Resolving customer...'); const customerId = await resolveCustomerId(customers); diff --git a/packages/api/examples/payment-methods/add-pix.js b/packages/payments/examples/payment-methods/add-pix.js similarity index 100% rename from packages/api/examples/payment-methods/add-pix.js rename to packages/payments/examples/payment-methods/add-pix.js diff --git a/packages/api/examples/payment-methods/delete-payment-method.js b/packages/payments/examples/payment-methods/delete-payment-method.js similarity index 90% rename from packages/api/examples/payment-methods/delete-payment-method.js rename to packages/payments/examples/payment-methods/delete-payment-method.js index 9d8b2b9f..ed247fe3 100644 --- a/packages/api/examples/payment-methods/delete-payment-method.js +++ b/packages/payments/examples/payment-methods/delete-payment-method.js @@ -6,7 +6,7 @@ */ const { getOakClient, resolveCustomerId } = require('../common/config'); -const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const { createPaymentMethodService, createCustomerService } = require('../../dist/index.js'); const logger = require('../common/logger'); async function main() { @@ -14,7 +14,8 @@ async function main() { try { const client = getOakClient(); - const { paymentMethods, customers } = Crowdsplit(client); + const paymentMethods = createPaymentMethodService(client); + const customers = createCustomerService(client); logger.step(1, 'Resolving customer...'); const customerId = await resolveCustomerId(customers); diff --git a/packages/api/examples/payment-methods/list-payment-methods.js b/packages/payments/examples/payment-methods/list-payment-methods.js similarity index 90% rename from packages/api/examples/payment-methods/list-payment-methods.js rename to packages/payments/examples/payment-methods/list-payment-methods.js index af1bddc1..a1efeb85 100644 --- a/packages/api/examples/payment-methods/list-payment-methods.js +++ b/packages/payments/examples/payment-methods/list-payment-methods.js @@ -5,7 +5,7 @@ */ const { getOakClient, resolveCustomerId } = require('../common/config'); -const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const { createPaymentMethodService, createCustomerService } = require('../../dist/index.js'); const logger = require('../common/logger'); async function main() { @@ -13,7 +13,8 @@ async function main() { try { const client = getOakClient(); - const { paymentMethods, customers } = Crowdsplit(client); + const paymentMethods = createPaymentMethodService(client); + const customers = createCustomerService(client); logger.step(1, 'Resolving customer...'); const customerId = await resolveCustomerId(customers); diff --git a/packages/api/examples/webhooks/manage-webhooks.js b/packages/payments/examples/webhooks/manage-webhooks.js similarity index 96% rename from packages/api/examples/webhooks/manage-webhooks.js rename to packages/payments/examples/webhooks/manage-webhooks.js index b02b1710..9ddb186f 100644 --- a/packages/api/examples/webhooks/manage-webhooks.js +++ b/packages/payments/examples/webhooks/manage-webhooks.js @@ -5,7 +5,7 @@ */ const { getOakClient } = require('../common/config'); -const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const { createWebhookService } = require('../../dist/index.js'); const logger = require('../common/logger'); async function main() { @@ -13,7 +13,7 @@ async function main() { try { const client = getOakClient(); - const webhooks = Crowdsplit(client).webhooks; + const webhooks = createWebhookService(client); // Step 1: List all webhooks logger.step(1, 'Listing all webhooks...'); diff --git a/packages/api/examples/webhooks/register-webhook.js b/packages/payments/examples/webhooks/register-webhook.js similarity index 95% rename from packages/api/examples/webhooks/register-webhook.js rename to packages/payments/examples/webhooks/register-webhook.js index cee6deaf..0900e770 100644 --- a/packages/api/examples/webhooks/register-webhook.js +++ b/packages/payments/examples/webhooks/register-webhook.js @@ -6,7 +6,7 @@ */ const { getOakClient } = require('../common/config'); -const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const { createWebhookService } = require('../../dist/index.js'); const logger = require('../common/logger'); async function main() { @@ -14,7 +14,7 @@ async function main() { try { const client = getOakClient(); - const webhooks = Crowdsplit(client).webhooks; + const webhooks = createWebhookService(client); // Webhook configuration const webhookData = { diff --git a/packages/payments/examples/webhooks/verify-signature.js b/packages/payments/examples/webhooks/verify-signature.js new file mode 100644 index 00000000..d574239b --- /dev/null +++ b/packages/payments/examples/webhooks/verify-signature.js @@ -0,0 +1,142 @@ +/** + * Webhook Signature Verification Example + * + * Demonstrates how to verify webhook signatures to ensure + * the payload is authentic and hasn't been tampered with. + */ + +const { + verifyWebhookSignature, + parseWebhookPayload, +} = require("../../dist/utils/webhookVerification"); +const logger = require("../common/logger"); +const crypto = require("crypto"); + +async function main() { + logger.section("Webhook Signature Verification Example"); + + // Example webhook data (simulating what Oak API would send) + const webhookSecret = "your_webhook_secret_here"; + const payload = JSON.stringify({ + event: "payment.completed", + data: { + payment_id: "pay_123456", + amount: 100.0, + currency: "USD", + customer_id: "cus_789", + status: "completed", + timestamp: new Date().toISOString(), + }, + }); + + // Generate a valid signature (Oak API would send this in the header) + const validSignature = crypto + .createHmac("sha256", webhookSecret) + .update(payload) + .digest("hex"); + + logger.step(1, "Testing valid webhook signature..."); + logger.info("Payload", JSON.parse(payload)); + logger.info( + "Signature (first 20 chars)", + validSignature.substring(0, 20) + "...", + ); + + const isValid = verifyWebhookSignature( + payload, + validSignature, + webhookSecret, + ); + + if (isValid) { + logger.success("✓ Signature is valid - webhook is authentic"); + } else { + logger.error("✗ Signature is invalid - webhook may be forged"); + } + + // Example 2: Invalid signature + logger.step(2, "Testing invalid webhook signature..."); + const invalidSignature = "invalid_signature_12345"; + + const isInvalid = verifyWebhookSignature( + payload, + invalidSignature, + webhookSecret, + ); + + if (!isInvalid) { + logger.success("✓ Correctly rejected invalid signature"); + } else { + logger.error("✗ Incorrectly accepted invalid signature"); + } + + // Example 3: Parse and verify in one step + logger.step(3, "Using parseWebhookPayload (verify + parse)..."); + + const parseResult = parseWebhookPayload( + payload, + validSignature, + webhookSecret, + ); + + if (parseResult.ok) { + logger.success("Webhook payload verified and parsed successfully"); + logger.info("Parsed event", parseResult.value); + } else { + logger.error("Failed to verify/parse webhook", parseResult.error); + } + + // Example 4: Real-world Express.js webhook endpoint + logger.section("Example: Express.js Webhook Endpoint"); + + console.log(` +const express = require('express'); +const { parseWebhookPayload } = require('@oaknetwork/payments-sdk'); + +const app = express(); + +app.post('/webhooks/oak', express.raw({ type: 'application/json' }), (req, res) => { + const signature = req.headers['x-oak-signature']; // Check actual header name + const payload = req.body.toString(); + const secret = process.env.WEBHOOK_SECRET; + + const result = parseWebhookPayload(payload, signature, secret); + + if (!result.ok) { + console.error('Invalid webhook signature'); + return res.status(401).json({ error: 'Invalid signature' }); + } + + // Handle the event + const event = result.value; + console.log('Received event:', event.event); + + switch (event.event) { + case 'payment.completed': + // Handle payment completion + break; + case 'payment.failed': + // Handle payment failure + break; + default: + console.log('Unhandled event type:', event.event); + } + + res.json({ received: true }); +}); + +app.listen(3000); + `); + + logger.section("Security Best Practices"); + console.log(" ✓ Always verify signatures before processing webhooks"); + console.log( + " ✓ Use timing-safe comparison (built into verifyWebhookSignature)", + ); + console.log(" ✓ Store webhook secrets in environment variables"); + console.log(" ✓ Use HTTPS for webhook endpoints in production"); + console.log(" ✓ Validate payload structure before using data"); + console.log(" ✓ Implement idempotency using event IDs"); +} + +main(); diff --git a/packages/api/examples/workflows/complete-payment-flow.js b/packages/payments/examples/workflows/complete-payment-flow.js similarity index 92% rename from packages/api/examples/workflows/complete-payment-flow.js rename to packages/payments/examples/workflows/complete-payment-flow.js index 9fd52b41..155ee9de 100644 --- a/packages/api/examples/workflows/complete-payment-flow.js +++ b/packages/payments/examples/workflows/complete-payment-flow.js @@ -8,7 +8,7 @@ */ const { getOakClient, resolveCustomerId } = require('../common/config'); -const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const { createCustomerService, createPaymentMethodService, createWebhookService } = require('../../dist/index.js'); const logger = require('../common/logger'); async function main() { @@ -16,7 +16,9 @@ async function main() { try { const client = getOakClient(); - const { customers, paymentMethods, webhooks } = Crowdsplit(client); + const customers = createCustomerService(client); + const paymentMethods = createPaymentMethodService(client); + const webhooks = createWebhookService(client); let customerId; diff --git a/packages/api/examples/workflows/customer-onboarding.js b/packages/payments/examples/workflows/customer-onboarding.js similarity index 89% rename from packages/api/examples/workflows/customer-onboarding.js rename to packages/payments/examples/workflows/customer-onboarding.js index 5492962d..7e42b2c7 100644 --- a/packages/api/examples/workflows/customer-onboarding.js +++ b/packages/payments/examples/workflows/customer-onboarding.js @@ -7,7 +7,7 @@ */ const { getOakClient, resolveOrCreateCustomerId } = require('../common/config'); -const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const { createCustomerService, createPaymentMethodService } = require('../../dist/index.js'); const logger = require('../common/logger'); async function main() { @@ -15,7 +15,8 @@ async function main() { try { const client = getOakClient(); - const { customers, paymentMethods } = Crowdsplit(client); + const customers = createCustomerService(client); + const paymentMethods = createPaymentMethodService(client); logger.step(1, 'Resolving or creating customer...'); const { customerId, created, email: createdEmail } = await resolveOrCreateCustomerId(customers); diff --git a/packages/api/jest.config.js b/packages/payments/jest.config.js similarity index 100% rename from packages/api/jest.config.js rename to packages/payments/jest.config.js diff --git a/packages/api/package.json b/packages/payments/package.json similarity index 82% rename from packages/api/package.json rename to packages/payments/package.json index 9f86af04..38747cb3 100644 --- a/packages/api/package.json +++ b/packages/payments/package.json @@ -1,7 +1,11 @@ { - "name": "@oaknetwork/api", - "version": "1.0.1", - "description": "TypeScript SDK for Crowdsplit API", + "name": "@oaknetwork/payments-sdk", + "version": "1.1.0", + "description": "A fully-typed TypeScript SDK for the Oak Network payment API.", + "keywords": [ + "sdk", + "oaknetwork" + ], "publishConfig": { "access": "public" }, @@ -19,10 +23,6 @@ "./services/*": { "types": "./dist/services/*.d.ts", "default": "./dist/services/*.js" - }, - "./products/crowdsplit": { - "types": "./dist/products/crowdsplit/index.d.ts", - "default": "./dist/products/crowdsplit/index.js" } }, "scripts": { @@ -49,7 +49,7 @@ "repository": { "type": "git", "url": "https://github.com/oak-network/sdk", - "directory": "packages/api" + "directory": "packages/payments" }, "files": [ "dist" diff --git a/packages/api/scripts/jest-run-exact.js b/packages/payments/scripts/jest-run-exact.js similarity index 53% rename from packages/api/scripts/jest-run-exact.js rename to packages/payments/scripts/jest-run-exact.js index 315fea8b..c3b46fe9 100644 --- a/packages/api/scripts/jest-run-exact.js +++ b/packages/payments/scripts/jest-run-exact.js @@ -4,43 +4,43 @@ * so only the exact test runs. Fixes vscode-jest running wrong tests when * similar test names exist. */ -const { spawn } = require('child_process'); +const { spawn } = require("child_process"); const args = process.argv.slice(2); function escapeRegex(str) { - return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function anchorTestPattern(arg) { const match = arg.match(/^--testNamePattern=(.+)$/); if (match) { - const value = match[1].replace(/^["']|["']$/g, ''); - if (value && !value.startsWith('^')) { + const value = match[1].replace(/^["']|["']$/g, ""); + if (value && !value.startsWith("^")) { return `--testNamePattern=^${escapeRegex(value)}$`; } } return arg; } -const tIdx = args.indexOf('-t'); +const tIdx = args.indexOf("-t"); if (tIdx >= 0 && args[tIdx + 1]) { - const value = args[tIdx + 1].replace(/^["']|["']$/g, ''); - if (value && !value.startsWith('^')) { + const value = args[tIdx + 1].replace(/^["']|["']$/g, ""); + if (value && !value.startsWith("^")) { args[tIdx + 1] = `^${escapeRegex(value)}$`; } } else { args.forEach((arg, i) => { - if (arg.startsWith('--testNamePattern=')) { + if (arg.startsWith("--testNamePattern=")) { args[i] = anchorTestPattern(arg); } }); } const child = spawn( - 'pnpm', - ['--filter', '@oaknetwork/api', 'exec', 'jest', ...args], - { stdio: 'inherit', cwd: require('path').resolve(__dirname, '../..') }, + "pnpm", + ["--filter", "@oaknetwork/payments-sdk", "exec", "jest", ...args], + { stdio: "inherit", cwd: require("path").resolve(__dirname, "../..") }, ); -child.on('exit', (code) => process.exit(code ?? 0)); +child.on("exit", (code) => process.exit(code ?? 0)); diff --git a/packages/api/src/authManager.ts b/packages/payments/src/authManager.ts similarity index 100% rename from packages/api/src/authManager.ts rename to packages/payments/src/authManager.ts diff --git a/packages/api/src/client.ts b/packages/payments/src/client.ts similarity index 100% rename from packages/api/src/client.ts rename to packages/payments/src/client.ts diff --git a/packages/api/src/decorators/index.ts b/packages/payments/src/decorators/index.ts similarity index 100% rename from packages/api/src/decorators/index.ts rename to packages/payments/src/decorators/index.ts diff --git a/packages/api/src/decorators/sandboxOnly.ts b/packages/payments/src/decorators/sandboxOnly.ts similarity index 100% rename from packages/api/src/decorators/sandboxOnly.ts rename to packages/payments/src/decorators/sandboxOnly.ts diff --git a/packages/api/src/index.ts b/packages/payments/src/index.ts similarity index 100% rename from packages/api/src/index.ts rename to packages/payments/src/index.ts diff --git a/packages/api/src/services/buyService.ts b/packages/payments/src/services/buyService.ts similarity index 100% rename from packages/api/src/services/buyService.ts rename to packages/payments/src/services/buyService.ts diff --git a/packages/api/src/services/customerService.ts b/packages/payments/src/services/customerService.ts similarity index 100% rename from packages/api/src/services/customerService.ts rename to packages/payments/src/services/customerService.ts diff --git a/packages/api/src/services/helpers.ts b/packages/payments/src/services/helpers.ts similarity index 100% rename from packages/api/src/services/helpers.ts rename to packages/payments/src/services/helpers.ts diff --git a/packages/api/src/services/index.ts b/packages/payments/src/services/index.ts similarity index 100% rename from packages/api/src/services/index.ts rename to packages/payments/src/services/index.ts diff --git a/packages/api/src/services/paymentMethodService.ts b/packages/payments/src/services/paymentMethodService.ts similarity index 100% rename from packages/api/src/services/paymentMethodService.ts rename to packages/payments/src/services/paymentMethodService.ts diff --git a/packages/api/src/services/paymentService.ts b/packages/payments/src/services/paymentService.ts similarity index 100% rename from packages/api/src/services/paymentService.ts rename to packages/payments/src/services/paymentService.ts diff --git a/packages/api/src/services/planService.ts b/packages/payments/src/services/planService.ts similarity index 100% rename from packages/api/src/services/planService.ts rename to packages/payments/src/services/planService.ts diff --git a/packages/api/src/services/providerService.ts b/packages/payments/src/services/providerService.ts similarity index 100% rename from packages/api/src/services/providerService.ts rename to packages/payments/src/services/providerService.ts diff --git a/packages/api/src/services/refundService.ts b/packages/payments/src/services/refundService.ts similarity index 100% rename from packages/api/src/services/refundService.ts rename to packages/payments/src/services/refundService.ts diff --git a/packages/api/src/services/sellService.ts b/packages/payments/src/services/sellService.ts similarity index 100% rename from packages/api/src/services/sellService.ts rename to packages/payments/src/services/sellService.ts diff --git a/packages/api/src/services/transactionService.ts b/packages/payments/src/services/transactionService.ts similarity index 100% rename from packages/api/src/services/transactionService.ts rename to packages/payments/src/services/transactionService.ts diff --git a/packages/api/src/services/transferService.ts b/packages/payments/src/services/transferService.ts similarity index 100% rename from packages/api/src/services/transferService.ts rename to packages/payments/src/services/transferService.ts diff --git a/packages/api/src/services/webhookService.ts b/packages/payments/src/services/webhookService.ts similarity index 100% rename from packages/api/src/services/webhookService.ts rename to packages/payments/src/services/webhookService.ts diff --git a/packages/api/src/types/buy.ts b/packages/payments/src/types/buy.ts similarity index 100% rename from packages/api/src/types/buy.ts rename to packages/payments/src/types/buy.ts diff --git a/packages/api/src/types/client.ts b/packages/payments/src/types/client.ts similarity index 100% rename from packages/api/src/types/client.ts rename to packages/payments/src/types/client.ts diff --git a/packages/api/src/types/common.ts b/packages/payments/src/types/common.ts similarity index 100% rename from packages/api/src/types/common.ts rename to packages/payments/src/types/common.ts diff --git a/packages/api/src/types/customer.ts b/packages/payments/src/types/customer.ts similarity index 100% rename from packages/api/src/types/customer.ts rename to packages/payments/src/types/customer.ts diff --git a/packages/api/src/types/environment.ts b/packages/payments/src/types/environment.ts similarity index 100% rename from packages/api/src/types/environment.ts rename to packages/payments/src/types/environment.ts diff --git a/packages/api/src/types/index.ts b/packages/payments/src/types/index.ts similarity index 100% rename from packages/api/src/types/index.ts rename to packages/payments/src/types/index.ts diff --git a/packages/api/src/types/payment.ts b/packages/payments/src/types/payment.ts similarity index 100% rename from packages/api/src/types/payment.ts rename to packages/payments/src/types/payment.ts diff --git a/packages/api/src/types/paymentMethod.ts b/packages/payments/src/types/paymentMethod.ts similarity index 95% rename from packages/api/src/types/paymentMethod.ts rename to packages/payments/src/types/paymentMethod.ts index 32bf7898..82041073 100644 --- a/packages/api/src/types/paymentMethod.ts +++ b/packages/payments/src/types/paymentMethod.ts @@ -21,7 +21,7 @@ export namespace PaymentMethod { metadata?: Record; } - export interface CrowdSplitBankAccount { + export interface OakBankAccount { type: string; // from SUBJECT_PAYMENT_METHOD_TYPE keys provider?: string; // from PLATFORMS keys bank_branch_code: string; @@ -77,7 +77,7 @@ export namespace PaymentMethod { metadata?: Record; } - export interface CrowdSplitCustomerWallet { + export interface OakCustomerWallet { type: string; // from SUBJECT_PAYMENT_METHOD_TYPE keys provider?: string; // from PLATFORMS keys evm_address: string; // validated as checksummed Ethereum address @@ -100,7 +100,7 @@ export namespace PaymentMethod { metadata?: Record; } - export interface CrowdSplitPix { + export interface OakPix { type: string; // from SUBJECT_PAYMENT_METHOD_TYPE keys provider?: string; // from PLATFORMS keys pix_string: string; @@ -137,14 +137,14 @@ export namespace PaymentMethod { export type Request = | BridgeBankAccount - | CrowdSplitBankAccount + | OakBankAccount | StripeBankAccount | MercadoPagoCard | PagarMeCard | StripeCard - | CrowdSplitCustomerWallet + | OakCustomerWallet | BridgeLiquidationAddress - | CrowdSplitPix + | OakPix | BridgePlaid | BridgeVirtualAccount; diff --git a/packages/api/src/types/plan.ts b/packages/payments/src/types/plan.ts similarity index 100% rename from packages/api/src/types/plan.ts rename to packages/payments/src/types/plan.ts diff --git a/packages/api/src/types/provider.ts b/packages/payments/src/types/provider.ts similarity index 100% rename from packages/api/src/types/provider.ts rename to packages/payments/src/types/provider.ts diff --git a/packages/api/src/types/refund.ts b/packages/payments/src/types/refund.ts similarity index 100% rename from packages/api/src/types/refund.ts rename to packages/payments/src/types/refund.ts diff --git a/packages/api/src/types/result.ts b/packages/payments/src/types/result.ts similarity index 100% rename from packages/api/src/types/result.ts rename to packages/payments/src/types/result.ts diff --git a/packages/api/src/types/sell.ts b/packages/payments/src/types/sell.ts similarity index 100% rename from packages/api/src/types/sell.ts rename to packages/payments/src/types/sell.ts diff --git a/packages/api/src/types/token.ts b/packages/payments/src/types/token.ts similarity index 100% rename from packages/api/src/types/token.ts rename to packages/payments/src/types/token.ts diff --git a/packages/api/src/types/transactions.ts b/packages/payments/src/types/transactions.ts similarity index 100% rename from packages/api/src/types/transactions.ts rename to packages/payments/src/types/transactions.ts diff --git a/packages/api/src/types/transfer.ts b/packages/payments/src/types/transfer.ts similarity index 100% rename from packages/api/src/types/transfer.ts rename to packages/payments/src/types/transfer.ts diff --git a/packages/api/src/types/webhook.ts b/packages/payments/src/types/webhook.ts similarity index 100% rename from packages/api/src/types/webhook.ts rename to packages/payments/src/types/webhook.ts diff --git a/packages/api/src/utils/buildUrl.ts b/packages/payments/src/utils/buildUrl.ts similarity index 100% rename from packages/api/src/utils/buildUrl.ts rename to packages/payments/src/utils/buildUrl.ts diff --git a/packages/api/src/utils/defaultRetryConfig.ts b/packages/payments/src/utils/defaultRetryConfig.ts similarity index 100% rename from packages/api/src/utils/defaultRetryConfig.ts rename to packages/payments/src/utils/defaultRetryConfig.ts diff --git a/packages/api/src/utils/errorHandler.ts b/packages/payments/src/utils/errorHandler.ts similarity index 100% rename from packages/api/src/utils/errorHandler.ts rename to packages/payments/src/utils/errorHandler.ts diff --git a/packages/api/src/utils/httpClient.ts b/packages/payments/src/utils/httpClient.ts similarity index 100% rename from packages/api/src/utils/httpClient.ts rename to packages/payments/src/utils/httpClient.ts diff --git a/packages/api/src/utils/index.ts b/packages/payments/src/utils/index.ts similarity index 100% rename from packages/api/src/utils/index.ts rename to packages/payments/src/utils/index.ts diff --git a/packages/api/src/utils/retryHandler.ts b/packages/payments/src/utils/retryHandler.ts similarity index 100% rename from packages/api/src/utils/retryHandler.ts rename to packages/payments/src/utils/retryHandler.ts diff --git a/packages/api/src/utils/webhookVerification.ts b/packages/payments/src/utils/webhookVerification.ts similarity index 100% rename from packages/api/src/utils/webhookVerification.ts rename to packages/payments/src/utils/webhookVerification.ts diff --git a/packages/api/src/utils/withAuth.ts b/packages/payments/src/utils/withAuth.ts similarity index 100% rename from packages/api/src/utils/withAuth.ts rename to packages/payments/src/utils/withAuth.ts diff --git a/packages/api/tsconfig.build.json b/packages/payments/tsconfig.build.json similarity index 100% rename from packages/api/tsconfig.build.json rename to packages/payments/tsconfig.build.json diff --git a/packages/api/tsconfig.json b/packages/payments/tsconfig.json similarity index 100% rename from packages/api/tsconfig.json rename to packages/payments/tsconfig.json diff --git a/packages/api/tsconfig.test.json b/packages/payments/tsconfig.test.json similarity index 100% rename from packages/api/tsconfig.test.json rename to packages/payments/tsconfig.test.json diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1d187cfb..94a164c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,7 +19,7 @@ importers: specifier: ^30.0.0 version: 30.0.0 - packages/api: + packages/payments: devDependencies: '@types/jest': specifier: ^30.0.0