Handle store.menu_refresh_request webhook with store metadata tracking

This commit is contained in:
MOHAN 2026-03-29 18:45:49 +05:30
parent 64f214f3ae
commit bc3d4e6641
6 changed files with 102 additions and 1 deletions

View File

@ -16,6 +16,10 @@ Source checked: Uber Eats "Webhook" section shared by you.
- `resource_id` - `resource_id`
- `resource_href` - `resource_href`
- signature and dedupe key - signature and dedupe key
- Added explicit event handling for `store.menu_refresh_request`:
- mapped via `store_id`
- records latest menu refresh request metadata on `uber_connections`
- stores webhook UUID and `X-Environment` marker
## Existing Before ## Existing Before
@ -27,4 +31,3 @@ Source checked: Uber Eats "Webhook" section shared by you.
- Per-event downstream job workers (accept/deny SLA orchestration). - Per-event downstream job workers (accept/deny SLA orchestration).
- Alerting if order accept/deny not sent before timeout window. - Alerting if order accept/deny not sent before timeout window.
- Full typed schema validation per webhook `event_type`. - Full typed schema validation per webhook `event_type`.

View File

@ -25,8 +25,17 @@ Common event types handled:
- `delivery.state_changed` - `delivery.state_changed`
- `store.provisioned` - `store.provisioned`
- `store.deprovisioned` - `store.deprovisioned`
- `store.menu_refresh_request`
- `store.status.changed` - `store.status.changed`
Menu refresh handling:
- On `store.menu_refresh_request`, wrapper records the request on the mapped Uber store connection.
- Persisted fields include:
- `last_menu_refresh_requested_at`
- `last_menu_refresh_webhook_uuid`
- `last_webhook_environment` (from `X-Environment`)
Retail fulfillment follow-up: Retail fulfillment follow-up:
- On `orders.fulfillment_issues.resolved`, fetch updated order details and inspect customer acknowledgment before next action. - On `orders.fulfillment_issues.resolved`, fetch updated order details and inspect customer acknowledgment before next action.

View File

@ -1659,6 +1659,42 @@
} }
} }
}, },
{
"name": "Webhook Ingest - Menu Refresh (Simulation)",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "X-Uber-Signature",
"value": "replace-with-valid-hmac"
},
{
"key": "X-Environment",
"value": "production"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"event_type\": \"store.menu_refresh_request\",\n \"partner_store_id\": \"123456\",\n \"resource_href\": \"https://api.uber.com/v1/eats/stores/{{storeId}}\",\n \"store_id\": \"{{storeId}}\",\n \"webhook_meta\": {\n \"client_id\": \"app_client_id\",\n \"webhook_config_id\": \"merchant-integration.menu-refresh-request\",\n \"webhook_msg_timestamp\": 1622813397,\n \"webhook_msg_uuid\": \"b2340f4c-6dd7-4d65-a9bc-016631a2a13a\"\n }\n}"
},
"url": {
"raw": "{{baseUrl}}/api/v1/webhooks/uber",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"v1",
"webhooks",
"uber"
]
}
}
},
{ {
"name": "Generic Uber Request", "name": "Generic Uber Request",
"request": { "request": {

View File

@ -107,6 +107,31 @@ const uberConnectionRepository = {
return this.findByMerchantId(merchantId); return this.findByMerchantId(merchantId);
}, },
markMenuRefreshRequestedByStoreId(uberStoreId, payload) {
const existing = this.findByUberStoreId(uberStoreId);
if (!existing) {
return null;
}
const timestamp = nowIso();
db.prepare(
`
UPDATE uber_connections
SET last_menu_refresh_requested_at = ?,
last_menu_refresh_webhook_uuid = ?,
last_webhook_environment = ?,
updated_at = ?
WHERE uber_store_id = ?
`
).run(
payload?.requestedAt || timestamp,
payload?.webhookMsgUuid || null,
payload?.environment || null,
timestamp,
uberStoreId
);
return this.findByUberStoreId(uberStoreId);
},
list() { list() {
return db return db
.prepare(` .prepare(`

View File

@ -120,6 +120,16 @@ function initSchema() {
if (!tableHasColumn("api_logs", "order_id")) { if (!tableHasColumn("api_logs", "order_id")) {
db.exec("ALTER TABLE api_logs ADD COLUMN order_id TEXT"); db.exec("ALTER TABLE api_logs ADD COLUMN order_id TEXT");
} }
if (!tableHasColumn("uber_connections", "last_menu_refresh_requested_at")) {
db.exec("ALTER TABLE uber_connections ADD COLUMN last_menu_refresh_requested_at TEXT");
}
if (!tableHasColumn("uber_connections", "last_menu_refresh_webhook_uuid")) {
db.exec("ALTER TABLE uber_connections ADD COLUMN last_menu_refresh_webhook_uuid TEXT");
}
if (!tableHasColumn("uber_connections", "last_webhook_environment")) {
db.exec("ALTER TABLE uber_connections ADD COLUMN last_webhook_environment TEXT");
}
} }
module.exports = { module.exports = {

View File

@ -87,6 +87,23 @@ function applyProvisioningStateFromWebhook(eventType, payload) {
uberConnectionRepository.setStatusByMerchantId(connection.merchant_id, nextStatus); uberConnectionRepository.setStatusByMerchantId(connection.merchant_id, nextStatus);
} }
function applyMenuRefreshRequestFromWebhook(eventType, payload, headers) {
if (eventType !== "store.menu_refresh_request") {
return;
}
const storeId = extractStoreId(payload);
if (!storeId) {
return;
}
uberConnectionRepository.markMenuRefreshRequestedByStoreId(String(storeId), {
requestedAt: new Date().toISOString(),
webhookMsgUuid: payload?.webhook_meta?.webhook_msg_uuid || null,
environment: headers?.["x-environment"] || null
});
}
async function handleUberWebhook(req, res) { async function handleUberWebhook(req, res) {
if (!verifyBasicAuthIfConfigured(req)) { if (!verifyBasicAuthIfConfigured(req)) {
return res.status(401).json({ return res.status(401).json({
@ -133,6 +150,7 @@ async function handleUberWebhook(req, res) {
}); });
applyProvisioningStateFromWebhook(eventType, req.body || {}); applyProvisioningStateFromWebhook(eventType, req.body || {});
applyMenuRefreshRequestFromWebhook(eventType, req.body || {}, req.headers || {});
return res.status(200).end(); return res.status(200).end();
} }