feat: implement promotions api v1.0.0 create revoke get and list endpoints

This commit is contained in:
MOHAN 2026-03-29 18:31:05 +05:30
parent 1ce9a38808
commit 0c41ad5858
9 changed files with 461 additions and 3 deletions

View File

@ -14,7 +14,7 @@ This file intentionally separates high-priority vs extended APIs for easier team
## Group B (Extended / Optional / Can Be Added Incrementally)
- Promotions
- Promotions (Promotions API v1.0.0 route set available)
- Ads / sponsored listings
- Payout and financial reconciliation (Reporting API route now available)
- Store holiday/special schedules

View File

@ -0,0 +1,32 @@
# 17 Promotions API 1.0.0 Audit
Source checked: "Uber Eats Marketplace Promotions API (1.0.0)" shared by you.
## Implemented Now (Dedicated Wrapper Namespace)
- Create Promotion:
- `POST /api/v1/uber/delivery-promotions/stores/{storeId}`
- upstream: `/v1/delivery/stores/{store_id}/promotion`
- Revoke Promotion:
- `POST /api/v1/uber/delivery-promotions/{promotionId}/revoke`
- upstream: `/v1/delivery/promotions/{promotion_id}/revoke`
- Get Promotion:
- `GET /api/v1/uber/delivery-promotions/{promotionId}`
- upstream: `/v1/delivery/promotions/{promotion_id}`
- Get Promotions:
- `GET /api/v1/uber/delivery-promotions/stores/{storeId}`
- upstream: `/v1/delivery/stores/{store_id}/promotions`
## Validation Added
- `user_group` enum: `ALL_CUSTOMERS | FIRST_TIME_CUSTOMERS`
- promotions list `state` enum:
- `active`, `pending`, `completed`, `revoked`, `expired`, `deleted`
- required top-level create payload fields enforced
## Pending
- Strict schema unions for each promotion type object variant:
- flat_off, free_item, bogo, percent_off, menu_item, free_delivery
- typed handling for `time_range` object query form when full exact query contract is shared

View File

@ -0,0 +1,14 @@
# 17 Promotions
Promotions API 1.0.0 wrapper routes:
- `POST /api/v1/uber/delivery-promotions/stores/{storeId}` (Create promotion)
- `GET /api/v1/uber/delivery-promotions/stores/{storeId}` (List promotions)
- `GET /api/v1/uber/delivery-promotions/{promotionId}` (Get single promotion)
- `POST /api/v1/uber/delivery-promotions/{promotionId}/revoke` (Revoke promotion)
Notes:
- Only promotions created via API are expected to be returned by get/list endpoints.
- User group and state filters are validated.

View File

