path-terminal-init 0.2.8 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "path-terminal-init",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "One-line setup for the Path Terminal SDK integration assistant — connects the user's AI agent to the Path MCP server and installs the per-OS integration recipe (iOS, Android, Windows). The single source of truth for the bootstrap CLI and the recipes.",
5
5
  "bin": {
6
6
  "path-terminal-init": "dist/cli.js"
@@ -151,11 +151,28 @@ returns one synthetic device for the configured host, so "Apply & Connect" conne
151
151
 
152
152
  #### Step 2b — Emulator over Bluetooth — scan and connect
153
153
 
154
+ > **⚠ Runtime permissions are MANDATORY — this is the #1 reason Bluetooth silently
155
+ > fails.** Declaring `BLUETOOTH_SCAN` / `BLUETOOTH_CONNECT` in the manifest is **not
156
+ > enough** on Android 12+ (API 31). You must **request them at runtime** before the
157
+ > first scan, or `discoverDevices()` finds nothing / throws and the user sees an empty
158
+ > list with no error. Call `get_code_example` with operation='backend-switch' and
159
+ > platform='android' and reproduce its `rememberBlePermission()` pattern:
160
+ > - Use `ActivityResultContracts.RequestMultiplePermissions()` to request
161
+ > `BLUETOOTH_SCAN` **and** `BLUETOOTH_CONNECT` (guarded by `Build.VERSION.SDK_INT >= S`).
162
+ > - Show a **"Grant Bluetooth permission"** button when not yet granted, and **gate the
163
+ > Scan button** on the granted state — don't let the user scan without permission.
164
+ > - Keep `android:usesPermissionFlags="neverForLocation"` on `BLUETOOTH_SCAN` (call
165
+ > `get_manifest_requirements`) so `ACCESS_FINE_LOCATION` is not also required.
166
+ > - Surface **"Bluetooth is turned off"** — `discoverDevices()` throws
167
+ > `PathError(CONNECTIVITY)` when the radio is disabled. The emulator must be in
168
+ > Bluetooth mode and advertise a name containing "Path".
169
+
154
170
  For the BLE backend keep the scan/connect flow:
155
- 1. **Scan for Path Terminals** — triggers BLE discovery
156
- 2. Discovered devices listeach with a **Connect** button
157
- 3. Live connection state (Scanning / Connected / Disconnected)
158
- 4. **Disconnect** when connected
171
+ 1. **Grant Bluetooth permission** (runtime, per the callout above) required before scanning
172
+ 2. **Scan for Path Terminals** triggers BLE discovery (only enabled once permission is granted)
173
+ 3. Discovered devices list each with a **Connect** button
174
+ 4. Live connection state (Scanning / Connected / Disconnected)
175
+ 5. **Disconnect** when connected
159
176
 
160
177
  **Add to `PaymentTerminalAdapter` interface** (with no-op default implementations):
161
178
  ```kotlin
@@ -248,6 +265,44 @@ Call `validate_integration` with platform='android' on your code before finalisi
248
265
 
249
266
  ---
250
267
 
268
+ ### Step 3b — Merchant logo on the customer display (idle branding)
269
+
270
+ The demo can push a merchant logo to the terminal's **customer-facing display** (idle/
271
+ attract mode). This is a real feature you MUST wire — it works on the **emulator (Wi-Fi
272
+ and Bluetooth)** and on Verifone. Call `get_code_example` with operation='idle-branding'
273
+ and platform='android' first, then reproduce the demo's proven pattern.
274
+
275
+ **The SDK API** (on `PathTerminal`, a `suspend` function):
276
+ ```kotlin
277
+ suspend fun setIdleBranding(content: CustomerDisplayContent?)
278
+ // content: raw PNG/JPEG bytes wrapped in a class — NOT a Bitmap/base64/resource id
279
+ CustomerDisplayContent(imageBytes = logoBytes, caption = caption?.ifBlank { null })
280
+ // pass null to CLEAR the logo (when the "show logo" toggle is off)
281
+ ```
282
+
283
+ **Add a "Customer Display" section to Settings** with:
284
+ - A **"Show merchant logo"** toggle (persisted).
285
+ - A **photo picker** — `ActivityResultContracts.GetContent()` launched with `"image/*"` —
286
+ storing the chosen image **pre-scaled to ≤ 480px PNG** in private storage (so a fresh
287
+ launch still has a logo). Ship a default logo asset so it works out of the box.
288
+ - An optional **caption** field.
289
+ - An **"Apply to terminal now"** button, **enabled only when `connectionState` is Connected**.
290
+
291
+ > **CRITICAL — re-apply after every connect.** A fresh connection starts with a **blank**
292
+ > display. The SDK re-pushes after each sale/refund, but the *first* show is yours: call
293
+ > your `applyBranding()` (which calls `terminal.setIdleBranding(...)`) **right after every
294
+ > successful `terminal.connect()`**, not just from the button. An integration that only
295
+ > sets branding once, or never calls `setIdleBranding` at all, shows nothing.
296
+
297
+ > **Best-effort + backend-gated.** `setIdleBranding` returns `Unit` and swallows backend
298
+ > errors — **"no exception" does not prove the logo rendered**; confirm on the device.
299
+ > Keep the Verifone payload small (a logo, not a full-res photo) — pre-scaling to ≤ 480px
300
+ > PNG keeps the base64 under the ~32 000-char cap that would otherwise wedge the display.
301
+
302
+ Call `validate_integration` with platform='android' before finalising.
303
+
304
+ ---
305
+
251
306
  ### Step 3.5 — Compile the changes (check the environment first)
252
307
 
253
308
  Confirm your code compiles against the SDK's **real** API — this catches wrong method
@@ -278,18 +333,25 @@ Frame a missing build toolchain as **"here's how to verify"**, never as a failur
278
333
 
279
334
  1. Confirm the Application/MainActivity injects `PathTerminalAdapter` (not the old adapter).
280
335
  2. Confirm AndroidManifest.xml has BLUETOOTH_SCAN and BLUETOOTH_CONNECT permissions
281
- (needed for the Bluetooth backend).
336
+ (with `neverForLocation` on SCAN) **and** that the app requests them at **runtime**
337
+ before scanning — manifest-only is not enough on Android 12+ (Step 2b).
282
338
  3. **Prove on the emulator first (the default path — Wi-Fi/IP):**
283
339
  - Put the Path POS Emulator in Wi-Fi mode (Config → Connection on the device);
284
340
  it shows its `IP:port` on the welcome screen.
285
341
  - Run the app → Settings → Payment Terminal → backend **Emulator (Wi-Fi)** → type
286
342
  that IP → **Apply & Connect**. connectionState should reach Connected.
287
- - (Optionally also prove **Emulator (Bluetooth)**: select it, **Scan**, **Connect**.)
343
+ - (Optionally also prove **Emulator (Bluetooth)**: **Grant Bluetooth permission** when
344
+ prompted, then select it, **Scan**, **Connect**. If Scan finds nothing, the runtime
345
+ permission was almost certainly not granted/requested — recheck Step 2b.)
288
346
  4. Add items to cart → Card payment → present a card / tap NFC tag on emulator when prompted.
289
347
  5. Verify:
290
348
  - result.state == TransactionState.APPROVED (or result.isApproved == true)
291
349
  - result.transactionId is non-null
292
350
  - Receipt data populated via terminal.getReceiptData(transactionId)
351
+ 5b. **Prove the merchant logo (Step 3b):** in Settings → Customer Display, pick a logo and
352
+ tap **Apply to terminal now** while connected — confirm it appears on the emulator's
353
+ customer display. Then disconnect/reconnect and confirm it re-appears (proves you
354
+ re-apply after connect).
293
355
  6. **Then prove the switch to Verifone:** select backend **Verifone**, enter the terminal's
294
356
  IP and confirm the stored login, **Apply & Connect** — the SDK runs the PSDK init + login
295
357
  and the same sale/refund/void flow runs against the real terminal, unchanged. (Only one
@@ -306,6 +368,10 @@ Frame a missing build toolchain as **"here's how to verify"**, never as a failur
306
368
  - Handle TransactionState: APPROVED, DECLINED, TIMED_OUT, FAILED, CANCELLED
307
369
  - For **refunds**, success state is `REFUNDED` — not `APPROVED`
308
370
  - Catch `PathError` and check `.recoverable`
371
+ - Request BLE runtime permissions (`BLUETOOTH_SCAN` + `BLUETOOTH_CONNECT`) before scanning —
372
+ the manifest declaration alone does NOT work on Android 12+ (see Step 2b)
373
+ - Re-apply idle branding (`terminal.setIdleBranding(CustomerDisplayContent(imageBytes, caption))`)
374
+ after every successful `connect()`, not just from the button (see Step 3b)
309
375
  - Call `validate_integration` with platform='android' before presenting code
310
376
  - Call from coroutines — `terminal.sale()` and `terminal.refund()` are `suspend` functions
311
377
 
@@ -196,6 +196,37 @@ Confirm the transaction methods map correctly:
196
196
 
197
197
  ---
198
198
 
199
+ ### Step 3b — Merchant logo on the customer display (idle branding)
200
+
201
+ The app can push a merchant logo to the terminal's **customer-facing display** (idle/attract
202
+ mode). It works on the **emulator (Wi-Fi and Bluetooth)** and on Verifone. Call
203
+ `get_code_example` operation='idle-branding' (platform='windows') first.
204
+
205
+ **The SDK API** (on `PathTerminal`, async):
206
+ ```csharp
207
+ Task SetIdleBrandingAsync(CustomerDisplayContent? content, CancellationToken ct = default);
208
+ // ImageBytes = raw PNG/JPEG bytes (NOT a BitmapImage / not base64); Caption is optional.
209
+ new CustomerDisplayContent(imageBytes, caption); // pass null content to CLEAR the logo
210
+ ```
211
+
212
+ **Add a "Customer Display" section to Settings** with:
213
+ - A **"Show merchant logo"** toggle (persisted).
214
+ - A **file picker** (`OpenFileDialog`, `.png`/`.jpg`) storing the image **pre-scaled to ≤ 480px
215
+ PNG** so a fresh launch still has a logo; ship a default logo asset so it works out of the box.
216
+ - An optional **caption** field.
217
+ - An **"Apply to terminal now"** button, **enabled only when connected**.
218
+
219
+ > **CRITICAL — re-apply after every connect.** A fresh connection starts with a **blank**
220
+ > display. Call `SetIdleBrandingAsync(...)` **right after every successful connect**, not just
221
+ > from the button — otherwise the logo never appears. Pass `null` content to clear when the
222
+ > toggle is off. Call it with `await` from your handler — never `.Result` / `.Wait()`.
223
+
224
+ > **Best-effort + backend-gated.** `SetIdleBrandingAsync` swallows backend errors — "no
225
+ > exception" does not prove it rendered; confirm on the device. Keep the Verifone payload small
226
+ > (a logo pre-scaled to ≤ 480px PNG, not a full-res photo, so the base64 stays under the cap).
227
+
228
+ ---
229
+
199
230
  ### Step 3.5 — Build to catch API mismatches (check the environment first)
200
231
 
201
232
  Confirm your C# compiles against the SDK's **real** API. **Probe the toolchain first**, and
@@ -192,6 +192,37 @@ Confirm the transaction methods map correctly:
192
192
 
193
193
  ---
194
194
 
195
+ ### Step 3b — Merchant logo on the customer display (idle branding)
196
+
197
+ The app can push a merchant logo to the terminal's **customer-facing display** (idle/attract
198
+ mode). It works on the **emulator (Wi-Fi and Bluetooth)** and on Verifone. Call
199
+ `get_code_example` operation='idle-branding' (platform='windows') first.
200
+
201
+ **The SDK API** (on `PathTerminal`, async):
202
+ ```csharp
203
+ Task SetIdleBrandingAsync(CustomerDisplayContent? content, CancellationToken ct = default);
204
+ // ImageBytes = raw PNG/JPEG bytes (NOT a BitmapImage / not base64); Caption is optional.
205
+ new CustomerDisplayContent(imageBytes, caption); // pass null content to CLEAR the logo
206
+ ```
207
+
208
+ **Add a "Customer Display" section to Settings** with:
209
+ - A **"Show merchant logo"** toggle (persisted).
210
+ - A **file picker** (`FileOpenPicker`, `.png`/`.jpg`) storing the image **pre-scaled to ≤ 480px
211
+ PNG** so a fresh launch still has a logo; ship a default logo asset so it works out of the box.
212
+ - An optional **caption** field.
213
+ - An **"Apply to terminal now"** button, **enabled only when connected**.
214
+
215
+ > **CRITICAL — re-apply after every connect.** A fresh connection starts with a **blank**
216
+ > display. Call `SetIdleBrandingAsync(...)` **right after every successful connect**, not just
217
+ > from the button — otherwise the logo never appears. Pass `null` content to clear when the
218
+ > toggle is off.
219
+
220
+ > **Best-effort + backend-gated.** `SetIdleBrandingAsync` swallows backend errors — "no
221
+ > exception" does not prove it rendered; confirm on the device. Keep the Verifone payload small
222
+ > (a logo pre-scaled to ≤ 480px PNG, not a full-res photo, so the base64 stays under the cap).
223
+
224
+ ---
225
+
195
226
  ### Step 3.5 — Build to catch API mismatches (check the environment first)
196
227
 
197
228
  Confirm your C# compiles against the SDK's **real** API. **Probe the toolchain first**, and