# Commerce7 API Client

A clean, simple, and type-safe JavaScript/TypeScript client for the [Commerce7 REST API](https://developer.commerce7.com/docs/commerce7-developer-docs).

## Features

- ✅ **Type-Safe**: Full TypeScript support with type definitions
- ✅ **Simple API**: Clean, intuitive interface
- ✅ **Automatic Authentication**: Handles Basic Auth and tenant headers
- ✅ **Built-in Pagination**: Automatic cursor-based pagination
- ✅ **Rate Limiting**: Respects `Retry-After` headers
- ✅ **Error Handling**: Comprehensive error handling with typed errors
- ✅ **Node-RED Support**: Includes Node-RED nodes for easy integration
- ✅ **Zero Dependencies**: Only uses `axios` (peer dependency)

## Installation

### As a Node.js Package

```bash
npm install commerce7-api-client
```

### As a Node-RED Package

Install via Node-RED's palette manager or manually:

```bash
cd ~/.node-red
npm install node-red-contrib-commerce7-api-client
```

Then restart Node-RED. You'll find two new nodes:
- **commerce7 config**: Configuration node for API credentials
- **commerce7 request**: Node for making API requests

## Quick Start

```typescript
import { createClient } from 'commerce7-api-client';

// Create a client
const client = createClient({
  appId: 'your-app-id',
  appSecret: 'your-app-secret',
  tenant: 'your-tenant-id'
});

// Make a request
const response = await client.get('/customer', { limit: 10 });
console.log(response.data);

// Paginate through results
const allMemberships = await client.paginate('/club-membership', {
  updatedAt: 'gte:2024-01-01'
}, {
  recordTag: 'clubMemberships'
});
```

## API Reference

### Creating a Client

```typescript
import { Commerce7Client, createClient } from 'commerce7-api-client';

// Using factory function
const client = createClient({
  appId: 'your-app-id',
  appSecret: 'your-app-secret',
  tenant: 'your-tenant-id',
  baseURL: 'https://api.commerce7.com/v1', // optional
  timeout: 30000, // optional, default 30s
  maxRetries: 3 // optional, default 3
});

// Or using class directly
const client = new Commerce7Client({
  appId: 'your-app-id',
  appSecret: 'your-app-secret',
  tenant: 'your-tenant-id'
});
```

### Making Requests

#### GET Request

```typescript
// Simple GET
const response = await client.get('/customer/{id}');

// GET with query parameters
const response = await client.get('/customer', {
  q: 'john',
  lifetimeValue: 'gt:100000',
  orderCount: 'gte:2'
});
```

#### POST Request

```typescript
const customer = await client.post('/customer', {
  firstName: 'John',
  lastName: 'Doe',
  emails: [{ email: 'john@example.com' }]
});
```

#### PUT Request

```typescript
const updated = await client.put('/customer/{id}', {
  firstName: 'Jane'
});
```

#### PATCH Request

```typescript
const patched = await client.patch('/customer/{id}', {
  metaData: { customField: 'value' }
});
```

#### DELETE Request

```typescript
await client.delete('/customer/{id}');
```

### Pagination

#### Automatic Pagination (Collect All)

```typescript
// Collects all pages automatically
const allMemberships = await client.paginate('/club-membership', {
  updatedAt: 'gte:2024-01-01'
}, {
  recordTag: 'clubMemberships', // Field containing records
  maxPages: 1000 // Safety limit
});

console.log(`Found ${allMemberships.length} memberships`);
```

#### Pagination with Callback

```typescript
// Process pages as they arrive
const allRecords = await client.paginate('/club-membership', {
  updatedAt: 'gte:2024-01-01'
}, {
  recordTag: 'clubMemberships',
  onPage: (page, pageNumber) => {
    console.log(`Processing page ${pageNumber}...`);
    // Process page.data here
  }
});
```

### Multi-Tenant Support

```typescript
// Create client for tenant 1
const client1 = createClient({
  appId: 'app-id',
  appSecret: 'secret',
  tenant: 'tenant-1'
});

// Create client for tenant 2 (reuses config)
const client2 = client1.withTenant('tenant-2');

// Both clients share the same app credentials
await client1.get('/customer');
await client2.get('/customer');
```

### Error Handling

```typescript
import { Commerce7Error } from 'commerce7-api-client';

try {
  const response = await client.get('/customer/invalid-id');
} catch (error) {
  if (error instanceof Commerce7Error) {
    console.error(`API Error ${error.statusCode}:`, error.message);
    console.error('Response:', error.responseData);
  } else {
    console.error('Network error:', error.message);
  }
}
```

## Examples

### Get Customer by ID

```typescript
const response = await client.get('/customer/a36b6ff1-7190-49a0-895e-fd2c001eb2a6');
const customer = response.data;
console.log(customer.firstName, customer.lastName);
```

### Search Customers

```typescript
const response = await client.get('/customer', {
  q: 'john',
  lifetimeValue: 'gt:100000',
  orderCount: 'gte:2',
  createdAt: 'gte:2024-01-01'
});

const customers = response.data.customers;
console.log(`Found ${response.data.totalCount} customers`);
```

### Create Customer

```typescript
const response = await client.post('/customer', {
  firstName: 'John',
  lastName: 'Doe',
  emails: [{ email: 'john@example.com' }],
  phones: [{ phone: '+15551234567' }],
  isSendTransactionEmail: false
});

const newCustomer = response.data;
console.log('Created customer:', newCustomer.id);
```

### Update Customer

```typescript
const response = await client.put('/customer/{id}', {
  firstName: 'Jane',
  metaData: {
    customField: 'value'
  }
});
```

### List Club Memberships with Pagination

```typescript
// Get all active memberships
const memberships = await client.paginate('/club-membership', {
  status: 'Active',
  updatedAt: 'gte:2024-01-01'
}, {
  recordTag: 'clubMemberships'
});

console.log(`Total memberships: ${memberships.length}`);
```

### Create Club Membership

```typescript
// 1. Create customer
const customer = await client.post('/customer', {
  firstName: 'John',
  lastName: 'Doe',
  emails: [{ email: 'john@example.com' }]
});

// 2. Create address
const address = await client.post('/customer-address', {
  customerId: customer.data.id,
  address1: '123 Main St',
  city: 'Napa',
  stateCode: 'CA',
  zipCode: '94558',
  countryCode: 'US'
});

// 3. Create credit card
const card = await client.post('/customer-credit-card', {
  customerId: customer.data.id,
  // ... card details
});

// 4. Create membership
const membership = await client.post('/club-membership', {
  customerId: customer.data.id,
  clubId: 'club-uuid',
  billToCustomerAddressId: address.data.id,
  shipToCustomerAddressId: address.data.id,
  customerCreditCardId: card.data.id,
  orderDeliveryMethod: 'Ship',
  signupDate: new Date().toISOString(),
  isSendEmailConfirmation: false
});
```

## Node-RED Usage

This package includes two Node-RED nodes for easy integration with Commerce7 API:

- **commerce7 config**: Configuration node for storing API credentials
- **commerce7 request**: Request node for making API calls

### Installation

Install via Node-RED's palette manager:
1. Open Node-RED
2. Go to **Menu** → **Manage palette**
3. Click **Install** tab
4. Search for `node-red-contrib-commerce7-api-client`
5. Click **Install**

Or install manually:
```bash
cd ~/.node-red
npm install node-red-contrib-commerce7-api-client
```

Then restart Node-RED.

### Setup

1. **Create a Config Node**
   - Drag a **commerce7 config** node onto your flow (found in the `config` category)
   - Double-click to configure:
     - **Name**: Give it a descriptive name (e.g., "Commerce7 Production")
     - **App ID**: Your Commerce7 application ID
     - **App Secret**: Your Commerce7 application secret (stored securely)
     - **Base URL**: API base URL (default: `https://api.commerce7.com/v1`)
     - **Timeout**: Request timeout in milliseconds (default: `30000`)
   - Click **Done**

2. **Create a Request Node**
   - Drag a **commerce7 request** node onto your flow (found in the `commerce7` category)
   - Double-click to configure:
     - **Name**: Optional node name
     - **Config**: Select your commerce7 config node (or click edit button to create/edit)
     - **Tenant**: The Commerce7 tenant identifier
     - **Endpoint**: Select from dropdown or enter custom endpoint (e.g., `customer`, `club-membership`)
     - **Method**: HTTP method (GET, POST, PUT, PATCH, DELETE)
     - **Query Params**: 
       - **None**: No query parameters
       - **JSON**: Enter query parameters as JSON object (e.g., `{"limit": 10, "q": "search"}`)
       - **msg.params**: Use `msg.params` from the incoming message
     - **Body**: 
       - **None**: No request body
       - **JSON**: Enter request body as JSON object
       - **msg.payload**: Use `msg.payload` from the incoming message
   - Click **Done**

### Available Endpoints

The request node includes a dropdown with common Commerce7 API endpoints:
- `customer` - Customer management
- `customer-address` - Customer addresses
- `customer-credit-card` - Customer payment methods
- `club` - Wine clubs
- `club-membership` - Club memberships
- `order` - Orders
- `product` - Products
- `collection` - Product collections
- `department` - Departments
- `coupon` - Coupons
- `promotion` - Promotions
- `gift-card` - Gift cards
- `shipping` - Shipping methods
- `tax` - Tax configurations
- `vendor` - Vendors
- `reservation` - Reservations
- `reservation-type` - Reservation types
- `note` - Notes
- `tag` - Tags
- `webhook` - Webhooks
- `app` - Apps
- **Custom...** - Enter any custom endpoint path

### Output

The request node has two outputs:
- **First output (top)**: Success responses
  - `msg.payload`: API response data
  - `msg.statusCode`: HTTP status code
  - `msg.headers`: Response headers
  - `msg.pagination`: Pagination information (cursor, totalCount) if available

- **Second output (bottom)**: Error responses
  - `msg.payload`: Error response data
  - `msg.statusCode`: HTTP status code
  - `msg.error`: Error object with details

### Dynamic Configuration

All request parameters can be set dynamically via message properties, which take priority over node configuration:

```javascript
// In a function node before the commerce7 request node
msg.tenant = "my-tenant-id";           // Override tenant
msg.endpoint = "/customer";            // Override endpoint
msg.method = "GET";                    // Override method
msg.params = { limit: 10, q: "john" }; // Query parameters (or use msg.query)
msg.body = { firstName: "John" };      // Request body (for POST/PUT/PATCH)
return msg;
```

**Note**: For query parameters, you can use either `msg.params` or `msg.query`. For request body, you can use `msg.body` or `msg.payload` (if body type is set to "msg").

### Example Flows

#### Get Customer List

```
[inject] -> [commerce7 request] -> [debug]
```

**commerce7 request** configuration:
- Config: Select your config node
- Tenant: `my-tenant-id`
- Endpoint: `customer`
- Method: `GET`
- Query Params: JSON → `{"limit": 10}`

#### Search Customers Dynamically

```
[inject] -> [function] -> [commerce7 request] -> [debug]
```

**function** node code:
```javascript
msg.tenant = "my-tenant-id";
msg.endpoint = "/customer";
msg.method = "GET";
msg.params = {
    q: "john",
    lifetimeValue: "gt:100000",
    orderCount: "gte:2"
};
return msg;
```

#### Create Customer

```
[inject] -> [function] -> [commerce7 request] -> [debug]
```

**function** node code:
```javascript
msg.tenant = "my-tenant-id";
msg.endpoint = "/customer";
msg.method = "POST";
msg.body = {
    firstName: "John",
    lastName: "Doe",
    emails: [{ email: "john@example.com" }]
};
return msg;
```

#### Handle Errors

```
[inject] -> [commerce7 request] -> [switch]
                                    ├─ [success handler] (output 1)
                                    └─ [error handler] (output 2)
```

**switch** node configuration:
- Property: `msg.statusCode`
- Rules:
  - `200-299` → Route to success handler
  - `400-599` → Route to error handler

### Multi-Tenant Support

A single config node can be used with multiple tenants. Simply set the `tenant` parameter differently in each request node, or use `msg.tenant` to switch tenants dynamically.

## TypeScript Support

The package includes full TypeScript definitions:

```typescript
import { Commerce7Client, Customer, ClubMembership } from 'commerce7-api-client';

const client = createClient({ /* config */ });

// Typed responses
const response = await client.get<Customer>('/customer/{id}');
const customer: Customer = response.data;

// Typed pagination
const memberships = await client.paginate<ClubMembership>('/club-membership', {}, {
  recordTag: 'clubMemberships'
});
```

## Configuration Options

```typescript
interface Commerce7Config {
  appId: string;              // Required: Your Commerce7 App ID
  appSecret: string;          // Required: Your Commerce7 App Secret
  tenant: string;            // Required: Tenant identifier
  baseURL?: string;          // Optional: API base URL (default: https://api.commerce7.com/v1)
  timeout?: number;          // Optional: Request timeout in ms (default: 30000)
  maxRetries?: number;       // Optional: Max retry attempts (default: 3)
}
```

## Error Types

### Commerce7Error

Thrown for API errors (4xx, 5xx):

```typescript
class Commerce7Error extends Error {
  statusCode: number;
  responseData?: any;
  responseHeaders?: Record<string, string>;
}
```

## Rate Limiting

The client automatically handles rate limiting:

- Detects `429 Too Many Requests` responses
- Checks for `Retry-After` header
- Automatically retries after the specified delay
- No manual retry logic needed

## Browser Support

This package is designed for Node.js environments. For browser use, you'll need to:

1. Use a bundler (webpack, rollup, etc.)
2. Provide polyfills for Node.js built-ins (Buffer, etc.)
3. Consider CORS restrictions

## License

MIT

## Links

- [Commerce7 Developer Documentation](https://developer.commerce7.com/docs/commerce7-developer-docs)
- [Commerce7 API Overview](https://developer.commerce7.com/docs/commerce7-developer-docs/api-overview)
- [App Partner Program](https://commerce7.com/partners/become-a-partner/app/)