@ -1026,6 +1026,96 @@
}
}
},
"/api/v1/uber/delivery-promotions/stores/{storeId}": {
"post": {
"summary": "Promotions API 1.0.0 - Create promotion",
"tags": [
"Uber Delivery Promotions v1"
],
"parameters": [
{
"in": "path",
"name": "storeId",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Promotion created"
}
}
},
"get": {
"summary": "Promotions API 1.0.0 - Get promotions by store",
"tags": [
"Uber Delivery Promotions v1"
],
"parameters": [
{
"in": "path",
"name": "storeId",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Promotions listed"
}
}
}
},
"/api/v1/uber/delivery-promotions/{promotionId}": {
"get": {
"summary": "Promotions API 1.0.0 - Get promotion by ID",
"tags": [
"Uber Delivery Promotions v1"
],
"parameters": [
{
"in": "path",
"name": "promotionId",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Promotion retrieved"
}
}
}
},
"/api/v1/uber/delivery-promotions/{promotionId}/revoke": {
"post": {
"summary": "Promotions API 1.0.0 - Revoke promotion",
"tags": [
"Uber Delivery Promotions v1"
],
"parameters": [
{
"in": "path",
"name": "promotionId",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Promotion revoked"
}
}
}
},
"/api/v1/uber/reporting/fetch": {
"post": {
"summary": "Fetch Uber reporting CSV with retries and header-based parsing",

View File

@ -927,6 +927,123 @@
}
}
},
{
"name": "Promotions API - Create Promotion",
"request": {
"method": "POST",
"header": [
{
"key": "x-api-key",
"value": "{{apiKey}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"start_time\": \"2026-04-10T00:00:00-07:00\",\n \"end_time\": \"2026-04-20T00:00:00-07:00\",\n \"external_promotion_id\": \"Uber_Superbowl\",\n \"user_group\": \"ALL_CUSTOMERS\",\n \"allow_unlimited_apply\": true,\n \"currency_code\": \"USD\",\n \"budget\": {\n \"unlimited_budget\": true\n },\n \"promo_type\": \"FLATOFF\",\n \"promotion_discount\": {\n \"flat_off_discount\": {}\n }\n}"
},
"url": {
"raw": "{{baseUrl}}/api/v1/uber/delivery-promotions/stores/{{storeId}}",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"v1",
"uber",
"delivery-promotions",
"stores",
"{{storeId}}"
]
}
}
},
{
"name": "Promotions API - List Promotions",
"request": {
"method": "GET",
"header": [
{
"key": "x-api-key",
"value": "{{apiKey}}"
}
],
"url": {
"raw": "{{baseUrl}}/api/v1/uber/delivery-promotions/stores/{{storeId}}?state=active",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"v1",
"uber",
"delivery-promotions",
"stores",
"{{storeId}}"
],
"query": [
{
"key": "state",
"value": "active"
}
]
}
}
},
{
"name": "Promotions API - Get Promotion",
"request": {
"method": "GET",
"header": [
{
"key": "x-api-key",
"value": "{{apiKey}}"
}
],
"url": {
"raw": "{{baseUrl}}/api/v1/uber/delivery-promotions/{{promotionId}}",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"v1",
"uber",
"delivery-promotions",
"{{promotionId}}"
]
}
}
},
{
"name": "Promotions API - Revoke Promotion",
"request": {
"method": "POST",
"header": [
{
"key": "x-api-key",
"value": "{{apiKey}}"
}
],
"url": {
"raw": "{{baseUrl}}/api/v1/uber/delivery-promotions/{{promotionId}}/revoke",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"v1",
"uber",
"delivery-promotions",
"{{promotionId}}",
"revoke"
]
}
}
},
{
"name": "Get Store By ID",
"request": {
@ -1491,6 +1608,10 @@
{
"key": "orderId",
"value": ""
},
{
"key": "promotionId",
"value": ""
}
]
}

View File

