npm package: @sathish_dev/ttc-cli
Description: Terminal Tester CLI for Surfboard payments
Author: Sathish889
Latest version: 1.0.0

# TTC — Terminal Testing CLI

Replace the Terminal Tester mobile app with a single command-line tool. No phone, no tapping through screens — just three commands.

```
Mobile App (12 steps):              TTC (3 commands):
────────────────────                ─────────────────
1. Pick up phone                    1. ttc register ...
2. Open app                         2. ttc use <terminalId>
3. Register terminal                3. ttc pay --card sb_visa_approved
4. Enter merchant ID
5. Enter store ID                   Done.
6. Wait for code
7. Create order
8. Enter amount
9. Select card
10. Wait for result
11. Write down result
12. Repeat...
```

---

## Requirements

- [Node.js](https://nodejs.org) v18 or higher
- npm (comes with Node)

Check you have them:

```bash
node --version   # should print v18.x.x or higher
npm --version    # should print 9.x.x or higher
```

---

## Setup (Run Once)

Open a terminal, navigate to the project folder, and run:

```bash
cd /path/to/ttc

npm install       # install dependencies
npm run build     # compile TypeScript → dist/
npm link          # make 'ttc' available everywhere
```

Verify it worked — from **any folder**:

```bash
ttc --version
```

```
1.0.0
```

That's it. You never need to be in the project folder again.

---

## Where to Run Commands

After `npm link`, run `ttc` from **any folder** in your terminal:

```bash
# Your Desktop
cd ~/Desktop
ttc pay --card sb_visa_approved

# Any project folder
cd ~/my-project
ttc register -e beta -m abc -s xyz

# Home directory
cd ~
ttc status
```

You do **not** need to be inside the `ttc/` project folder to run commands.

---

## Quick Start

```bash
# 1. Register a terminal (one time per environment)
ttc register --env beta --merchant-id <merchantId> --store-id <storeId>

# 2. Set it as active
ttc use <terminalId>

# 3. Run payments
ttc pay --card sb_visa_approved
ttc pay --card sb_visa_fin_1     # financial decline
ttc pay --card sb_mc_approved
```

---

## The 3-Step Flow

### Step 1 — Register

Register a terminal and get a Terminal ID. You only do this once per environment.

```bash
ttc register --env beta --merchant-id 824c5733809940050f --store-id 824c5733809940050f
```

```
[ttc] registering terminal on beta...
[ttc] ✓ code generated (expires in 90s)
[ttc] ✓ terminal registered

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  TERMINAL ID:  83facf1d2247580204
  Environment:  beta
  Store:        824c5733809940050f
  Status:       ACTIVE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Next step:
  ttc use 83facf1d2247580204
```

TTC generates the 6-digit registration code automatically — you never need to see it or handle it.

**Flags:**

| Flag            | Short | Required | Description         |
|-----------------|-------|----------|---------------------|
| `--env`         | `-e`  | Yes      | `beta` or `prod`    |
| `--merchant-id` | `-m`  | Yes      | Merchant identifier |
| `--store-id`    | `-s`  | Yes      | Store identifier    |

Short flag form:

```bash
ttc register -e beta -m 824c5733809940050f -s 824c5733809940050f
```

---

### Step 2 — Use

Set a terminal as active and load its credentials. Run this after registering, or any time you want to switch terminals.

```bash
ttc use 83facf1d2247580204
```

```
[ttc] loading terminal 83facf1d2247580204...
[ttc] ✓ credentials loaded (service account)
[ttc] ✓ terminal set as active

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  Active Terminal:  83facf1d2247580204
  Environment:      beta
  Merchant:         824c5733809940050f
  Store:            824c5733809940050f
  Auth:             Service Account ✓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Ready. Run:
  ttc pay --card sb_visa_approved
```

From this point, all `ttc pay` commands use this terminal automatically.

---

### Step 3 — Pay

Create an order and process a card payment. Repeat as many times as needed — each run uses a random amount so every transaction is unique.

```bash
ttc pay --card sb_visa_approved
```

```
[ttc] using terminal 83facf1d2247580204
[ttc] creating order — amount: 4829 SEK...
[ttc]   ✓ order: 83fad0180227d0790b
[ttc]   ✓ payment: 83fad019172fb00406
[ttc] connecting to AGW...
[ttc] processing payment — sb_visa_approved (400000******0000)...
[ttc]   ✓ APPROVED — auth: 123456

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  RESULT:      APPROVED
  Order:       83fad0180227d0790b
  Payment ID:  83fad019172fb00406
  Card:        sb_visa_approved (400000******0000)
  Amount:      48.29 SEK
  Auth Code:   123456
  Response:    00
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```

**Flags:**

| Flag        | Short | Required | Default     | Description                                       |
|-------------|-------|----------|-------------|---------------------------------------------------|
| `--card`    | `-k`  | Yes      | —           | Card key (e.g. `sb_visa_approved`) or number (`01`) |
| `--amount`  | `-a`  | No       | random      | Amount in minor units (e.g. `5000` = 50.00 SEK)  |
| `--currency`|       | No       | `752` (SEK) | ISO 4217 numeric currency code                   |
| `--timeout` |       | No       | `30`        | AGW response timeout in seconds                  |
| `--verbose` | `-v`  | No       | off         | Show full HTTP/WebSocket details                  |
| `--json`    | `-j`  | No       | off         | Output as JSON                                    |
| `--mock`    |       | No       | off         | Use mock mode (skip real AGW)                     |

---

## Test Cards

TTC includes the full Surfboard test card catalog — 34 cards across 5 categories. These are the same cards used in the Terminal Tester mobile app.

> **Only Surfboard cards (`sb_*`) work with a real terminal.** Generic cards are blocked in real mode.

### View all cards

```bash
ttc cards list
```

### Categories

| Category | Keys | Expected |
|----------|------|----------|
| Approved | `sb_visa_approved` (×6), `sb_mc_approved` (×6) | APPROVED |
| Financial Issues | `sb_visa_fin_1–4`, `sb_mc_fin_1–3` | DECLINED |
| Card & Account Issues | `sb_visa_acc_1–3`, `sb_mc_acc_1–3` | DECLINED |
| Data & Processing Errors | `sb_visa_data_1–3`, `sb_mc_data_1–3` | DECLINED |
| Technical & Security | `sb_visa_tech_1–2`, `sb_mc_tech_1` | DECLINED |

### Pay by number

Cards are numbered 01–34 in display order:

```bash
ttc pay --card 01     # → sb_visa_approved (Visa — Approved)
ttc pay --card 07     # → sb_mc_approved (Mastercard — Approved)
ttc pay --card 13     # → sb_visa_fin_1 (Visa — Financial Decline)
```

### Pay by key

```bash
ttc pay --card sb_visa_approved
ttc pay --card sb_mc_fin_1
ttc pay --card sb_visa_acc_1
```

---

## All Commands

### `ttc terminals get` — List Terminals

See all terminals registered for an environment.

```bash
ttc terminals get --env beta
# or short form:
ttc terminals get -e beta
```

```
ENVIRONMENT: beta

ID                     STATUS    STORE                REGISTERED
─────────────────────────────────────────────────────────────────
83facf1d2247580204     ACTIVE    824c573380994...     2026-03-30
84abc123d456789e       ACTIVE    3d8e6f4a2b01...      2026-03-28
85def456e789012f       EXPIRED   824c573380994...     2026-03-15

Active: 83facf1d2247580204 ←
```

---

### `ttc transaction get` — View Transaction Details

After a payment, use the Payment ID from the result to fetch full transaction data.

```bash
ttc transaction get 83fad019172fb00406
```

```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  TRANSACTION DETAILS
  Payment ID:   83fad019172fb00406
  Order ID:     83fad0180227d0790b
  Terminal:     83facf1d2247580204
  Status:       APPROVED
  Card:         VISA 400000******0000
  Amount:       48.29 SEK
  Auth Code:    123456
  Response:     00
  Timestamp:    2026-03-30T10:32:15Z
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```

---

### `ttc status` — Show Active Terminal

```bash
ttc status
```

```
[ttc] status:

  Active Terminal:  83facf1d2247580204
  Environment:      beta
  Merchant:         824c5733809940050f
  Store:            824c5733809940050f
  Registered:       2026-03-30 10:30 AM
  Auth:             Service Account ✓
  Token Expires:    2026-03-30 11:30 AM (48 min remaining)
```

---

### `ttc reset` — Clear All State

Wipes all saved terminals and credentials. Useful when starting fresh.

```bash
ttc reset
```

---

## All Flags Reference

| Flag            | Short | Used In                  | Required | Default  | Description                 |
|-----------------|-------|--------------------------|----------|----------|-----------------------------|
| `--env`         | `-e`  | `register`, `terminals`  | Yes      | —        | `beta` or `prod`            |
| `--merchant-id` | `-m`  | `register`               | Yes      | —        | Merchant identifier         |
| `--store-id`    | `-s`  | `register`               | Yes      | —        | Store identifier            |
| `--card`        | `-k`  | `pay`                    | Yes      | —        | Card key or number (01–34)  |
| `--amount`      | `-a`  | `pay`                    | No       | random   | Amount in minor units       |
| `--currency`    |       | `pay`                    | No       | `752`    | ISO 4217 code (SEK)         |
| `--timeout`     |       | `pay`                    | No       | `30`     | AGW timeout in seconds      |
| `--mock`        |       | `pay`, `test`            | No       | off      | Skip real AGW               |
| `--verbose`     | `-v`  | any                      | No       | off      | Show HTTP/WS details        |
| `--json`        | `-j`  | any                      | No       | off      | JSON output                 |
| `--version`     | `-V`  | global                   | —        | —        | Print version               |
| `--help`        |       | global                   | —        | —        | Print help                  |

---

## Complete Flows

### Flow 1: First-Time Setup + Payment

```bash
# Check version
ttc --version

# List existing terminals (probably empty first time)
ttc terminals get --env beta

# Register a terminal
ttc register -e beta -m 824c5733809940050f -s 824c5733809940050f
# → TERMINAL ID: 83facf1d2247580204

# Set it as active
ttc use 83facf1d2247580204

# Run a payment
ttc pay --card sb_visa_approved
# → APPROVED — auth: 123456
# → Payment ID: 83fad019172fb00406

# Check the transaction
ttc transaction get 83fad019172fb00406
```

---

### Flow 2: Repeat Payments (After Setup)

After setup, you only need one command per payment:

```bash
ttc pay --card sb_visa_approved      # → APPROVED
ttc pay --card sb_visa_fin_1         # → DECLINED (insufficient funds)
ttc pay --card sb_mc_approved        # → APPROVED
ttc pay --card sb_visa_acc_1         # → DECLINED (card/account issue)
ttc pay --card sb_mc_tech_1          # → DECLINED (technical/security)

# Pay by number (same cards, shorter syntax)
ttc pay --card 01   # → sb_visa_approved
ttc pay --card 13   # → sb_visa_fin_1
```

Each run generates a random amount automatically. Override with `--amount`:

```bash
ttc pay --card sb_visa_approved --amount 15000   # 150.00 SEK
ttc pay --card sb_mc_approved -a 99900           # 999.00 SEK
```

---

### Flow 3: JSON Output (for scripts and CI)

```bash
ttc pay --card sb_visa_approved --json
```

```json
{
  "command": "pay",
  "success": true,
  "terminalId": "83facf1d2247580204",
  "orderId": "83fad0180227d0790b",
  "paymentId": "83fad019172fb00406",
  "card": "sb_visa_approved",
  "cardMasked": "400000******0000",
  "cardBrand": "VISA",
  "amount": 4829,
  "amountFormatted": "48.29",
  "currency": "752",
  "result": "APPROVED",
  "authCode": "123456",
  "responseCode": "00",
  "message": "Transaction approved",
  "durationMs": 1240
}
```

Use in a script:

```bash
RESULT=$(ttc pay --card sb_visa_approved --json)
STATUS=$(echo "$RESULT" | jq -r '.result')
PAYMENT_ID=$(echo "$RESULT" | jq -r '.paymentId')

if [ "$STATUS" = "APPROVED" ]; then
  echo "Payment approved: $PAYMENT_ID"
fi
```

---

### Flow 4: Verbose Output (for debugging)

```bash
ttc pay --card sb_visa_approved --verbose
```

```
[ttc] using terminal 83facf1d2247580204
[ttc] creating order — amount: 4829 SEK...
[ttc]   → POST https://carbon.beta.surfboard.se/api/orders
[ttc]   ← 200 OK (220ms)
[ttc]   ✓ order: 83fad0180227d0790b
[ttc]   ✓ payment: 83fad019172fb00406
[ttc] connecting to AGW...
[ttc]   → WSS wss://agw.beta.surfboard.se/ws?token=eyJhbGci...
[ttc]   ← CONNECTION_ACK
[ttc] processing payment — sb_visa_approved (400000******0000)...
[ttc]   → PAYMENT_REQUEST { "orderId": "...", "amount": 4829, ... }
[ttc]   ← PAYMENT_RESPONSE { "result": "APPROVED", "authCode": "123456" }
[ttc]   ✓ APPROVED — auth: 123456
```

---

## Error Reference

| Error | Cause | Fix |
|-------|-------|-----|
| `ERROR: no active terminal set` | Haven't run `ttc use` | Run `ttc use <terminalId>` |
| `ERROR: terminal "xyz" not found` | Wrong terminal ID | Run `ttc terminals get -e <env>` |
| `ERROR: unknown card "xyz"` | Card not in catalogue | Run `ttc cards list` |
| `ERROR: --env is required` | Missing flag | Add `-e beta` or `-e prod` |
| `ERROR: --merchant-id is required` | Missing flag | Add `-m <id>` |
| `ERROR: --store-id is required` | Missing flag | Add `-s <id>` |
| `ERROR: registration failed after 3 retries` | Slow network | Check VPN/network speed |
| `ERROR: order failed — unauthorized` | Token expired | Run `ttc use <terminalId>` again |
| `ERROR: AGW connection refused` | Gateway unreachable | Check VPN |
| `TIMEOUT — no response from AGW` | AGW slow | Add `--timeout 60` |
| `DECLINED — 05: Do not honor` | Card declined (expected) | Check `ttc cards list` for expected result |
| `"xyz" is a generic test card` | Non-surfboard card in real mode | Use `sb_*` cards or add `--mock` |

---

## Exit Codes

| Code | Meaning                    |
|------|----------------------------|
| `0`  | Success                    |
| `1`  | Invalid command / flags    |
| `2`  | Registration failed        |
| `3`  | Authentication failed      |
| `4`  | Order creation failed      |
| `5`  | AGW connection failed      |
| `6`  | Payment declined           |
| `7`  | Payment timeout            |
| `8`  | Transaction fetch failed   |
| `10` | Unexpected error           |

---

## Local State

TTC stores state in `~/.ttc/` — no database, just JSON files.

```
~/.ttc/
├── config.json              # Active terminal + environment
├── slack.pid                # Slack bot process ID (when running)
├── slack.log                # Slack bot logs
└── terminals/
    ├── 83facf1d...json      # Terminal + credentials
    └── 84abc123...json
```

To start fresh:

```bash
ttc reset
```

---

## Slack Bot

Run TTC commands directly from Slack. All 34 Surfboard test cards available. Pay by number or by key.

```
/ttc pay --card 01           → pays with card #01 (Visa — Approved)
/ttc pay --card sb_mc_fin_1  → pays with Mastercard financial decline
/ttc cards                   → interactive card picker with Pay buttons
/ttc status                  → show active terminal
/ttc bot restart             → rebuild & restart bot (no terminal needed)
```

### How it works

```
You type in Slack:   /ttc pay --card sb_visa_approved
                              │
                              ▼
                     Slack sends POST to Cloudflare tunnel URL
                              │
                              ▼
                     Bot runs: node dist/index.js pay --card sb_visa_approved --json
                              │
                              ▼
                     Rich result posted back to Slack channel
```

---

### Credentials — What They Are

The bot reads these from `.env` in the project root:

| Variable | What it is | Where to get it |
|----------|-----------|-----------------|
| `PORT` | Port the bot HTTP server listens on | Set to any free port (default: `3001`) |
| `SLACK_WEBHOOK_URL` | Incoming webhook URL — bot posts results here | Slack app → Incoming Webhooks → Add New Webhook |
| `SLACK_SIGNING_SECRET` | Verifies slash command requests come from Slack | api.slack.com/apps → TTC → Basic Information → App Credentials |
| `SLACK_APP_CONFIG_TOKEN` | Config API token — lets bot update its own slash command URL | api.slack.com/apps (list page, not inside TTC) → App Configuration Tokens → Copy **Access Token** |
| `SLACK_APP_CONFIG_REFRESH_TOKEN` | Refreshes the config token when it expires (12h) | Same page → Copy **Refresh Token** |
| `SLACK_APP_ID` | Your app's unique ID | api.slack.com/apps → TTC → Basic Information (starts with `A...`) |

**First-time setup:**

```
# .env
PORT=3001
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T.../B.../xxx
SLACK_SIGNING_SECRET=abc123...
SLACK_APP_CONFIG_TOKEN=xoxe.xoxp-1-...
SLACK_APP_CONFIG_REFRESH_TOKEN=xoxe-1-...
SLACK_APP_ID=A0123456789
```

---

### Starting the Bot

#### First run (sets up auto-start):

```bash
ttc slack
```

On the first run, TTC installs a **macOS LaunchAgent** that auto-starts the bot on every login. You never need to run this again manually.

After the first run, the bot:
- Starts automatically when you log in to your Mac
- Restarts automatically if it crashes
- Connects a Cloudflare tunnel (public HTTPS URL — no account needed)
- Auto-updates your Slack app's slash command URL when the tunnel changes

#### Stopping and restarting:

```bash
# From Slack (no terminal needed):
/ttc slack --off       → stop the bot
/ttc bot restart       → rebuild & restart (takes ~15 seconds)
/ttc bot start         → start without rebuilding
/ttc bot status        → check if running
/ttc bot logs          → show last 20 log lines

# From terminal:
ttc slack --off        → stop the bot
ttc slack --install    → reinstall LaunchAgent (if needed)
```

---

### Auto URL Updates (Cloudflare Tunnel)

The Cloudflare tunnel generates a new random URL every time the bot restarts. TTC handles this automatically:

**If `SLACK_APP_CONFIG_REFRESH_TOKEN` is set** (recommended):
- Bot auto-refreshes the config token when it expires
- Bot auto-updates the Slack slash command URL
- No manual steps needed — ever

**If refresh token is NOT set:**
- Bot posts the new tunnel URLs to your Slack channel on startup
- You update Slack manually:
  1. api.slack.com/apps → TTC → **Slash Commands** → Edit `/ttc` → paste new URL → Save
  2. api.slack.com/apps → TTC → **Interactivity & Shortcuts** → paste new URL → Save

**One-time setup to make it fully automatic:**

1. Go to [api.slack.com/apps](https://api.slack.com/apps) — the list page, not inside TTC
2. Find **"App Configuration Tokens"** section
3. Copy the **Refresh Token** (`xoxe-1-...`)
4. Add to `.env`:
   ```
   SLACK_APP_CONFIG_REFRESH_TOKEN=xoxe-1-...
   ```
5. Restart the bot: `/ttc bot restart`

---

### All Slack Commands

```
/ttc help                                          Show all commands
/ttc setup                                         First-time credentials guide
/ttc cards                                         Interactive card picker (all 34 cards)
/ttc pay --card <key|#>                            Pay with card key or number
/ttc pay --card 01                                 Pay with card #01 (sb_visa_approved)
/ttc pay --card sb_visa_approved                   Pay with card by key
/ttc pay --card sb_visa_fin_1 --amount 5000        Pay with specific amount
/ttc status                                        Show active terminal
/ttc register -e beta -m <merchantId> -s <storeId> Register a terminal
/ttc use <terminalId>                              Set active terminal
/ttc transaction get <paymentId>                   View transaction details
/ttc terminals -e beta                             List terminals
/ttc reset                                         Clear all state

Bot management (no terminal needed):
/ttc bot status        Check if bot is running
/ttc bot restart       Rebuild & restart bot (~15 seconds)
/ttc bot start         Start bot (no rebuild)
/ttc bot stop          Stop bot
/ttc bot logs          Show last 20 log lines
/ttc slack --off       Stop bot (same as bot stop)
/ttc slack restart     Restart bot (same as bot restart)
```

---

### What it looks like in Slack

**Approved payment:**
```
✅ APPROVED — @you ran `pay --card sb_visa_approved`
────────────────────────────────────
Result:      APPROVED
Card:        VISA 400000******0000
Amount:      kr48.29 SEK 🇸🇪
Order:       83fad0180227d0790b
Payment ID:  83fad019172fb00406
Auth Code:   123456
Response:    00
```

**Declined payment:**
```
❌ DECLINED — @you ran `pay --card sb_visa_fin_1`
────────────────────────────────────
Result:      DECLINED
Card:        VISA 466554******0574
Amount:      kr72.31 SEK 🇸🇪
Response:    05 — Do not honour
```

**Interactive card picker** (`/ttc cards`):
```
┌──────────────────────────────────┐
│ 🃏 Surfboard Test Cards (34)     │
│                                  │
│ APPROVED (12 cards)              │
│ #01  VISA  400000******0000      │
│      sb_visa_approved    [Pay]   │
│ #02  VISA  400000******0007      │
│      sb_visa_approved_2  [Pay]   │
│ ...                              │
│                                  │
│ Financial Issues (7 cards)       │
│ #13  VISA  466554******0574      │
│      sb_visa_fin_1       [Pay]   │
│ ...                              │
└──────────────────────────────────┘
```

---

### Troubleshooting

| Problem | Fix |
|---------|-----|
| `/ttc` times out in Slack | Slack URL is stale — check Slack channel for new URL posted by bot, or `/ttc bot restart` |
| `App did not respond` | Same as above — tunnel URL changed. Bot posts new URL to channel on startup |
| `Invalid signature` error | Paste the correct `SLACK_SIGNING_SECRET` from Slack into `.env` |
| `TTC CLI not found` | Run `npm run build` in project directory |
| Bot not starting on login | Run `ttc slack` once from terminal to reinstall LaunchAgent |
| Port already in use | Change `PORT=` in `.env` or run `ttc slack --off` first |
| Auto-config fails every restart | Add `SLACK_APP_CONFIG_REFRESH_TOKEN` to `.env` (see Auto URL Updates above) |

---

## Development

### Setup

```bash
git clone <repo>
cd ttc
npm install
npm run build
npm link          # makes 'ttc' available globally
```

### Run Without Building

```bash
npm run dev -- register -e beta -m abc -s xyz
npm run dev -- pay --card sb_visa_approved
```

### Rebuild After Changes

```bash
npm run build
```

No need to re-run `npm link` after a rebuild — the link points to `dist/index.js` which gets updated automatically.

### Unlink

```bash
npm unlink -g ttc
```