feat: implement promotions api v1.0.0 create revoke get and list endpoints
This commit is contained in:
parent
1ce9a38808
commit
0c41ad5858
@ -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
|
||||
|
||||
32
docs/developer-portal/17-promotions-api-1-0-0-audit.md
Normal file
32
docs/developer-portal/17-promotions-api-1-0-0-audit.md
Normal 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
|
||||
|
||||
14
docs/developer-portal/17-promotions.md
Normal file
14
docs/developer-portal/17-promotions.md
Normal 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.
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user