feat: add typed retail fulfillment issue and order-ready endpoints
This commit is contained in:
parent
6b768bfbf5
commit
8eb11897a5
@ -12,9 +12,17 @@ Order flow for POS:
|
||||
Typed routes:
|
||||
|
||||
- `GET /api/v1/uber/orders/{orderId}` (order details)
|
||||
- `POST /api/v1/uber/orders/{orderId}/fulfillment-issues`
|
||||
- `POST /api/v1/uber/orders/{orderId}/ready`
|
||||
- `POST /api/v1/uber/orders/{orderId}/action` with action:
|
||||
- `accept`
|
||||
- `deny`
|
||||
- `ready`
|
||||
- `cancel`
|
||||
- `resolve`
|
||||
|
||||
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.
|
||||
|
||||
38
docs/developer-portal/06-retail-fulfillment-audit.md
Normal file
38
docs/developer-portal/06-retail-fulfillment-audit.md
Normal file
@ -0,0 +1,38 @@
|
||||
# 06 Retail Fulfillment Audit
|
||||
|
||||
Source checked: Uber Eats "Retail Order Fulfillment API Guide" section shared by you.
|
||||
|
||||
## Implemented Now
|
||||
|
||||
- Dedicated fulfillment issues endpoint:
|
||||
- `POST /api/v1/uber/orders/{orderId}/fulfillment-issues`
|
||||
- Supported issue types in validation:
|
||||
- `FOUND_ITEM`
|
||||
- `PARTIAL_AVAILABILITY`
|
||||
- `OUT_OF_ITEM`
|
||||
- Supported action types in validation:
|
||||
- `REPLACE_FOR_ME`
|
||||
- `SUBSTITUTE_ME`
|
||||
- `REMOVE_ITEM`
|
||||
- `ALTERNATIVE_ITEM`
|
||||
- `SUBSTITUTION_REJECTED`
|
||||
- Dedicated order-ready endpoint:
|
||||
- `POST /api/v1/uber/orders/{orderId}/ready`
|
||||
- Existing webhook handling includes:
|
||||
- `orders.fulfillment_issues.resolved` event ingestion + signature verification + dedupe
|
||||
|
||||
## Existing Before
|
||||
|
||||
- Get order details endpoint and order actions endpoint
|
||||
- Webhook ingestion and `200` acknowledgement
|
||||
- Resolve flow available through generic action path
|
||||
|
||||
## Pending
|
||||
|
||||
- Exact payload field-level schema parity for all quantity/weight variants from endpoint reference
|
||||
- Automated workflow engine:
|
||||
- fetch order after `orders.fulfillment_issues.resolved`
|
||||
- inspect `customer_ack_type`
|
||||
- trigger next fulfillment action automatically
|
||||
- Explicit support helper for count-vs-weight conversion rules
|
||||
|
||||
@ -25,3 +25,7 @@ Common event types handled:
|
||||
- `store.provisioned`
|
||||
- `store.deprovisioned`
|
||||
- `store.status.changed`
|
||||
|
||||
Retail fulfillment follow-up:
|
||||
|
||||
- On `orders.fulfillment_issues.resolved`, fetch updated order details and inspect customer acknowledgment before next action.
|
||||
|
||||
@ -347,6 +347,52 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/uber/orders/{orderId}/fulfillment-issues": {
|
||||
"post": {
|
||||
"summary": "Resolve retail fulfillment issues (found/partial/out-of-stock substitutions)",
|
||||
"tags": [
|
||||
"Uber Orders"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "orderId",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Fulfillment issue update submitted"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/uber/orders/{orderId}/ready": {
|
||||
"post": {
|
||||
"summary": "Mark order ready for handoff/pickup",
|
||||
"tags": [
|
||||
"Uber Orders"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "orderId",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Order marked ready"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/uber/stores": {
|
||||
"get": {
|
||||
"summary": "Retrieve all stores provisioned to developer account",
|
||||
|
||||
@ -457,6 +457,74 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Resolve Fulfillment Issues (Typed)",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "x-api-key",
|
||||
"value": "{{apiKey}}"
|
||||
},
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"issues\": [\n {\n \"cart_item_id\": \"cart_item_1\",\n \"issue_type\": \"OUT_OF_ITEM\",\n \"action_type\": \"REPLACE_FOR_ME\",\n \"item_substitute\": {\n \"id\": \"sub_item_1\"\n }\n }\n ]\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/api/v1/uber/orders/{{orderId}}/fulfillment-issues",
|
||||
"host": [
|
||||
"{{baseUrl}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"v1",
|
||||
"uber",
|
||||
"orders",
|
||||
"{{orderId}}",
|
||||
"fulfillment-issues"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Mark Order 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/orders/{{orderId}}/ready",
|
||||
"host": [
|
||||
"{{baseUrl}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"v1",
|
||||
"uber",
|
||||
"orders",
|
||||
"{{orderId}}",
|
||||
"ready"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Order Response SLA Metric",
|
||||
"request": {
|
||||
|
||||
@ -110,6 +110,58 @@ async function orderAction(req, res) {
|
||||
return res.json({ success: true, data });
|
||||
}
|
||||
|
||||
async function resolveFulfillmentIssues(req, res) {
|
||||
const quantityObject = z
|
||||
.object({
|
||||
in_sellable_unit: z.any().optional(),
|
||||
in_priceable_unit: z.any().optional()
|
||||
})
|
||||
.optional();
|
||||
|
||||
const fulfillmentIssueSchema = z.object({
|
||||
cart_item_id: z.string().optional(),
|
||||
issue_type: z.enum([
|
||||
"FOUND_ITEM",
|
||||
"PARTIAL_AVAILABILITY",
|
||||
"OUT_OF_ITEM"
|
||||
]),
|
||||
action_type: z
|
||||
.enum([
|
||||
"REPLACE_FOR_ME",
|
||||
"SUBSTITUTE_ME",
|
||||
"REMOVE_ITEM",
|
||||
"ALTERNATIVE_ITEM",
|
||||
"SUBSTITUTION_REJECTED"
|
||||
])
|
||||
.optional(),
|
||||
item_substitute: z.any().optional(),
|
||||
item_availability: z
|
||||
.object({
|
||||
items_available: quantityObject
|
||||
})
|
||||
.optional()
|
||||
});
|
||||
|
||||
const schema = z.object({
|
||||
issues: z.array(fulfillmentIssueSchema).min(1)
|
||||
});
|
||||
const payload = schema.parse(req.body || {});
|
||||
|
||||
const data = await proxyService.resolveFulfillmentIssues({
|
||||
orderId: req.params.orderId,
|
||||
payload
|
||||
});
|
||||
return res.json({ success: true, data });
|
||||
}
|
||||
|
||||
async function markOrderReady(req, res) {
|
||||
const data = await proxyService.markOrderReady({
|
||||
orderId: req.params.orderId,
|
||||
payload: req.body || {}
|
||||
});
|
||||
return res.json({ success: true, data });
|
||||
}
|
||||
|
||||
async function updateHours(req, res) {
|
||||
const schema = z.object({
|
||||
merchantId: z.string().min(1),
|
||||
@ -235,6 +287,8 @@ module.exports = {
|
||||
listOrders,
|
||||
getOrderById,
|
||||
orderAction,
|
||||
resolveFulfillmentIssues,
|
||||
markOrderReady,
|
||||
updateHours,
|
||||
listProvisionableStores,
|
||||
listStores,
|
||||
|
||||
@ -215,6 +215,30 @@ async function orderAction({ merchantId, orderId, action, payload }) {
|
||||
});
|
||||
}
|
||||
|
||||
async function resolveFulfillmentIssues({ orderId, payload }) {
|
||||
const uberPath = interpolatePath(uberEndpoints.orders.resolveFulfillmentIssue, { orderId });
|
||||
return callUberApi({
|
||||
method: "POST",
|
||||
uberPath,
|
||||
body: payload,
|
||||
wrapperRoute: "/api/v1/uber/orders/:orderId/fulfillment-issues",
|
||||
authMode: "app",
|
||||
scopes: AUTH_SCOPES.ORDER
|
||||
});
|
||||
}
|
||||
|
||||
async function markOrderReady({ orderId, payload }) {
|
||||
const uberPath = interpolatePath(uberEndpoints.orders.readyForPickup, { orderId });
|
||||
return callUberApi({
|
||||
method: "POST",
|
||||
uberPath,
|
||||
body: payload || {},
|
||||
wrapperRoute: "/api/v1/uber/orders/:orderId/ready",
|
||||
authMode: "app",
|
||||
scopes: AUTH_SCOPES.ORDER
|
||||
});
|
||||
}
|
||||
|
||||
async function updateStoreHours({ merchantId, storeId, payload }) {
|
||||
const uberPath = interpolatePath(uberEndpoints.stores.updateHours, { storeId });
|
||||
return callUberApi({
|
||||
@ -355,6 +379,8 @@ module.exports = {
|
||||
ordersList,
|
||||
getOrderById,
|
||||
orderAction,
|
||||
resolveFulfillmentIssues,
|
||||
markOrderReady,
|
||||
updateStoreHours,
|
||||
listProvisionableStores,
|
||||
listStores,
|
||||
|
||||
@ -101,6 +101,47 @@ router.get("/uber/orders", asyncHandler(controller.listOrders));
|
||||
*/
|
||||
router.get("/uber/orders/:orderId", asyncHandler(controller.getOrderById));
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /api/v1/uber/orders/{orderId}/fulfillment-issues:
|
||||
* post:
|
||||
* summary: Resolve retail fulfillment issues (found/partial/out-of-stock substitutions)
|
||||
* tags:
|
||||
* - Uber Orders
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: orderId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Fulfillment issue update submitted
|
||||
*/
|
||||
router.post(
|
||||
"/uber/orders/:orderId/fulfillment-issues",
|
||||
asyncHandler(controller.resolveFulfillmentIssues)
|
||||
);
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /api/v1/uber/orders/{orderId}/ready:
|
||||
* post:
|
||||
* summary: Mark order ready for handoff/pickup
|
||||
* tags:
|
||||
* - Uber Orders
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: orderId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Order marked ready
|
||||
*/
|
||||
router.post("/uber/orders/:orderId/ready", asyncHandler(controller.markOrderReady));
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /api/v1/uber/stores:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user