# Merchant Settings

Configure general merchant account settings: business profile, contact details, notification recipients and notification preferences.

All endpoints on this page are admin/portal endpoints. Authenticate with `Authorization: Bearer <JWT>`; the tenant is taken from the token. Merchant users can only read and update their own merchant (`403 FORBIDDEN` otherwise); tenant admins can access any merchant in the tenant.

> **Note:** There is no separate `/settings` resource. General settings are fields on the merchant record itself, read with `GET /api/v1/merchants/:merchantId` and changed with `PUT /api/v1/merchants/:merchantId`.

---

## Get Settings

```http
GET /api/v1/merchants/:merchantId
```

```bash
curl https://uat.auxcore.net/api/v1/merchants/8f3c2a1e-5b7d-4e9a-9c1f-2d6e8a4b7c30 \
  -H "Authorization: Bearer YOUR_TOKEN"
```

**Response (excerpt):** the full merchant record is returned; the settings fields are shown below.

```json
{
  "success": true,
  "data": {
    "id": "8f3c2a1e-5b7d-4e9a-9c1f-2d6e8a4b7c30",
    "companyName": "ACME Corp",
    "businessLegalName": "ACME Corporation LLC",
    "dbaName": "ACME",
    "contactFirstName": "Jane",
    "contactLastName": "Doe",
    "contactEmail": "contact@acme.example",
    "phoneNumber": "+15125550100",
    "notificationEmails": ["notifications@acme.example", "ops@acme.example"],
    "timezone": "Eastern",
    "address": "100 Main St",
    "city": "Austin",
    "state": "TX",
    "postalCode": "78701",
    "country": "US",
    "dynamicDescriptor": "ACME CORP",
    "convenienceFeeActive": false,
    "convenienceFeeValue": 0,
    "convenienceFeeType": "Fixed",
    "convenienceFeeMinimum": 0,
    "convenienceFeeMaximum": null,
    "serviceFeePaidBy": "customer"
  }
}
```

---

## Update Settings

```http
PUT /api/v1/merchants/:merchantId
```

Partial update: only the fields you send are changed.

```bash
curl -X PUT https://uat.auxcore.net/api/v1/merchants/8f3c2a1e-5b7d-4e9a-9c1f-2d6e8a4b7c30 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "companyName": "ACME Corporation",
    "timezone": "America/Los_Angeles",
    "notificationEmails": ["admin@acme.example", "billing@acme.example"]
  }'
```

**Response:** `200 OK` with the full updated merchant record.

```json
{
  "success": true,
  "data": {
    "id": "8f3c2a1e-5b7d-4e9a-9c1f-2d6e8a4b7c30",
    "companyName": "ACME Corporation",
    "timezone": "Pacific",
    "notificationEmails": ["admin@acme.example", "billing@acme.example"],
    "updatedAt": "2026-01-28T12:00:00.000Z"
  }
}
```

**Errors:**

| Status | Code | When |
|--------|------|------|
| `400` | `VALIDATION_ERROR` | Invalid `contactEmail`, invalid `notificationEmails`, invalid state/country pair, invalid policy URL |
| `403` | `FORBIDDEN` | A merchant user updating another merchant |
| `404` | `NOT_FOUND` | Merchant does not exist in this tenant |

---

## Available Settings

