diff --git a/docs/developer-portal/06-order-api-1-0-0-audit.md b/docs/developer-portal/06-order-api-1-0-0-audit.md new file mode 100644 index 0000000..22ebf52 --- /dev/null +++ b/docs/developer-portal/06-order-api-1-0-0-audit.md @@ -0,0 +1,46 @@ +# 06 Order API 1.0.0 Audit + +Source checked: "Order Fulfillment API (1.0.0)" shared by you. + +## Implemented Now (Dedicated Wrapper Namespace) + +- Get Order Details: + - `GET /api/v1/uber/delivery-order/orders/{orderId}` + - upstream: `/v1/delivery/order/{order_id}` +- List Orders Details: + - `GET /api/v1/uber/delivery-order/stores/{storeId}/orders` + - upstream: `/v1/delivery/store/{store_id}/orders` +- Accept Order: + - `POST /api/v1/uber/delivery-order/orders/{orderId}/accept` +- Deny Order: + - `POST /api/v1/uber/delivery-order/orders/{orderId}/deny` +- Cancel Order: + - `POST /api/v1/uber/delivery-order/orders/{orderId}/cancel` +- Mark Order Ready: + - `POST /api/v1/uber/delivery-order/orders/{orderId}/ready` +- Adjust Order Price: + - `POST /api/v1/uber/delivery-order/orders/{orderId}/adjust-price` +- Update Order Ready Time: + - `POST /api/v1/uber/delivery-order/orders/{orderId}/update-ready-time` +- Resolve Fulfillment Issues: + - `POST /api/v1/uber/delivery-order/orders/{orderId}/resolve-fulfillment-issues` +- Get Replacement Recommendations: + - `POST /api/v1/uber/delivery-order/replacement-recommendations` + +## Validation Added + +- adjust-price reasons enum and `custom_reason` required when reason is `OTHER` +- update-ready-time requires `ready_for_pickup_time` +- deny/cancel reason object shape validation +- list orders page size max enforced (`<= 50`) + +## Existing Before + +- Legacy order routes under `/api/v1/uber/orders...` +- Retail fulfillment helpers and related webhooks + +## Pending + +- Response normalization nuances for `204 No Content` cancel semantics if required by consuming clients +- Deeper typed schemas for webhook metadata and fulfillment issue object variants + diff --git a/docs/developer-portal/06-orders.md b/docs/developer-portal/06-orders.md index 8316d4f..30d3ad2 100644 --- a/docs/developer-portal/06-orders.md +++ b/docs/developer-portal/06-orders.md @@ -26,3 +26,16 @@ Retail fulfillment guidance: - Read customer preference (`REPLACE_FOR_ME`, `SUBSTITUTE_ME`, `REMOVE_ITEM`) from order details. - Update issue states via fulfillment endpoint (`FOUND_ITEM`, `PARTIAL_AVAILABILITY`, `OUT_OF_ITEM`). - On `orders.fulfillment_issues.resolved` webhook, fetch latest order and continue resolution. + +Order API 1.0.0 coverage (delivery namespace): + +- `GET /api/v1/uber/delivery-order/orders/{orderId}` +- `GET /api/v1/uber/delivery-order/stores/{storeId}/orders` +- `POST /api/v1/uber/delivery-order/orders/{orderId}/accept` +- `POST /api/v1/uber/delivery-order/orders/{orderId}/deny` +- `POST /api/v1/uber/delivery-order/orders/{orderId}/cancel` +- `POST /api/v1/uber/delivery-order/orders/{orderId}/ready` +- `POST /api/v1/uber/delivery-order/orders/{orderId}/adjust-price` +- `POST /api/v1/uber/delivery-order/orders/{orderId}/update-ready-time` +- `POST /api/v1/uber/delivery-order/orders/{orderId}/resolve-fulfillment-issues` +- `POST /api/v1/uber/delivery-order/replacement-recommendations` diff --git a/docs/developer-portal/07-webhooks.md b/docs/developer-portal/07-webhooks.md index ad03af5..0652626 100644 --- a/docs/developer-portal/07-webhooks.md +++ b/docs/developer-portal/07-webhooks.md @@ -22,6 +22,7 @@ Common event types handled: - `orders.release` - `orders.scheduled.notification` - `orders.cancel` +- `delivery.state_changed` - `store.provisioned` - `store.deprovisioned` - `store.status.changed` diff --git a/docs/openapi/openapi.json b/docs/openapi/openapi.json index 10af0fc..bd31b28 100644 --- a/docs/openapi/openapi.json +++ b/docs/openapi/openapi.json @@ -840,6 +840,156 @@ } } }, + "/api/v1/uber/delivery-order/orders/{orderId}": { + "get": { + "summary": "Order API 1.0.0 - Get order details", + "tags": [ + "Uber Delivery Order v1" + ], + "parameters": [ + { + "in": "path", + "name": "orderId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Order details retrieved" + } + } + } + }, + "/api/v1/uber/delivery-order/stores/{storeId}/orders": { + "get": { + "summary": "Order API 1.0.0 - List store orders with details", + "tags": [ + "Uber Delivery Order v1" + ], + "parameters": [ + { + "in": "path", + "name": "storeId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Orders listed" + } + } + } + }, + "/api/v1/uber/delivery-order/orders/{orderId}/accept": { + "post": { + "summary": "Order API 1.0.0 - Accept order", + "tags": [ + "Uber Delivery Order v1" + ], + "responses": { + "200": { + "description": "Order accepted" + } + } + } + }, + "/api/v1/uber/delivery-order/orders/{orderId}/deny": { + "post": { + "summary": "Order API 1.0.0 - Deny order", + "tags": [ + "Uber Delivery Order v1" + ], + "responses": { + "200": { + "description": "Order denied" + } + } + } + }, + "/api/v1/uber/delivery-order/orders/{orderId}/cancel": { + "post": { + "summary": "Order API 1.0.0 - Cancel order", + "tags": [ + "Uber Delivery Order v1" + ], + "responses": { + "200": { + "description": "Order canceled" + } + } + } + }, + "/api/v1/uber/delivery-order/orders/{orderId}/ready": { + "post": { + "summary": "Order API 1.0.0 - Mark order ready", + "tags": [ + "Uber Delivery Order v1" + ], + "responses": { + "200": { + "description": "Order ready" + } + } + } + }, + "/api/v1/uber/delivery-order/orders/{orderId}/adjust-price": { + "post": { + "summary": "Order API 1.0.0 - Adjust order price", + "tags": [ + "Uber Delivery Order v1" + ], + "responses": { + "200": { + "description": "Order price adjusted" + } + } + } + }, + "/api/v1/uber/delivery-order/orders/{orderId}/update-ready-time": { + "post": { + "summary": "Order API 1.0.0 - Update order ready time", + "tags": [ + "Uber Delivery Order v1" + ], + "responses": { + "200": { + "description": "Order ready time updated" + } + } + } + }, + "/api/v1/uber/delivery-order/orders/{orderId}/resolve-fulfillment-issues": { + "post": { + "summary": "Order API 1.0.0 - Resolve fulfillment issues", + "tags": [ + "Uber Delivery Order v1" + ], + "responses": { + "200": { + "description": "Fulfillment issue resolution submitted" + } + } + } + }, + "/api/v1/uber/delivery-order/replacement-recommendations": { + "post": { + "summary": "Order API 1.0.0 - Get replacement recommendations", + "tags": [ + "Uber Delivery Order v1" + ], + "responses": { + "200": { + "description": "Replacement recommendations returned" + } + } + } + }, "/api/v1/uber/reporting/fetch": { "post": { "summary": "Fetch Uber reporting CSV with retries and header-based parsing", diff --git a/postman/Uber_Wrapper.postman_collection.json b/postman/Uber_Wrapper.postman_collection.json index 8c7be56..8fb1db3 100644 --- a/postman/Uber_Wrapper.postman_collection.json +++ b/postman/Uber_Wrapper.postman_collection.json @@ -516,6 +516,349 @@ } } }, + { + "name": "Delivery Order API - Get Order Details", + "request": { + "method": "GET", + "header": [ + { + "key": "x-api-key", + "value": "{{apiKey}}" + } + ], + "url": { + "raw": "{{baseUrl}}/api/v1/uber/delivery-order/orders/{{orderId}}?expand=carts,deliveries,payment", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v1", + "uber", + "delivery-order", + "orders", + "{{orderId}}" + ], + "query": [ + { + "key": "expand", + "value": "carts,deliveries,payment" + } + ] + } + } + }, + { + "name": "Delivery Order API - List Store Orders", + "request": { + "method": "GET", + "header": [ + { + "key": "x-api-key", + "value": "{{apiKey}}" + } + ], + "url": { + "raw": "{{baseUrl}}/api/v1/uber/delivery-order/stores/{{storeId}}/orders?page_size=50", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v1", + "uber", + "delivery-order", + "stores", + "{{storeId}}", + "orders" + ], + "query": [ + { + "key": "page_size", + "value": "50" + } + ] + } + } + }, + { + "name": "Delivery Order API - Accept", + "request": { + "method": "POST", + "header": [ + { + "key": "x-api-key", + "value": "{{apiKey}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ready_for_pickup_time\": \"2026-04-01T18:50:05.000Z\",\n \"accepted_by\": \"John Smith\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/api/v1/uber/delivery-order/orders/{{orderId}}/accept", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v1", + "uber", + "delivery-order", + "orders", + "{{orderId}}", + "accept" + ] + } + } + }, + { + "name": "Delivery Order API - Deny", + "request": { + "method": "POST", + "header": [ + { + "key": "x-api-key", + "value": "{{apiKey}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"deny_reason\": {\n \"type\": \"ITEM_ISSUE\",\n \"info\": \"Item is not available\"\n }\n}" + }, + "url": { + "raw": "{{baseUrl}}/api/v1/uber/delivery-order/orders/{{orderId}}/deny", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v1", + "uber", + "delivery-order", + "orders", + "{{orderId}}", + "deny" + ] + } + } + }, + { + "name": "Delivery Order API - Cancel", + "request": { + "method": "POST", + "header": [ + { + "key": "x-api-key", + "value": "{{apiKey}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"cancellation_reason\": {\n \"type\": \"ITEM_ISSUE\",\n \"info\": \"Item sold out\"\n }\n}" + }, + "url": { + "raw": "{{baseUrl}}/api/v1/uber/delivery-order/orders/{{orderId}}/cancel", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v1", + "uber", + "delivery-order", + "orders", + "{{orderId}}", + "cancel" + ] + } + } + }, + { + "name": "Delivery Order API - Ready", + "request": { + "method": "POST", + "header": [ + { + "key": "x-api-key", + "value": "{{apiKey}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{}" + }, + "url": { + "raw": "{{baseUrl}}/api/v1/uber/delivery-order/orders/{{orderId}}/ready", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v1", + "uber", + "delivery-order", + "orders", + "{{orderId}}", + "ready" + ] + } + } + }, + { + "name": "Delivery Order API - Adjust Price", + "request": { + "method": "POST", + "header": [ + { + "key": "x-api-key", + "value": "{{apiKey}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"amount_e5\": -500000,\n \"reason\": \"ITEM_SOLD_OUT\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/api/v1/uber/delivery-order/orders/{{orderId}}/adjust-price", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v1", + "uber", + "delivery-order", + "orders", + "{{orderId}}", + "adjust-price" + ] + } + } + }, + { + "name": "Delivery Order API - Update Ready Time", + "request": { + "method": "POST", + "header": [ + { + "key": "x-api-key", + "value": "{{apiKey}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ready_for_pickup_time\": \"2026-04-01T18:50:05.000Z\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/api/v1/uber/delivery-order/orders/{{orderId}}/update-ready-time", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v1", + "uber", + "delivery-order", + "orders", + "{{orderId}}", + "update-ready-time" + ] + } + } + }, + { + "name": "Delivery Order API - Resolve Fulfillment Issues", + "request": { + "method": "POST", + "header": [ + { + "key": "x-api-key", + "value": "{{apiKey}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"fulfillment_issues\": [\n {}\n ]\n}" + }, + "url": { + "raw": "{{baseUrl}}/api/v1/uber/delivery-order/orders/{{orderId}}/resolve-fulfillment-issues", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v1", + "uber", + "delivery-order", + "orders", + "{{orderId}}", + "resolve-fulfillment-issues" + ] + } + } + }, + { + "name": "Delivery Order API - Replacement Recommendations", + "request": { + "method": "POST", + "header": [ + { + "key": "x-api-key", + "value": "{{apiKey}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"id\": \"3679399\",\n \"order_id\": \"{{orderId}}\",\n \"store_id\": \"{{storeId}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/api/v1/uber/delivery-order/replacement-recommendations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v1", + "uber", + "delivery-order", + "replacement-recommendations" + ] + } + } + }, { "name": "Get Store By ID", "request": { diff --git a/src/config/uberEndpoints.js b/src/config/uberEndpoints.js index 7a50b43..9b4b232 100644 --- a/src/config/uberEndpoints.js +++ b/src/config/uberEndpoints.js @@ -31,6 +31,18 @@ module.exports = { updatePrepTime: "/v1/delivery/store/{storeId}/update-store-prep-time", updateFulfillmentConfig: "/v1/delivery/store/{storeId}/update-fulfillment-configuration" }, + deliveryOrder: { + getById: "/v1/delivery/order/{orderId}", + listByStore: "/v1/delivery/store/{storeId}/orders", + accept: "/v1/delivery/order/{orderId}/accept", + deny: "/v1/delivery/order/{orderId}/deny", + cancel: "/v1/delivery/order/{orderId}/cancel", + ready: "/v1/delivery/order/{orderId}/ready", + adjustPrice: "/v1/delivery/order/{orderId}/adjust-price", + updateReadyTime: "/v1/delivery/order/{orderId}/update-ready-time", + resolveFulfillmentIssues: "/v1/delivery/order/{orderId}/resolve-fulfillment-issues", + replacementRecommendations: "/v1/delivery/get-replacement-recommendations" + }, webhooks: { events: "/v1/eats/stores/{storeId}/event_feed" } diff --git a/src/modules/proxy/proxy.controller.js b/src/modules/proxy/proxy.controller.js index 83460a9..8b2d7f1 100644 --- a/src/modules/proxy/proxy.controller.js +++ b/src/modules/proxy/proxy.controller.js @@ -444,6 +444,159 @@ async function deliveryUpdateFulfillmentConfig(req, res) { return res.json({ success: true, data }); } +async function deliveryGetOrderDetails(req, res) { + const schema = z.object({ + expand: z.string().optional() + }); + const query = schema.parse(req.query || {}); + const data = await proxyService.deliveryGetOrderDetails({ + orderId: req.params.orderId, + query + }); + return res.json({ success: true, data }); +} + +async function deliveryListOrders(req, res) { + const schema = z.object({ + expand: z.string().optional(), + state: z.string().optional(), + status: z.string().optional(), + start_time: z.string().optional(), + end_time: z.string().optional(), + next_page_token: z.string().optional(), + page_size: z.coerce.number().int().min(1).max(50).optional() + }); + const query = schema.parse(req.query || {}); + const data = await proxyService.deliveryListOrders({ + storeId: req.params.storeId, + query + }); + return res.json({ success: true, data }); +} + +async function deliveryAcceptOrder(req, res) { + const schema = z.object({ + ready_for_pickup_time: z.string().optional(), + external_reference_id: z.string().optional(), + accepted_by: z.string().optional(), + order_pickup_instructions: z.string().optional() + }); + const payload = schema.parse(req.body || {}); + const data = await proxyService.deliveryAcceptOrder({ + orderId: req.params.orderId, + payload + }); + return res.json({ success: true, data }); +} + +async function deliveryDenyOrder(req, res) { + const denyReasonSchema = z.object({ + info: z.string().optional(), + type: z.string().min(1), + client_error_code: z.string().optional(), + item_metadata: z.record(z.string(), z.any()).optional() + }); + const schema = z.object({ + deny_reason: denyReasonSchema + }); + const payload = schema.parse(req.body || {}); + const data = await proxyService.deliveryDenyOrder({ + orderId: req.params.orderId, + payload + }); + return res.json({ success: true, data }); +} + +async function deliveryCancelOrder(req, res) { + const cancelReasonSchema = z.object({ + info: z.string().optional(), + type: z.string().min(1), + client_error_code: z.string().optional(), + item_metadata: z.record(z.string(), z.any()).optional() + }); + const schema = z.object({ + cancellation_reason: cancelReasonSchema + }); + const payload = schema.parse(req.body || {}); + const data = await proxyService.deliveryCancelOrder({ + orderId: req.params.orderId, + payload + }); + return res.json({ success: true, data }); +} + +async function deliveryMarkOrderReady(req, res) { + const data = await proxyService.deliveryMarkOrderReady({ + orderId: req.params.orderId, + payload: req.body || {} + }); + return res.json({ success: true, data }); +} + +async function deliveryAdjustOrderPrice(req, res) { + const schema = z + .object({ + amount_e5: z.coerce.number(), + tax_rate: z.string().optional(), + reason: z.enum([ + "REQUESTED_ADD_ONS", + "BIGGER_SIZE", + "NEW_ITEM_ADDED", + "ITEM_SOLD_OUT", + "REMOVED_ITEM", + "ADD_ON_UNAVAILABLE", + "OTHER" + ]), + custom_reason: z.string().optional() + }) + .refine((value) => value.reason !== "OTHER" || Boolean(value.custom_reason), { + message: "custom_reason is required when reason is OTHER" + }); + const payload = schema.parse(req.body || {}); + const data = await proxyService.deliveryAdjustOrderPrice({ + orderId: req.params.orderId, + payload + }); + return res.json({ success: true, data }); +} + +async function deliveryUpdateReadyTime(req, res) { + const schema = z.object({ + ready_for_pickup_time: z.string().min(1) + }); + const payload = schema.parse(req.body || {}); + const data = await proxyService.deliveryUpdateReadyTime({ + orderId: req.params.orderId, + payload + }); + return res.json({ success: true, data }); +} + +async function deliveryResolveFulfillmentIssues(req, res) { + const schema = z.object({ + fulfillment_issues: z.array(z.any()).min(1) + }); + const payload = schema.parse(req.body || {}); + const data = await proxyService.deliveryResolveFulfillmentIssues({ + orderId: req.params.orderId, + payload + }); + return res.json({ success: true, data }); +} + +async function deliveryGetReplacementRecommendations(req, res) { + const schema = z.object({ + id: z.string().min(1), + order_id: z.string().min(1), + store_id: z.string().min(1) + }); + const payload = schema.parse(req.body || {}); + const data = await proxyService.deliveryGetReplacementRecommendations({ + payload + }); + return res.json({ success: true, data }); +} + module.exports = { genericProxy, upsertMenu, @@ -473,5 +626,15 @@ module.exports = { deliveryGetStoreStatus, deliverySetStoreStatus, deliveryUpdatePrepTime, - deliveryUpdateFulfillmentConfig + deliveryUpdateFulfillmentConfig, + deliveryGetOrderDetails, + deliveryListOrders, + deliveryAcceptOrder, + deliveryDenyOrder, + deliveryCancelOrder, + deliveryMarkOrderReady, + deliveryAdjustOrderPrice, + deliveryUpdateReadyTime, + deliveryResolveFulfillmentIssues, + deliveryGetReplacementRecommendations }; diff --git a/src/modules/proxy/proxy.service.js b/src/modules/proxy/proxy.service.js index 0d09e03..5bc3707 100644 --- a/src/modules/proxy/proxy.service.js +++ b/src/modules/proxy/proxy.service.js @@ -470,6 +470,125 @@ async function deliveryUpdateFulfillmentConfig({ storeId, payload }) { }); } +async function deliveryGetOrderDetails({ orderId, query }) { + const uberPath = interpolatePath(uberEndpoints.deliveryOrder.getById, { orderId }); + return callUberApi({ + method: "GET", + uberPath, + query, + wrapperRoute: "/api/v1/uber/delivery-order/orders/:orderId", + authMode: "app", + scopes: AUTH_SCOPES.ORDER + }); +} + +async function deliveryListOrders({ storeId, query }) { + const uberPath = interpolatePath(uberEndpoints.deliveryOrder.listByStore, { storeId }); + return callUberApi({ + method: "GET", + uberPath, + query, + wrapperRoute: "/api/v1/uber/delivery-order/stores/:storeId/orders", + authMode: "app", + scopes: AUTH_SCOPES.ORDER + }); +} + +async function deliveryAcceptOrder({ orderId, payload }) { + const uberPath = interpolatePath(uberEndpoints.deliveryOrder.accept, { orderId }); + return callUberApi({ + method: "POST", + uberPath, + body: payload || {}, + wrapperRoute: "/api/v1/uber/delivery-order/orders/:orderId/accept", + authMode: "app", + scopes: AUTH_SCOPES.ORDER + }); +} + +async function deliveryDenyOrder({ orderId, payload }) { + const uberPath = interpolatePath(uberEndpoints.deliveryOrder.deny, { orderId }); + return callUberApi({ + method: "POST", + uberPath, + body: payload, + wrapperRoute: "/api/v1/uber/delivery-order/orders/:orderId/deny", + authMode: "app", + scopes: AUTH_SCOPES.ORDER + }); +} + +async function deliveryCancelOrder({ orderId, payload }) { + const uberPath = interpolatePath(uberEndpoints.deliveryOrder.cancel, { orderId }); + return callUberApi({ + method: "POST", + uberPath, + body: payload, + wrapperRoute: "/api/v1/uber/delivery-order/orders/:orderId/cancel", + authMode: "app", + scopes: AUTH_SCOPES.ORDER + }); +} + +async function deliveryMarkOrderReady({ orderId, payload }) { + const uberPath = interpolatePath(uberEndpoints.deliveryOrder.ready, { orderId }); + return callUberApi({ + method: "POST", + uberPath, + body: payload || {}, + wrapperRoute: "/api/v1/uber/delivery-order/orders/:orderId/ready", + authMode: "app", + scopes: AUTH_SCOPES.ORDER + }); +} + +async function deliveryAdjustOrderPrice({ orderId, payload }) { + const uberPath = interpolatePath(uberEndpoints.deliveryOrder.adjustPrice, { orderId }); + return callUberApi({ + method: "POST", + uberPath, + body: payload, + wrapperRoute: "/api/v1/uber/delivery-order/orders/:orderId/adjust-price", + authMode: "app", + scopes: AUTH_SCOPES.ORDER + }); +} + +async function deliveryUpdateReadyTime({ orderId, payload }) { + const uberPath = interpolatePath(uberEndpoints.deliveryOrder.updateReadyTime, { orderId }); + return callUberApi({ + method: "POST", + uberPath, + body: payload, + wrapperRoute: "/api/v1/uber/delivery-order/orders/:orderId/update-ready-time", + authMode: "app", + scopes: AUTH_SCOPES.ORDER + }); +} + +async function deliveryResolveFulfillmentIssues({ orderId, payload }) { + const uberPath = interpolatePath(uberEndpoints.deliveryOrder.resolveFulfillmentIssues, { orderId }); + return callUberApi({ + method: "POST", + uberPath, + body: payload, + wrapperRoute: "/api/v1/uber/delivery-order/orders/:orderId/resolve-fulfillment-issues", + authMode: "app", + scopes: AUTH_SCOPES.ORDER + }); +} + +async function deliveryGetReplacementRecommendations({ payload }) { + return callUberApi({ + method: "POST", + uberPath: uberEndpoints.deliveryOrder.replacementRecommendations, + body: payload, + wrapperRoute: "/api/v1/uber/delivery-order/replacement-recommendations", + authMode: "app", + scopes: AUTH_SCOPES.ORDER + }); +} + module.exports = { genericProxy, menuUpsert, @@ -499,5 +618,15 @@ module.exports = { deliveryGetStoreStatus, deliverySetStoreStatus, deliveryUpdatePrepTime, - deliveryUpdateFulfillmentConfig + deliveryUpdateFulfillmentConfig, + deliveryGetOrderDetails, + deliveryListOrders, + deliveryAcceptOrder, + deliveryDenyOrder, + deliveryCancelOrder, + deliveryMarkOrderReady, + deliveryAdjustOrderPrice, + deliveryUpdateReadyTime, + deliveryResolveFulfillmentIssues, + deliveryGetReplacementRecommendations }; diff --git a/src/modules/webhooks/webhooks.controller.js b/src/modules/webhooks/webhooks.controller.js index 6aae8ed..99bac30 100644 --- a/src/modules/webhooks/webhooks.controller.js +++ b/src/modules/webhooks/webhooks.controller.js @@ -105,7 +105,12 @@ async function handleUberWebhook(req, res) { const merchantId = req.query.merchantId || req.body?.merchant_id || null; const eventType = req.body?.event_type || req.body?.type || "unknown"; - const resourceId = req.body?.resource_id || req.body?.order_id || req.body?.order?.id || null; + const resourceId = + req.body?.resource_id || + req.body?.meta?.resource_id || + req.body?.order_id || + req.body?.order?.id || + null; const resourceHref = req.body?.resource_href || null; const dedupeKey = buildDedupeKey(verification.signature, req); diff --git a/src/routes/proxy.routes.js b/src/routes/proxy.routes.js index 2ff822e..37f8d71 100644 --- a/src/routes/proxy.routes.js +++ b/src/routes/proxy.routes.js @@ -481,4 +481,173 @@ router.post( asyncHandler(controller.deliveryUpdateFulfillmentConfig) ); +/** + * @openapi + * /api/v1/uber/delivery-order/orders/{orderId}: + * get: + * summary: Order API 1.0.0 - Get order details + * tags: + * - Uber Delivery Order v1 + * parameters: + * - in: path + * name: orderId + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Order details retrieved + */ +router.get("/uber/delivery-order/orders/:orderId", asyncHandler(controller.deliveryGetOrderDetails)); + +/** + * @openapi + * /api/v1/uber/delivery-order/stores/{storeId}/orders: + * get: + * summary: Order API 1.0.0 - List store orders with details + * tags: + * - Uber Delivery Order v1 + * parameters: + * - in: path + * name: storeId + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Orders listed + */ +router.get( + "/uber/delivery-order/stores/:storeId/orders", + asyncHandler(controller.deliveryListOrders) +); + +/** + * @openapi + * /api/v1/uber/delivery-order/orders/{orderId}/accept: + * post: + * summary: Order API 1.0.0 - Accept order + * tags: + * - Uber Delivery Order v1 + * responses: + * 200: + * description: Order accepted + */ +router.post( + "/uber/delivery-order/orders/:orderId/accept", + asyncHandler(controller.deliveryAcceptOrder) +); + +/** + * @openapi + * /api/v1/uber/delivery-order/orders/{orderId}/deny: + * post: + * summary: Order API 1.0.0 - Deny order + * tags: + * - Uber Delivery Order v1 + * responses: + * 200: + * description: Order denied + */ +router.post( + "/uber/delivery-order/orders/:orderId/deny", + asyncHandler(controller.deliveryDenyOrder) +); + +/** + * @openapi + * /api/v1/uber/delivery-order/orders/{orderId}/cancel: + * post: + * summary: Order API 1.0.0 - Cancel order + * tags: + * - Uber Delivery Order v1 + * responses: + * 200: + * description: Order canceled + */ +router.post( + "/uber/delivery-order/orders/:orderId/cancel", + asyncHandler(controller.deliveryCancelOrder) +); + +/** + * @openapi + * /api/v1/uber/delivery-order/orders/{orderId}/ready: + * post: + * summary: Order API 1.0.0 - Mark order ready + * tags: + * - Uber Delivery Order v1 + * responses: + * 200: + * description: Order ready + */ +router.post( + "/uber/delivery-order/orders/:orderId/ready", + asyncHandler(controller.deliveryMarkOrderReady) +); + +/** + * @openapi + * /api/v1/uber/delivery-order/orders/{orderId}/adjust-price: + * post: + * summary: Order API 1.0.0 - Adjust order price + * tags: + * - Uber Delivery Order v1 + * responses: + * 200: + * description: Order price adjusted + */ +router.post( + "/uber/delivery-order/orders/:orderId/adjust-price", + asyncHandler(controller.deliveryAdjustOrderPrice) +); + +/** + * @openapi + * /api/v1/uber/delivery-order/orders/{orderId}/update-ready-time: + * post: + * summary: Order API 1.0.0 - Update order ready time + * tags: + * - Uber Delivery Order v1 + * responses: + * 200: + * description: Order ready time updated + */ +router.post( + "/uber/delivery-order/orders/:orderId/update-ready-time", + asyncHandler(controller.deliveryUpdateReadyTime) +); + +/** + * @openapi + * /api/v1/uber/delivery-order/orders/{orderId}/resolve-fulfillment-issues: + * post: + * summary: Order API 1.0.0 - Resolve fulfillment issues + * tags: + * - Uber Delivery Order v1 + * responses: + * 200: + * description: Fulfillment issue resolution submitted + */ +router.post( + "/uber/delivery-order/orders/:orderId/resolve-fulfillment-issues", + asyncHandler(controller.deliveryResolveFulfillmentIssues) +); + +/** + * @openapi + * /api/v1/uber/delivery-order/replacement-recommendations: + * post: + * summary: Order API 1.0.0 - Get replacement recommendations + * tags: + * - Uber Delivery Order v1 + * responses: + * 200: + * description: Replacement recommendations returned + */ +router.post( + "/uber/delivery-order/replacement-recommendations", + asyncHandler(controller.deliveryGetReplacementRecommendations) +); + module.exports = router;