@ -49,6 +49,12 @@ module.exports = {
deliveryByoc: {
ingestCourierLocation: "/v1/eats/byoc/restaurants/orders/event/location"
},
deliveryPromotions: {
createByStore: "/v1/delivery/stores/{storeId}/promotion",
revokeById: "/v1/delivery/promotions/{promotionId}/revoke",
getById: "/v1/delivery/promotions/{promotionId}",
listByStore: "/v1/delivery/stores/{storeId}/promotions"
},
webhooks: {
events: "/v1/eats/stores/{storeId}/event_feed"
}

View File

@ -635,6 +635,63 @@ async function deliveryByocIngestCourierLocation(req, res) {
return res.json({ success: true, data });
}
async function deliveryCreatePromotion(req, res) {
const budgetSchema = z
.object({
unlimited_budget: z.boolean().optional(),
amount_e5: z.coerce.number().optional()
})
.optional();
const promotionSchema = z.object({
start_time: z.string(),
end_time: z.string(),
external_promotion_id: z.string().optional(),
user_group: z.enum(["ALL_CUSTOMERS", "FIRST_TIME_CUSTOMERS"]).optional(),
allow_unlimited_apply: z.boolean().optional(),
currency_code: z.string().optional(),
budget: budgetSchema,
promo_type: z.string(),
promotion_discount: z.record(z.string(), z.any()).optional(),
promotion_customization: z.record(z.string(), z.any()).optional()
});
const payload = promotionSchema.parse(req.body || {});
const data = await proxyService.deliveryCreatePromotion({
storeId: req.params.storeId,
payload
});
return res.json({ success: true, data });
}
async function deliveryRevokePromotion(req, res) {
const data = await proxyService.deliveryRevokePromotion({
promotionId: req.params.promotionId
});
return res.json({ success: true, data });
}
async function deliveryGetPromotion(req, res) {
const data = await proxyService.deliveryGetPromotion({
promotionId: req.params.promotionId
});
return res.json({ success: true, data });
}
async function deliveryListPromotions(req, res) {
const schema = z.object({
state: z.enum(["active", "pending", "completed", "revoked", "expired", "deleted"]).optional(),
start_time: z.string().optional(),
end_time: z.string().optional()
});
const query = schema.parse(req.query || {});
const data = await proxyService.deliveryListPromotions({
storeId: req.params.storeId,
query
});
return res.json({ success: true, data });
}
module.exports = {
genericProxy,
upsertMenu,
@ -676,5 +733,9 @@ module.exports = {
deliveryResolveFulfillmentIssues,
deliveryGetReplacementRecommendations,
deliveryUpdatePartnerCount,
deliveryByocIngestCourierLocation
deliveryByocIngestCourierLocation,
deliveryCreatePromotion,
deliveryRevokePromotion,
deliveryGetPromotion,
deliveryListPromotions
};

View File

@ -612,6 +612,53 @@ async function deliveryByocIngestCourierLocation({ payload }) {
});
}
async function deliveryCreatePromotion({ storeId, payload }) {
const uberPath = interpolatePath(uberEndpoints.deliveryPromotions.createByStore, { storeId });
return callUberApi({
method: "POST",
uberPath,
body: payload,
wrapperRoute: "/api/v1/uber/delivery-promotions/stores/:storeId",
authMode: "app",
scopes: AUTH_SCOPES.STORE
});
}
async function deliveryRevokePromotion({ promotionId }) {
const uberPath = interpolatePath(uberEndpoints.deliveryPromotions.revokeById, { promotionId });
return callUberApi({
method: "POST",
uberPath,
body: {},
wrapperRoute: "/api/v1/uber/delivery-promotions/:promotionId/revoke",
authMode: "app",
scopes: AUTH_SCOPES.STORE
});
}
async function deliveryGetPromotion({ promotionId }) {
const uberPath = interpolatePath(uberEndpoints.deliveryPromotions.getById, { promotionId });
return callUberApi({
method: "GET",
uberPath,
wrapperRoute: "/api/v1/uber/delivery-promotions/:promotionId",
authMode: "app",
scopes: AUTH_SCOPES.STORE
});
}
async function deliveryListPromotions({ storeId, query }) {
const uberPath = interpolatePath(uberEndpoints.deliveryPromotions.listByStore, { storeId });
return callUberApi({
method: "GET",
uberPath,
query,
wrapperRoute: "/api/v1/uber/delivery-promotions/stores/:storeId",
authMode: "app",
scopes: AUTH_SCOPES.STORE
});
}
module.exports = {
genericProxy,
menuUpsert,
@ -653,5 +700,9 @@ module.exports = {
deliveryResolveFulfillmentIssues,
deliveryGetReplacementRecommendations,
deliveryUpdatePartnerCount,
deliveryByocIngestCourierLocation
deliveryByocIngestCourierLocation,
deliveryCreatePromotion,
deliveryRevokePromotion,
deliveryGetPromotion,
deliveryListPromotions
};

View File

@ -688,4 +688,87 @@ router.post(
asyncHandler(controller.deliveryByocIngestCourierLocation)
);
/**
* @openapi
* /api/v1/uber/delivery-promotions/stores/{storeId}:
* post:
* summary: Promotions API 1.0.0 - Create promotion
* tags:
* - Uber Delivery Promotions v1
* parameters:
* - in: path
* name: storeId
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Promotion created
* get:
* summary: Promotions API 1.0.0 - Get promotions by store
* tags:
* - Uber Delivery Promotions v1
* parameters:
* - in: path
* name: storeId
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Promotions listed
*/
router.post(
"/uber/delivery-promotions/stores/:storeId",
asyncHandler(controller.deliveryCreatePromotion)
);
router.get(
"/uber/delivery-promotions/stores/:storeId",
asyncHandler(controller.deliveryListPromotions)
);
/**
* @openapi
* /api/v1/uber/delivery-promotions/{promotionId}:
* get:
* summary: Promotions API 1.0.0 - Get promotion by ID
* tags:
* - Uber Delivery Promotions v1
* parameters:
* - in: path
* name: promotionId
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Promotion retrieved
*/
router.get(
"/uber/delivery-promotions/:promotionId",
asyncHandler(controller.deliveryGetPromotion)
);
/**
* @openapi
* /api/v1/uber/delivery-promotions/{promotionId}/revoke:
* post:
* summary: Promotions API 1.0.0 - Revoke promotion
* tags:
* - Uber Delivery Promotions v1
* parameters:
* - in: path
* name: promotionId
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Promotion revoked
*/
router.post(
"/uber/delivery-promotions/:promotionId/revoke",
asyncHandler(controller.deliveryRevokePromotion)
);
module.exports = router;