| Setting | Type | Description |
|---------|------|-------------|
| `companyName` | string | Business name shown across the platform |
| `businessLegalName` | string | Legal business name |
| `dbaName` | string | Doing-business-as name |
| `contactFirstName` / `contactLastName` | string | Primary contact name |
| `contactEmail` | email | Primary contact email. Must be a valid email address. |
| `phoneNumber` | string | Primary contact phone. `contactPhone` is accepted as an alias on update. |
| `notificationEmails` | email[] \| null | Notification recipients. See the next section. |
| `timezone` | string | One of `Hawaii`, `Pacific`, `Mountain`, `Central`, `Eastern`. US IANA names such as `America/New_York` or `America/Los_Angeles` are accepted and converted. |
| `address`, `city`, `state`, `postalCode`, `country` | string | Business address. `state` must be valid for `country`. |
| `dynamicDescriptor` | string | Statement descriptor (max 255 chars). Also copied to the primary gateway if that gateway has none. |
| `website`, `customerServicePhone`, `businessType` | string | Stored in the merchant's `metadata` |
| `termsOfUseUrl`, `privacyPolicyUrl`, `returnPolicyUrl`, `cancellationPolicyUrl`, `shippingPolicyUrl` | url \| null | Customer-facing policy links. Must be `http(s)`. `null` or `""` clears the link. The matching `*Status` flag is derived and cannot be set directly. |
| `requirePolicyAcknowledgment` | boolean | Hosted checkout asks the payer to accept the policies before paying |
| `convenienceFeeActive`, `convenienceFeeValue`, `convenienceFeeType`, `convenienceFeeMinimum`, `convenienceFeeMaximum`, `serviceFeePaidBy` | mixed | Convenience fee on the primary gateway. `convenienceFeeMaximum` of `0` or empty removes the cap. `serviceFeePaidBy` is `customer` or `merchant`. |

---

## Notification Recipients

`notificationEmails` holds up to **10** addresses. Transaction alerts, dispute alerts, chargeback alerts and the daily summary are sent to **every** address in the list.

- Values are trimmed and de-duplicated; empty strings are ignored.
- More than 10 addresses (`At most 10 notification emails are allowed`) or any malformed address (`Invalid notification email(s): bad@`) returns `400 VALIDATION_ERROR`.
- A non-array value returns `400` (`notificationEmails must be an array`).
- `null` or `[]` clears the list. Notifications then fall back to `contactEmail`.
- Placeholder addresses (such as `example.com`, `*.local`, `*.invalid`) are skipped at send time.

> The legacy single-value field `notificationEmail` was removed and is ignored if sent. Use `notificationEmails`.

```json
{
  "notificationEmails": ["owner@acme.example", "accounting@acme.example"]
}
```

---

## Get Notification Preferences

```http
GET /api/v1/merchants/:merchantId/notification-preferences
```

Returns which channels each notification type is sent on. Unsaved values come back as the defaults shown below.

**Response:**

```json
{
  "success": true,
  "data": {
    "transactionAlerts": { "email": true, "sms": false, "inApp": true },
    "dailySummary": {
      "email": true,
      "sms": false,
      "inApp": false,
      "deliveryTime": { "hour": 8, "minute": 0, "period": "AM" }
    },
    "disputeAlerts": { "email": true, "sms": false, "inApp": true }
  }
}
```

---

## Update Notification Preferences

```http
PUT /api/v1/merchants/:merchantId/notification-preferences
```

Send any subset of `transactionAlerts`, `dailySummary`, `disputeAlerts`. Each object is merged onto the stored value.

```bash
curl -X PUT https://uat.auxcore.net/api/v1/merchants/8f3c2a1e-5b7d-4e9a-9c1f-2d6e8a4b7c30/notification-preferences \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionAlerts": { "email": true, "inApp": false },
    "dailySummary": { "email": true, "deliveryTime": { "hour": 6, "minute": 30, "period": "PM" } }
  }'
```

**Response:** `200 OK` with the full saved preferences and `"message": "Notification preferences saved"`.

**Validation:**

- `email`, `sms`, `inApp` must be booleans.
- `dailySummary` is email-only: sending `sms: true` or `inApp: true` for it returns `400` (`Daily Summary only supports email`); both are stored as `false`.
- `deliveryTime.hour` is `1`–`12`, `deliveryTime.minute` is a multiple of 5 (`0`–`55`), `deliveryTime.period` is `AM` or `PM`. Anything else returns `400`.

---

## Best Practices

- **Keep contact info current** - Ensures important notifications reach you
- **Add more than one notification recipient** - Alerts still reach the team when someone is away
- **Set correct timezone** - Affects the daily summary and settlement schedules
- **Use clear statement descriptor** - Reduces chargebacks

---

**Need help?** Contact support@auxcore.net
