Align Update Item to v2 sparse item endpoint

This commit is contained in:
MOHAN 2026-03-29 18:39:27 +05:30
parent d80f9e9baf
commit 519f2f7169
8 changed files with 46 additions and 12 deletions

View File

@ -17,7 +17,9 @@ Source checked: Uber Eats "Menu Integration" section shared by you.
- validates known menu_type enum values - validates known menu_type enum values
- Individual item updates: - Individual item updates:
- `POST /api/v1/uber/menu/items` - `POST /api/v1/uber/menu/items`
- supports stock/price style item-level updates via Menu Items endpoint - aligned to upstream `POST /v2/eats/stores/{store_id}/menus/items/{item_id}`
- request body is sparse update (only provided fields are changed)
- supports `menu_type` enum for split delivery/pickup/dine-in menus
## Existing Before ## Existing Before

View File

@ -21,6 +21,13 @@ Current wrapper route for full replacement:
Item update route: Item update route:
- `POST /api/v1/uber/menu/items` - `POST /api/v1/uber/menu/items`
- upstream mapped to `POST /v2/eats/stores/{store_id}/menus/items/{item_id}`
- sparse update only (only provided fields are changed)
- wrapper payload shape:
- `merchantId`
- `storeId`
- `itemId`
- `update` object with fields such as `price_info`, `suspension_info`, `menu_type`, `product_info`, `classifications`, `beverage_info`, `physical_properties_info`, `medication_info`, `nutritional_info`, `selling_info`
Menu fetch route: Menu fetch route:

View File

@ -318,13 +318,13 @@
}, },
"/api/v1/uber/menu/items": { "/api/v1/uber/menu/items": {
"post": { "post": {
"summary": "Update individual menu items (stock/price updates)", "summary": "Update single menu item (POST /v2/eats/stores/{store_id}/menus/items/{item_id})",
"tags": [ "tags": [
"Uber Menu" "Uber Menu"
], ],
"responses": { "responses": {
"200": { "200": {
"description": "Menu items updated" "description": "Menu item sparsely updated (Uber returns 204 No Content)"
} }
} }
} }

View File

@ -200,7 +200,7 @@
} }
}, },
{ {
"name": "Update Menu Items", "name": "Update Item (v2 Sparse)",
"request": { "request": {
"method": "POST", "method": "POST",
"header": [ "header": [
@ -215,7 +215,7 @@
], ],
"body": { "body": {
"mode": "raw", "mode": "raw",
"raw": "{\n \"merchantId\": \"{{merchantId}}\",\n \"storeId\": \"{{storeId}}\",\n \"items\": [\n {\n \"id\": \"item_1\",\n \"price_info\": {\n \"price\": 799\n },\n \"suspension_info\": {\n \"suspend_until\": null\n }\n }\n ]\n}" "raw": "{\n \"merchantId\": \"{{merchantId}}\",\n \"storeId\": \"{{storeId}}\",\n \"itemId\": \"item_1\",\n \"update\": {\n \"menu_type\": \"MENU_TYPE_FULFILLMENT_DELIVERY\",\n \"price_info\": {\n \"price\": 799\n },\n \"suspension_info\": {\n \"suspension\": null\n }\n }\n}"
}, },
"url": { "url": {
"raw": "{{baseUrl}}/api/v1/uber/menu/items", "raw": "{{baseUrl}}/api/v1/uber/menu/items",

View File

@ -3,7 +3,7 @@ module.exports = {
upsert: "/v1/eats/stores/{storeId}/menus", upsert: "/v1/eats/stores/{storeId}/menus",
upload: "/v2/eats/stores/{storeId}/menus", upload: "/v2/eats/stores/{storeId}/menus",
get: "/v2/eats/stores/{storeId}/menus", get: "/v2/eats/stores/{storeId}/menus",
itemsUpdate: "/v1/eats/stores/{storeId}/menus/items" itemUpdate: "/v2/eats/stores/{storeId}/menus/items/{itemId}"
}, },
orders: { orders: {
list: "/v1/eats/stores/{storeId}/orders", list: "/v1/eats/stores/{storeId}/orders",

View File

@ -90,16 +90,41 @@ async function replaceMenu(req, res) {
} }
async function updateMenuItems(req, res) { async function updateMenuItems(req, res) {
const menuTypeEnum = z.enum([
"MENU_TYPE_FULFILLMENT_DELIVERY",
"MENU_TYPE_FULFILLMENT_PICK_UP",
"MENU_TYPE_FULFILLMENT_DINE_IN"
]);
const updateSchema = z
.object({
price_info: z.any().optional(),
suspension_info: z.any().optional(),
menu_type: menuTypeEnum.optional(),
product_info: z.any().optional(),
classifications: z.any().optional(),
beverage_info: z.any().optional(),
physical_properties_info: z.any().optional(),
medication_info: z.any().optional(),
nutritional_info: z.any().optional(),
selling_info: z.any().optional()
})
.refine((value) => Object.keys(value).length > 0, {
message: "update must include at least one sparse-update field"
});
const schema = z.object({ const schema = z.object({
merchantId: z.string().min(1), merchantId: z.string().min(1),
storeId: z.string().min(1), storeId: z.string().min(1),
items: z.array(z.any()).min(1) itemId: z.string().min(1),
update: updateSchema
}); });
const payload = schema.parse(req.body); const payload = schema.parse(req.body);
const data = await proxyService.updateMenuItems({ const data = await proxyService.updateMenuItems({
merchantId: payload.merchantId, merchantId: payload.merchantId,
storeId: payload.storeId, storeId: payload.storeId,
payload: { items: payload.items } itemId: payload.itemId,
payload: payload.update
}); });
return res.json({ success: true, data }); return res.json({ success: true, data });
} }

View File

@ -188,8 +188,8 @@ async function menuGet({ merchantId, storeId, menuType }) {
}); });
} }
async function updateMenuItems({ merchantId, storeId, payload }) { async function updateMenuItems({ merchantId, storeId, itemId, payload }) {
const uberPath = interpolatePath(uberEndpoints.menu.itemsUpdate, { storeId }); const uberPath = interpolatePath(uberEndpoints.menu.itemUpdate, { storeId, itemId });
return callUberApi({ return callUberApi({
merchantId, merchantId,
method: "POST", method: "POST",

View File

@ -47,12 +47,12 @@ router.put("/uber/menu/replace", asyncHandler(controller.replaceMenu));
* @openapi * @openapi
* /api/v1/uber/menu/items: * /api/v1/uber/menu/items:
* post: * post:
* summary: Update individual menu items (stock/price updates) * summary: Update single menu item (POST /v2/eats/stores/{store_id}/menus/items/{item_id})
* tags: * tags:
* - Uber Menu * - Uber Menu
* responses: * responses:
* 200: * 200:
* description: Menu items updated * description: Menu item sparsely updated (Uber returns 204 No Content)
*/ */
router.post("/uber/menu/items", asyncHandler(controller.updateMenuItems)); router.post("/uber/menu/items", asyncHandler(controller.updateMenuItems));