@rubytech/taskmaster 1.18.2 → 1.19.1
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/dist/agents/workspace-migrations.js +14 -0
- package/dist/build-info.json +3 -3
- package/dist/control-ui/assets/index-0WHVrpg7.css +1 -0
- package/dist/control-ui/assets/{index-DYBHelH8.js → index-mjAT1dyG.js} +779 -687
- package/dist/control-ui/assets/index-mjAT1dyG.js.map +1 -0
- package/dist/control-ui/index.html +2 -2
- package/dist/cron/isolated-agent/delivery-record.js +3 -3
- package/dist/cron/isolated-agent/run.js +4 -1
- package/dist/cron/preloaded.js +33 -0
- package/dist/gateway/control-ui.js +8 -6
- package/dist/gateway/protocol/schema/logs-chat.js +2 -0
- package/dist/gateway/server-http.js +2 -1
- package/dist/gateway/server-methods/access.js +13 -0
- package/dist/gateway/server-methods/logs.js +16 -1
- package/dist/gateway/server.impl.js +40 -18
- package/dist/hooks/bundled/ride-dispatch/handler.js +161 -50
- package/dist/media-understanding/defaults.js +1 -1
- package/dist/memory/audit.js +1 -1
- package/package.json +1 -1
- package/taskmaster-docs/USER-GUIDE.md +1 -1
- package/templates/beagle-zanzibar/agents/admin/AGENTS.md +157 -41
- package/templates/beagle-zanzibar/agents/public/AGENTS.md +27 -8
- package/templates/beagle-zanzibar/skills/beagle-zanzibar/cron-template.json +20 -0
- package/templates/beagle-zanzibar/skills/beagle-zanzibar/references/pin-qr.md +22 -9
- package/templates/beagle-zanzibar/skills/stripe/references/payment-links.md +25 -15
- package/dist/control-ui/assets/index-DYBHelH8.js.map +0 -1
- package/dist/control-ui/assets/index-XqRo9tNW.css +0 -1
|
@@ -12,6 +12,11 @@ Before doing anything else:
|
|
|
12
12
|
1. Read `SOUL.md` — this is who you are
|
|
13
13
|
2. Read `IDENTITY.md` — your role and boundaries
|
|
14
14
|
3. Check memory for recent bookings, driver issues, and any pending follow-ups
|
|
15
|
+
4. Ensure the stall-detection cron job is configured — call `cron` with action: `list` and check for a job named "Ride Booking Stall Check". If it does not exist, create it:
|
|
16
|
+
- action: `add`, name: `Ride Booking Stall Check`, schedule: `*/10 * * * *` (every 10 minutes)
|
|
17
|
+
- sessionTarget: `isolated`, wakeMode: `next-heartbeat`
|
|
18
|
+
- message: `Run the ride booking stall check.`
|
|
19
|
+
- deliver: false (operator does not need a notification unless a stall is found)
|
|
15
20
|
|
|
16
21
|
## Tools
|
|
17
22
|
|
|
@@ -64,61 +69,124 @@ response_rate: 0
|
|
|
64
69
|
|
|
65
70
|
## Ride Dispatch Processing
|
|
66
71
|
|
|
67
|
-
Messages prefixed with `[System: Ride Dispatch — ...]` are automated dispatches from the ride-dispatch hook.
|
|
72
|
+
Messages prefixed with `[System: Ride Dispatch — ...]` are automated dispatches from the ride-dispatch hook. Each message contains fully prescriptive steps — follow them exactly. The steps in this section are a reference summary; the message body is authoritative.
|
|
73
|
+
|
|
74
|
+
### Shared File Locations
|
|
75
|
+
|
|
76
|
+
All cross-agent state flows through structured files. Never rely on session memory to recover data another agent should provide.
|
|
77
|
+
|
|
78
|
+
| File | Written by | Read by |
|
|
79
|
+
|------|-----------|---------|
|
|
80
|
+
| `shared/dispatch/{jobId}-trip-request.md` | Public agent | Hook → Admin |
|
|
81
|
+
| `shared/dispatch/{jobId}-booking-confirm.md` | Public agent | Hook → Admin |
|
|
82
|
+
| `shared/active-negotiations/{phone-digits}.md` | Admin | Hook (driver reply routing) |
|
|
83
|
+
| `shared/offers/{jobId}.md` | Admin | Public agent (when tourist selects) |
|
|
84
|
+
| `shared/bookings/{jobId}.md` | Admin | Admin (post-payment) |
|
|
85
|
+
| `shared/state/{tourist-phone}-active.md` | Public agent | Public agent (job ID lookup) |
|
|
68
86
|
|
|
69
87
|
### Trip Request
|
|
70
88
|
|
|
71
|
-
When you receive `[System: Ride Dispatch — Trip Request]
|
|
89
|
+
When you receive `[System: Ride Dispatch — Trip Request]`, the message body contains step-by-step instructions. Summary:
|
|
72
90
|
|
|
73
|
-
1.
|
|
74
|
-
2.
|
|
75
|
-
3. Select up to 3 idle
|
|
91
|
+
1. `contact_lookup` → get all drivers
|
|
92
|
+
2. `memory_get` on `drivers/{name}.md` for each → check `status` field
|
|
93
|
+
3. Select up to 3 with `status: idle`, preferring route history matches
|
|
76
94
|
4. For each selected driver:
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
5.
|
|
80
|
-
6.
|
|
81
|
-
7.
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
|
|
85
|
-
|
|
95
|
+
- `memory_write` to `drivers/{name}.md` — set `status: awaiting_response`, `current_booking: {jobId}`
|
|
96
|
+
- `memory_write` to `shared/active-negotiations/{phone-digits}.md`
|
|
97
|
+
5. `message` each driver in Swahili — fare enquiry, not confirmed job (exact text in dispatch)
|
|
98
|
+
6. `message` the tourist — acknowledged, waiting for quotes (exact text in dispatch)
|
|
99
|
+
7. On each driver reply: record quote, then when all in (or after 5 min):
|
|
100
|
+
- `memory_write` to `shared/offers/{jobId}.md` (prescribed format below)
|
|
101
|
+
- `message` the tourist with formatted options (exact text in dispatch)
|
|
102
|
+
|
|
103
|
+
**Offers file format** (`shared/offers/{jobId}.md`):
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
# Offers: BGL-XXXX
|
|
107
|
+
job_id: BGL-XXXX
|
|
108
|
+
status: ready
|
|
109
|
+
tourist_phone: [tourist phone]
|
|
110
|
+
offer_1_driver: [name]
|
|
111
|
+
offer_1_phone: [digits only, no +]
|
|
112
|
+
offer_1_vehicle: [vehicle type]
|
|
113
|
+
offer_1_rating: [rating]
|
|
114
|
+
offer_1_fare: [USD amount as number]
|
|
115
|
+
offer_1_duration: [estimated minutes as number]
|
|
116
|
+
offer_2_driver: [if applicable]
|
|
117
|
+
offer_2_phone: [if applicable]
|
|
118
|
+
offer_2_vehicle: [if applicable]
|
|
119
|
+
offer_2_rating: [if applicable]
|
|
120
|
+
offer_2_fare: [if applicable]
|
|
121
|
+
offer_2_duration: [if applicable]
|
|
122
|
+
[offer_3_* fields if applicable]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Sort by fare ascending. Omit fields for offers you don't have.
|
|
86
126
|
|
|
87
127
|
### Booking Confirmation
|
|
88
128
|
|
|
89
|
-
When you receive `[System: Ride Dispatch — Booking Confirmation]
|
|
129
|
+
When you receive `[System: Ride Dispatch — Booking Confirmation]`, the message body contains step-by-step instructions. Summary:
|
|
130
|
+
|
|
131
|
+
1. Load the `stripe` skill. Create a Checkout Session for $2.00 USD:
|
|
132
|
+
- `success_url: https://zanzibar.beagle.taxi/booking-confirmed`
|
|
133
|
+
- `cancel_url: https://zanzibar.beagle.taxi/booking-cancelled`
|
|
134
|
+
- `metadata: booking_id, tourist_phone, account_id`
|
|
135
|
+
2. `memory_write` to `shared/bookings/{jobId}.md` (prescribed format below)
|
|
136
|
+
3. `message` the tourist with the payment link (exact text in dispatch)
|
|
137
|
+
4. Clear `shared/active-negotiations/{phone-digits}.md` for unselected drivers; reset their `drivers/{name}.md` status to `idle`
|
|
90
138
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
139
|
+
**Booking record format** (`shared/bookings/{jobId}.md`):
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
# Booking: BGL-XXXX
|
|
143
|
+
status: payment_pending
|
|
144
|
+
tourist_phone: [phone]
|
|
145
|
+
tourist_name: [name]
|
|
146
|
+
driver_name: [name]
|
|
147
|
+
driver_phone: [digits only]
|
|
148
|
+
vehicle: [vehicle type]
|
|
149
|
+
plate: [plate]
|
|
150
|
+
pickup: [location]
|
|
151
|
+
destination: [location]
|
|
152
|
+
date: [date]
|
|
153
|
+
time: [time]
|
|
154
|
+
fare: [USD amount]
|
|
155
|
+
payment_url: [Stripe URL]
|
|
156
|
+
created_at: [timestamp]
|
|
157
|
+
pin:
|
|
158
|
+
```
|
|
98
159
|
|
|
99
160
|
### Payment Confirmed
|
|
100
161
|
|
|
101
|
-
When you receive `[System: Ride Dispatch — Payment Confirmed]
|
|
162
|
+
When you receive `[System: Ride Dispatch — Payment Confirmed]`, the message body contains step-by-step instructions. Summary:
|
|
102
163
|
|
|
103
|
-
1.
|
|
104
|
-
2. Generate
|
|
105
|
-
3.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
164
|
+
1. `memory_get` on `shared/bookings/{bookingId}.md` — get all driver and tourist details
|
|
165
|
+
2. Generate a random 4-digit PIN (1000–9999)
|
|
166
|
+
3. QR code URL: `https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=PIN%3A+{pin}`
|
|
167
|
+
4. `message` the tourist with driver details and PIN (exact text in dispatch)
|
|
168
|
+
5. `message` the driver with the QR code image and booking details (exact text in dispatch)
|
|
169
|
+
6. `memory_write` to `shared/bookings/{bookingId}.md` — set `status: confirmed`, set `pin: {pin}`
|
|
170
|
+
7. `memory_write` to `shared/active-negotiations/{driver-phone-digits}.md` — empty content
|
|
110
171
|
|
|
111
172
|
### Driver Reply
|
|
112
173
|
|
|
113
|
-
When you receive `[System: Ride Dispatch — Driver Reply]
|
|
174
|
+
When you receive `[System: Ride Dispatch — Driver Reply]`, the message body contains step-by-step instructions. Summary:
|
|
175
|
+
|
|
176
|
+
**If quoting a fare:**
|
|
177
|
+
1. `contact_lookup` → match driver by phone digits → get name and vehicle
|
|
178
|
+
2. `memory_write` to `drivers/{name}.md` — record quote under `## Quotes`
|
|
179
|
+
3. Count active negotiations for this job — if all replied or 5 min elapsed:
|
|
180
|
+
- Write `shared/offers/{jobId}.md` with all quotes (format above)
|
|
181
|
+
- `message` the tourist with formatted options (exact text in dispatch — read `tourist_phone` from the offers file)
|
|
114
182
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
183
|
+
**If declining:**
|
|
184
|
+
1. `memory_write` to `drivers/{name}.md` — set `status: idle`, `current_booking: null`
|
|
185
|
+
2. `memory_write` to `shared/active-negotiations/{phone-digits}.md` — empty content
|
|
118
186
|
|
|
119
187
|
### Active Negotiation Index
|
|
120
188
|
|
|
121
|
-
|
|
189
|
+
`shared/active-negotiations/{phone-digits}.md` format:
|
|
122
190
|
|
|
123
191
|
```
|
|
124
192
|
job_id: BGL-XXXX
|
|
@@ -126,17 +194,65 @@ driver_name: [name]
|
|
|
126
194
|
contacted_at: [timestamp]
|
|
127
195
|
```
|
|
128
196
|
|
|
129
|
-
Clear
|
|
130
|
-
- A driver declines or is not selected
|
|
131
|
-
- The booking is confirmed (keep only the selected driver's file until pickup completes)
|
|
132
|
-
- A negotiation expires with no response
|
|
133
|
-
|
|
134
|
-
This index enables the hook to route driver WhatsApp replies to the correct ride session without requiring drivers to include job IDs in their messages.
|
|
197
|
+
The hook uses this file to route incoming driver WhatsApp replies to the correct admin ride session, without requiring drivers to include the job ID in their messages. Clear by writing empty content (not deletion) when a driver declines, is not selected, or after the booking is confirmed.
|
|
135
198
|
|
|
136
199
|
---
|
|
137
200
|
|
|
138
201
|
## Operational Focus Areas
|
|
139
202
|
|
|
203
|
+
### Ride Booking Stall Check
|
|
204
|
+
|
|
205
|
+
When you receive `Run the ride booking stall check.` (from the stall-detection cron), execute every step below.
|
|
206
|
+
|
|
207
|
+
**STEP 1 — Scan for trip requests without booking records**
|
|
208
|
+
Call `memory_search` on `shared/dispatch/` for all files matching `*-trip-request.md`.
|
|
209
|
+
For each job ID found, check if `shared/bookings/{jobId}.md` exists via `memory_get`.
|
|
210
|
+
If the booking record exists (status: payment_pending or confirmed), this job is active — skip it.
|
|
211
|
+
If no booking record, the job may be stalled — proceed to STEP 2.
|
|
212
|
+
|
|
213
|
+
**STEP 2 — Classify the stall**
|
|
214
|
+
For each stalled job ID:
|
|
215
|
+
|
|
216
|
+
a. Read `shared/dispatch/{jobId}-trip-request.md` to get `tourist_phone`, `pickup`, `destination`, `date`, `time`, `created_at` (or use file modification time).
|
|
217
|
+
|
|
218
|
+
b. If the trip request is less than 15 minutes old: skip — it may still be in progress.
|
|
219
|
+
|
|
220
|
+
c. Check if `shared/offers/{jobId}.md` exists via `memory_get`.
|
|
221
|
+
|
|
222
|
+
**STEP 3 — Recover (offers file exists)**
|
|
223
|
+
If `shared/offers/{jobId}.md` exists, the tourist likely confirmed but the booking-confirm dispatch was never written. Recover automatically:
|
|
224
|
+
- Read the offers file to get `tourist_phone` and all offer fields.
|
|
225
|
+
- Assume the tourist selected Option 1 (the lowest-fare option — already sorted ascending).
|
|
226
|
+
- Write `shared/dispatch/{jobId}-booking-confirm.md` via `memory_write`:
|
|
227
|
+
```
|
|
228
|
+
# Dispatch: Booking Confirmation
|
|
229
|
+
job_id: {jobId}
|
|
230
|
+
phase: booking-confirm
|
|
231
|
+
tourist_phone: {tourist_phone from offers file}
|
|
232
|
+
tourist_name: unknown
|
|
233
|
+
selected_offer: Option 1 (auto-recovered)
|
|
234
|
+
driver_name: {offer_1_driver}
|
|
235
|
+
driver_phone: {offer_1_phone}
|
|
236
|
+
vehicle: {offer_1_vehicle}
|
|
237
|
+
fare: {offer_1_fare}
|
|
238
|
+
account_id: beagle-zanzibar
|
|
239
|
+
```
|
|
240
|
+
- This triggers the hook, which dispatches the booking confirmation to you in the ride session for that job.
|
|
241
|
+
|
|
242
|
+
**STEP 4 — Alert (no offers file, job >30 minutes old)**
|
|
243
|
+
If `shared/offers/{jobId}.md` does NOT exist and the trip request is >30 minutes old, the negotiation stalled before drivers responded.
|
|
244
|
+
- Include this text in your response output (do NOT use the `message` tool — this is an internal alert for the operator, not an outbound WhatsApp message):
|
|
245
|
+
`STALL ALERT [{jobId}]: No driver quotes received for {pickup} → {destination} at {time}. Tourist: {tourist_phone}. Please investigate or retry.`
|
|
246
|
+
- The cron job posts your response to the operator's control panel session automatically.
|
|
247
|
+
|
|
248
|
+
If the job is between 15 and 30 minutes old with no offers file: no action needed — negotiations can take time.
|
|
249
|
+
|
|
250
|
+
**STEP 5 — Log outcome**
|
|
251
|
+
Write a brief note via `memory_write` to `shared/stall-checks/{date}.md`:
|
|
252
|
+
```
|
|
253
|
+
{timestamp}: Checked {N} stalled jobs. Recovered: {list of jobIds}. Alerted: {list of jobIds}. No action needed: {list of jobIds}.
|
|
254
|
+
```
|
|
255
|
+
|
|
140
256
|
### Bookings
|
|
141
257
|
- Booking records live at `bookings/{job-id}.md` in memory. Each tracks: route, fare, driver, status, timestamps, ratings, and any substitution flag.
|
|
142
258
|
- Summarise recent booking activity: confirmed, completed, cancelled, no-shows
|
|
@@ -89,33 +89,52 @@ passengers: [count]
|
|
|
89
89
|
luggage: [description]
|
|
90
90
|
special_requests: [any requests or "none"]
|
|
91
91
|
fare_estimate: [range from knowledge base or "unknown"]
|
|
92
|
-
account_id:
|
|
92
|
+
account_id: beagle-zanzibar
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
+
The `account_id` must be `beagle-zanzibar` — the WhatsApp account ID. Do not use your agent ID here.
|
|
96
|
+
|
|
97
|
+
Then write a second file via `memory_write` to `shared/state/{tourist-phone}-active.md`:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
job_id: BGL-XXXX
|
|
101
|
+
tourist_phone: [tourist phone]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
This gives you a reliable, direct-read source for the job ID when the tourist confirms. Do not rely on memory search to recover the job ID later.
|
|
105
|
+
|
|
95
106
|
### Step 5 — Notify the tourist
|
|
96
107
|
|
|
97
108
|
After writing the dispatch file, tell the tourist: "I'm reaching out to our drivers now. I'll have quotes for you shortly." Do not send this before writing the dispatch file.
|
|
98
109
|
|
|
99
110
|
### Step 6 — Present offers
|
|
100
111
|
|
|
101
|
-
When driver
|
|
112
|
+
When the operations agent messages the tourist with driver quotes, the message is echoed into your conversation. It will include the job ID (e.g. `[BGL-0002]`) and up to 3 numbered options. Present the options to the tourist clearly: fare, vehicle type, driver rating, estimated journey time. No driver personal details — name, phone, and plate are gated by payment. See `references/ride-matching.md` for formatting.
|
|
102
113
|
|
|
103
114
|
### Step 7 — Confirm booking
|
|
104
115
|
|
|
105
|
-
When the tourist chooses an offer
|
|
116
|
+
When the tourist chooses an offer:
|
|
117
|
+
|
|
118
|
+
1. Call `memory_get` on `shared/state/{tourist-phone}-active.md` to get the job ID. Do not use `memory_search` — this file was written at Step 4 and contains the job ID directly.
|
|
119
|
+
2. Call `memory_get` on `shared/offers/{job-id}.md` to get the full offer details (driver name, driver phone, vehicle, rating, fare, duration). This file was written by the operations agent after collecting driver quotes.
|
|
120
|
+
3. Write the booking confirmation dispatch via `memory_write` to `shared/dispatch/{job-id}-booking-confirm.md`:
|
|
106
121
|
|
|
107
122
|
```
|
|
108
123
|
# Dispatch: Booking Confirmation
|
|
109
124
|
job_id: BGL-XXXX
|
|
110
125
|
phase: booking-confirm
|
|
111
126
|
tourist_phone: [same phone used in trip-request]
|
|
112
|
-
tourist_name: [name]
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
127
|
+
tourist_name: [name if known]
|
|
128
|
+
selected_offer: Option 1
|
|
129
|
+
driver_name: [offer_1_driver from shared/offers/{job-id}.md]
|
|
130
|
+
driver_phone: [offer_1_phone from shared/offers/{job-id}.md]
|
|
131
|
+
vehicle: [offer_1_vehicle from shared/offers/{job-id}.md]
|
|
132
|
+
fare: [offer_1_fare from shared/offers/{job-id}.md]
|
|
133
|
+
account_id: beagle-zanzibar
|
|
117
134
|
```
|
|
118
135
|
|
|
136
|
+
Substitute the correct offer number (1, 2, or 3) based on the tourist's selection. All field values come directly from the offers file — do not invent or guess any driver details.
|
|
137
|
+
|
|
119
138
|
### Step 8 — Payment
|
|
120
139
|
|
|
121
140
|
The operations agent will generate a Stripe payment link and relay it to your conversation as a `[System: Ride Dispatch — Payment Link]` message. Present the payment link and terms to the tourist. If the tourist has questions about payment, explain the booking fee. Payment confirmation is automatic via Stripe webhook — the tourist does not need to tell you they've paid.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"templateId": "beagle-zanzibar-stall-check",
|
|
3
|
+
"name": "Ride Booking Stall Check",
|
|
4
|
+
"description": "Monitors active ride negotiations every 10 minutes. Automatically recovers stalled bookings where the tourist confirmed but the dispatch was missed. Alerts the operator if driver quotes are not received within 30 minutes.",
|
|
5
|
+
"enabled": true,
|
|
6
|
+
"agentId": "beagle-zanzibar-admin",
|
|
7
|
+
"schedule": { "kind": "cron", "expr": "*/10 * * * *" },
|
|
8
|
+
"sessionTarget": "isolated",
|
|
9
|
+
"wakeMode": "next-heartbeat",
|
|
10
|
+
"payload": {
|
|
11
|
+
"kind": "agentTurn",
|
|
12
|
+
"message": "Run the ride booking stall check.",
|
|
13
|
+
"deliver": false,
|
|
14
|
+
"bestEffortDeliver": false
|
|
15
|
+
},
|
|
16
|
+
"isolation": {
|
|
17
|
+
"postToMainMode": "summary",
|
|
18
|
+
"postToMainPrefix": "Stall Check"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
Each confirmed booking gets a unique 4-digit pickup PIN. The tourist
|
|
5
|
+
Each confirmed booking gets a unique 4-digit pickup PIN. The tourist receives the PIN via WhatsApp. The driver receives a QR code image encoding it. At pickup, the tourist either:
|
|
6
|
+
|
|
7
|
+
- Scans the driver's QR code — phone camera shows the PIN inline, tourist confirms it matches their message (works offline)
|
|
8
|
+
- Or simply asks the driver to read the number aloud — driver reads it from the caption on their WhatsApp message
|
|
9
|
+
|
|
10
|
+
Both methods require no internet and no app.
|
|
6
11
|
|
|
7
12
|
## Generating the PIN
|
|
8
13
|
|
|
@@ -16,16 +21,24 @@ pin: 4827
|
|
|
16
21
|
|
|
17
22
|
## Generating the QR Code Image
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
Encode the PIN as labelled plain text — NOT a URL. Plain text QR codes display inline in the phone camera without opening any app or requiring internet.
|
|
25
|
+
|
|
26
|
+
Format:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
PIN: {pin}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
QR image URL:
|
|
20
33
|
|
|
21
34
|
```
|
|
22
|
-
https://api.qrserver.com/v1/create-qr-code/?size=300x300&data={pin}
|
|
35
|
+
https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=PIN%3A+{pin}
|
|
23
36
|
```
|
|
24
37
|
|
|
25
38
|
Example — PIN 4827:
|
|
26
|
-
`https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=4827`
|
|
39
|
+
`https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=PIN%3A+4827`
|
|
27
40
|
|
|
28
|
-
This URL returns a PNG image
|
|
41
|
+
This URL returns a PNG image. When the tourist scans it, their phone camera displays `PIN: 4827` immediately — no browser, no internet needed.
|
|
29
42
|
|
|
30
43
|
## Sending to the Driver
|
|
31
44
|
|
|
@@ -35,17 +48,17 @@ Use the `message` tool to send the QR code as an inline WhatsApp image:
|
|
|
35
48
|
action: send
|
|
36
49
|
channel: whatsapp
|
|
37
50
|
target: {driver_phone_number}
|
|
38
|
-
media: https://api.qrserver.com/v1/create-qr-code/?size=300x300&data={pin}
|
|
39
|
-
caption: [BGL-XXXX] Your
|
|
51
|
+
media: https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=PIN%3A+{pin}
|
|
52
|
+
caption: [BGL-XXXX] Your pickup PIN is {pin}. When you meet your passenger, tell them this number. Say: "Your PIN is {pin}." They will confirm it matches theirs. The QR code above is a backup — they can scan it instead if they prefer.
|
|
40
53
|
```
|
|
41
54
|
|
|
42
|
-
The
|
|
55
|
+
The PIN is included in the caption so the driver can read it aloud if the tourist prefers that.
|
|
43
56
|
|
|
44
57
|
## Sending to the Tourist
|
|
45
58
|
|
|
46
59
|
Send the PIN with an explanation alongside the other driver details (in the main conversation):
|
|
47
60
|
|
|
48
|
-
> "Your
|
|
61
|
+
> "Your pickup PIN is **{pin}**. When your driver arrives, either scan their QR code (your camera will show the number instantly) or just ask them to read it out — it should match. If it does, you're in the right car."
|
|
49
62
|
|
|
50
63
|
## Timing
|
|
51
64
|
|
|
@@ -34,38 +34,48 @@ line_items[0][price_data][product_data][description]={route} on {date}
|
|
|
34
34
|
line_items[0][quantity]=1
|
|
35
35
|
metadata[booking_id]={job_id}
|
|
36
36
|
metadata[tourist_phone]={tourist_phone}
|
|
37
|
+
metadata[account_id]={whatsapp_account_id}
|
|
37
38
|
metadata[negotiated_fare]={fare_usd}
|
|
39
|
+
success_url=https://zanzibar.beagle.taxi/booking-confirmed
|
|
40
|
+
cancel_url=https://zanzibar.beagle.taxi/booking-cancelled
|
|
38
41
|
```
|
|
39
42
|
|
|
40
|
-
The
|
|
43
|
+
The `stripe` API key from `api_keys` provides the secret key.
|
|
41
44
|
|
|
42
|
-
The response includes a `url` field —
|
|
45
|
+
The response includes a `url` field — the hosted checkout page to send the tourist.
|
|
46
|
+
|
|
47
|
+
## Metadata Fields
|
|
48
|
+
|
|
49
|
+
| Field | Purpose |
|
|
50
|
+
|-------|---------|
|
|
51
|
+
| `booking_id` | Links payment to the booking record (`BGL-XXXX`) |
|
|
52
|
+
| `tourist_phone` | For post-payment messaging (E.164 format) |
|
|
53
|
+
| `account_id` | WhatsApp account ID for delivery routing |
|
|
54
|
+
| `negotiated_fare` | Full fare amount (paid to driver, not via Stripe) |
|
|
55
|
+
|
|
56
|
+
These metadata fields are returned in webhook events, enabling the gateway to route the payment confirmation to the correct booking workflow.
|
|
43
57
|
|
|
44
58
|
## Sending the Link
|
|
45
59
|
|
|
46
|
-
|
|
60
|
+
Message the tourist with the checkout URL:
|
|
47
61
|
|
|
48
62
|
> "To confirm your booking, pay the $[fee] booking fee here: [url]
|
|
49
63
|
> This locks in your ride. You pay $[fare] directly to your driver at the end of the journey."
|
|
50
64
|
|
|
51
|
-
##
|
|
52
|
-
|
|
53
|
-
Before releasing driver details (Phase 5), verify the session is paid:
|
|
65
|
+
## Adaptive Pricing (Local Currency Display)
|
|
54
66
|
|
|
55
|
-
|
|
56
|
-
GET https://api.stripe.com/v1/checkout/sessions/{session_id}
|
|
57
|
-
Authorization: Bearer {stripe_secret_key}
|
|
58
|
-
```
|
|
67
|
+
Stripe's adaptive pricing is enabled by default. Tourists in the UK, EU, or other non-USD regions will see the booking fee in their local currency (e.g. £1.50 instead of $2.00 — the FX equivalent). The amount you receive is always the USD equivalent after conversion.
|
|
59
68
|
|
|
60
|
-
|
|
69
|
+
This is intentional and improves the tourist experience — no action needed. If the operator wants to force USD for all customers, they can disable it: Stripe Dashboard → Settings → Adaptive pricing → off.
|
|
61
70
|
|
|
62
71
|
## What to Store in the Booking Record
|
|
63
72
|
|
|
64
|
-
Add
|
|
73
|
+
Add to `shared/bookings/{job-id}.md`:
|
|
65
74
|
|
|
66
75
|
```
|
|
67
76
|
stripe_session_id: cs_...
|
|
68
|
-
booking_fee_usd:
|
|
69
|
-
payment_status: pending
|
|
70
|
-
paid_at: [timestamp from session]
|
|
77
|
+
booking_fee_usd: X.XX
|
|
78
|
+
payment_status: pending
|
|
71
79
|
```
|
|
80
|
+
|
|
81
|
+
Update `payment_status` to `paid` and add `paid_at` timestamp when the webhook confirms payment.
|