Overview
API dành cho V-App (Customer-facing) để hiển thị và mua bán voucher trên Voucher Shop. Service cung cấp các chức năng:
- Duyệt & Tìm kiếm voucher — hiển thị danh sách voucher, lọc theo category/tag, tìm kiếm theo từ khóa
- Campaign management — hiển thị campaign carousel, danh sách campaign, chi tiết campaign
- Purchase flow — reserve → commit → release voucher qua checkout flow
Service Name: promotion-shop-service
Environments
| Môi trường | Base URL |
|---|
| DEV | https://promotion-shop-service.dev.vsf.services |
| SIT | https://promotion-shop-service.sit.vsf.services |
| UAT | https://promotion-shop-service.uat.vsf.services |
| Production | https://promotion-shop-service.prd.vsf.services |
Endpoints Summary
| # | Endpoint | Method | Purpose |
|---|
| 1 | /api/v1/vouchers | GET | Danh sách voucher (browse, filter theo tag/category/campaign) |
| 2 | /api/v1/vouchers/\{id\} | GET | Chi tiết voucher |
| 3 | /api/v1/vouchers/search | GET | Tìm kiếm voucher theo từ khóa |
| 4 | /api/v1/campaigns/carousel | GET | Carousel campaign cho trang chủ |
| 5 | /api/v1/campaigns | GET | Danh sách campaign |
| 6 | /api/v1/campaigns/\{id\} | GET | Chi tiết campaign |
| 7 | /api/v1/vouchers/reserve | POST | Reserve voucher cho checkout |
| 8 | /api/v1/vouchers/commit | POST | Commit purchase sau payment thành công |
| 9 | /api/v1/vouchers/release | POST | Release/cancel reservation |
| 10 | /api/v1/vouchers/reservation/\{key\} | GET | Check trạng thái reservation |
Sequence Diagrams
Browse & Search Flow
V-App Client Shop Service Database
| | |
| GET /vouchers | |
| (tag, category, page) | |
|------------------------->| Query vouchers |
| |------------------------>|
| | vouchers + inventory |
| |<------------------------|
| Page<ShopVoucherResponse>| |
|<-------------------------| |
| | |
| GET /vouchers/search | |
| (q=keyword) | |
|------------------------->| Search PM + Campaign |
| |------------------------>|
| Page<ShopVoucherResponse>| |
|<-------------------------| |
Purchase Flow
V-App Client Shop Service Inventory Service
| | |
| POST /vouchers/reserve | |
| (orderId, items) | |
|------------------------->| Reserve inventory |
| |------------------------>|
| | Issue voucher codes |
| |<------------------------|
| reservationKey, items | |
|<-------------------------| |
| | |
| [Payment success] | |
| POST /vouchers/commit | |
| (reservationKey) | |
|------------------------->| Confirm purchase |
| |------------------------>|
| committedAt | |
|<-------------------------| |
| | |
| [Payment fail/cancel] | |
| POST /vouchers/release | |
| (reservationKey) | |
|------------------------->| Release inventory |
| |------------------------>|
| releasedAt | |
|<-------------------------| |
Purchase State Transition
| State | Ý nghĩa |
|---|
| RESERVED | Đã reserve cho order; chờ commit hoặc release |
| COMMITTED | Đã commit sau payment thành công, voucher cấp vào wallet |
| RELEASED | Reservation bị huỷ, inventory trả về |
[Reserve] [Commit]
| |
v v
RESERVED --------> COMMITTED
|
| [Release]
v
RELEASED
Authentication
Shop Service sử dụng API Key để xác thực và phân biệt merchant. Mỗi merchant được cấp một X-Promotion-Api-Key riêng khi onboard.
| Field | Mô tả | Quản lý bởi |
|---|
X-Promotion-Api-Key | API Key xác định merchant. Có expiry và status | Promotion Service |
Header Definitions:
| Header | Required | Description |
|---|
client_id | Yes | Client ID để authenticate |
client_secret | Yes | Client secret để authenticate |
X-Promotion-Api-Key | Yes | API Key xác định merchant |
X-Merchant-Id | Yes | Merchant identifier (ví dụ: vinfast, vgreen) |
X-User-Id | Yes | Customer user ID. Xác định theo merchant hoặc org tương ứng |
X-Request-Id | Yes | Unique request ID cho tracing (UUID) |
X-Idempotency-Key | Yes (/reserve) | Ngăn duplicate operations. Format: UUID. Gửi lại cùng key sẽ trả response giống lần đầu |
Content-Type | Yes (POST) | application/json |
Accept | No | application/json |
Tất cả API response đều tuân theo cấu trúc:
{
"code": "SUCCESS",
"message": "Success",
"data": { ... }
}
Paginated Response:
{
"code": "SUCCESS",
"message": "Success",
"data": {
"content": [ ... ],
"page": {
"size": 20,
"number": 0,
"totalElements": 100,
"totalPages": 5
}
}
}
I. Voucher APIs
1. GET /api/v1/vouchers
Lấy danh sách voucher đang bán trên Voucher Shop. Hỗ trợ filter theo tag, category, campaign.
cURL Example
curl --location 'https://promotion-shop-service.sit.vsf.services/api/v1/vouchers?tag=HOT_DEAL&page=0&size=10' \
--header 'client_id: {your-client-id}' \
--header 'client_secret: {your-client-secret}' \
--header 'X-Promotion-Api-Key: {your-api-key}' \
--header 'X-Merchant-Id: {your-merchant-id}' \
--header 'X-User-Id: 349874708' \
--header 'X-Request-Id: {your-request-id}'
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|
tag | string | No | — | Filter theo single campaign tag (HOT_DEAL, FLASH_SALE, ...) |
tags | string | No | — | Filter theo multiple tags, phân cách bởi dấu phẩy (HOT_DEAL,FLASH_SALE) |
categoryIds | array[long] | No | — | Filter theo danh mục voucher |
campaignIds | array[long] | No | — | Chỉ lấy voucher từ campaign IDs cụ thể |
excludeCampaignIds | array[long] | No | — | Loại trừ voucher từ campaign IDs |
channel | string | No | V-APP | Kênh hiển thị (V-APP, WEB) |
page | int | No | 0 | Trang (0-indexed) |
size | int | No | 20 | Số lượng mỗi trang (max 50) |
Lưu ý: Nếu cả tag và tags đều có, tags được ưu tiên. Chỉ hiển thị voucher từ campaign APPROVED + ACTIVE + type SELL.
Response
{
"code": "SUCCESS",
"data": {
"content": [
{
"id": 402,
"categoryId": 14,
"miniAppIds": ["vinmec.v-app.vn"],
"campaign": {
"id": 1171,
"name": "Voucher giảm giá giảm giá của Vgreen khi sạc",
"tag": "HOT_DEAL",
"displayPriority": 1,
"validity": {
"startAt": "2026-03-15T08:25:00Z",
"endAt": "2028-05-31T16:59:00Z"
},
"countdown": null
},
"display": {
"title": "Voucher giảm giá 30K cho đơn hàng 100k",
"shortTitle": "Voucher giảm giá 30K cho đơn hàng 100k",
"brandName": "VGREEN",
"logoUrl": "https://core-services-uploader-public.sit.vsf.services/promotion/promotion_master_logo/.../ffffff.png",
"bannerUrl": "https://core-services-uploader-public.sit.vsf.services/promotion/promotion_master_banner/.../ffffff1.png",
"thumbnailUrl": "https://core-services-uploader-public.sit.vsf.services/promotion/promotion_master_logo/.../ffffff.png",
"description": "Voucher giảm giá 30K cho đơn hàng 100k"
},
"pricing": {
"faceValue": 30000.00,
"sellingPrice": 27000.00,
"discountedPrice": null,
"currency": "VND",
"discountPercentage": 10
},
"inventory": {
"totalQuantity": 40,
"remainingQuantity": 3,
"isOutOfStock": false
},
"userPurchaseUsage": {
"dailyPurchasedQty": 0,
"totalPurchasedQty": 2,
"dailyRemaining": 3,
"totalRemaining": null
},
"validity": null,
"terms": {
"usageType": "ONLINE",
"termsAndConditions": "Voucher giảm giá 30K cho đơn hàng 100k",
"validityDays": 1,
"usageLimit": null,
"maxPerDay": 3,
"maxPerOrder": null
}
}
],
"page": {
"size": 10,
"number": 0,
"totalElements": 15,
"totalPages": 2
}
},
"message": "Success"
}
Response Fields
| Field | Type | Description |
|---|
id | number | Voucher pool ID |
categoryId | number | Mã danh mục voucher |
miniAppIds | array[string] | Danh sách MiniApp IDs được phép sử dụng voucher |
campaign | object | Thông tin campaign chứa voucher |
campaign.id | number | Campaign ID |
campaign.name | string | Tên campaign |
campaign.tag | string | Tag phân loại (HOT_DEAL, FLASH_SALE) |
campaign.displayPriority | number | Thứ tự ưu tiên hiển thị (1 = cao nhất) |
campaign.validity | object | Thời gian hiệu lực campaign |
campaign.validity.startAt | string (ISO 8601) | Ngày bắt đầu |
campaign.validity.endAt | string (ISO 8601) | Ngày kết thúc |
campaign.countdown | object/null | Thông tin đếm ngược (nếu Flash Sale) |
campaign.countdown.endAt | string (ISO 8601) | Thời điểm kết thúc countdown |
campaign.countdown.remainingSeconds | number | Số giây còn lại |
display | object | Thông tin hiển thị (lấy từ PromotionMaster) |
display.title | string | Tiêu đề đầy đủ |
display.shortTitle | string | Tiêu đề ngắn gọn (hiển thị trên card) |
display.brandName | string | Tên thương hiệu |
display.logoUrl | string | URL logo |
display.bannerUrl | string | URL banner |
display.thumbnailUrl | string | URL thumbnail |
display.description | string | Mô tả chi tiết |
pricing | object | Thông tin giá |
pricing.faceValue | number | Mệnh giá voucher (VND/VPOINT) |
pricing.sellingPrice | number | Giá bán |
pricing.discountedPrice | number/null | Giá sau giảm (nếu có) |
pricing.currency | string | Loại tiền (VND, VPOINT) |
pricing.discountPercentage | number | Phần trăm giảm so với mệnh giá |
inventory | object | Thông tin tồn kho |
inventory.totalQuantity | number | Tổng số lượng |
inventory.remainingQuantity | number | Số lượng còn lại |
inventory.isOutOfStock | boolean | true nếu hết hàng |
userPurchaseUsage | object/null | Thông tin mua hàng của user (khi có X-User-Id) |
userPurchaseUsage.dailyPurchasedQty | number | Số lượng đã mua trong ngày |
userPurchaseUsage.totalPurchasedQty | number | Tổng số lượng đã mua |
userPurchaseUsage.dailyRemaining | number/null | Số lượng còn được mua trong ngày |
userPurchaseUsage.totalRemaining | number/null | Tổng số lượng còn được mua |
terms | object | Điều kiện sử dụng |
terms.usageType | string | Loại sử dụng (ONLINE, OFFLINE) |
terms.termsAndConditions | string | Điều khoản & điều kiện |
terms.validityDays | number | Số ngày hiệu lực sau khi mua |
terms.usageLimit | number/null | Giới hạn sử dụng tổng |
terms.maxPerDay | number/null | Giới hạn mua/ngày |
terms.maxPerOrder | number/null | Giới hạn mua/đơn |
2. GET /api/v1/vouchers/{id}
Chi tiết một voucher cụ thể, bao gồm thông tin mua hàng của user (nếu có X-User-Id).
cURL Example
curl --location 'https://promotion-shop-service.sit.vsf.services/api/v1/vouchers/402' \
--header 'client_id: {your-client-id}' \
--header 'client_secret: {your-client-secret}' \
--header 'X-Promotion-Api-Key: {your-api-key}' \
--header 'X-Merchant-Id: {your-merchant-id}' \
--header 'X-User-Id: 349874708' \
--header 'X-Request-Id: {your-request-id}'
Path Parameters
| Parameter | Type | Required | Description |
|---|
id | long | Yes | Voucher pool ID |
Response
Trả về cùng cấu trúc ShopVoucherResponse như endpoint list (xem mục 1), nhưng không wrap trong page.
{
"code": "SUCCESS",
"data": {
"id": 402,
"categoryId": 14,
"campaign": { ... },
"display": { ... },
"pricing": { ... },
"inventory": { ... },
"userPurchaseUsage": { ... },
"terms": { ... }
},
"message": "Success"
}
Error Responses
| HTTP | Code | Mô tả |
|---|
| 404 | SHOP-VOU-001 | Voucher không tồn tại hoặc đã bị xóa |
Tìm kiếm voucher theo từ khóa. Hỗ trợ tìm kiếm tiếng Việt có dấu. Tìm trong tên campaign, tiêu đề voucher, mô tả, và tên thương hiệu.
cURL Example
curl -G 'https://promotion-shop-service.sit.vsf.services/api/v1/vouchers/search' \
--data-urlencode 'q=giảm giá' \
-d 'page=0&size=20' \
--header 'client_id: {your-client-id}' \
--header 'client_secret: {your-client-secret}' \
--header 'X-Promotion-Api-Key: {your-api-key}' \
--header 'X-Merchant-Id: {your-merchant-id}' \
--header 'X-User-Id: 349874708' \
--header 'X-Request-Id: {your-request-id}'
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|
q | string | Yes | — | Từ khóa tìm kiếm (tối thiểu 2 ký tự, hỗ trợ Unicode/tiếng Việt) |
tag | string | No | — | Filter thêm theo campaign tag |
channel | string | No | V-APP | Kênh hiển thị (V-APP, WEB) |
page | int | No | 0 | Trang (0-indexed) |
size | int | No | 20 | Số lượng mỗi trang (max 100) |
Quan trọng: Từ khóa q phải từ 2 ký tự trở lên. Nếu < 2 ký tự sẽ trả lỗi SHOP-VOU-002.
Lưu ý encoding: Client phải URL-encode đúng UTF-8 cho từ khóa tiếng Việt có dấu. Ví dụ: giảm giá → gi%E1%BA%A3m+gi%C3%A1.
Response
Cùng cấu trúc phân trang như endpoint list (xem mục 1).
{
"code": "SUCCESS",
"data": {
"content": [ ... ],
"page": {
"size": 20,
"number": 0,
"totalElements": 5,
"totalPages": 1
}
},
"message": "Success"
}
Error Responses
| HTTP | Code | Mô tả |
|---|
| 400 | SHOP-VOU-002 | Từ khóa tìm kiếm quá ngắn (< 2 ký tự) |
II. Campaign APIs
4. GET /api/v1/campaigns/carousel
Lấy danh sách campaign hiển thị dạng carousel trên trang chủ Voucher Shop.
cURL Example
curl --location 'https://promotion-shop-service.sit.vsf.services/api/v1/campaigns/carousel?page=0&size=5' \
--header 'client_id: {your-client-id}' \
--header 'client_secret: {your-client-secret}' \
--header 'X-Promotion-Api-Key: {your-api-key}' \
--header 'X-Merchant-Id: {your-merchant-id}' \
--header 'X-User-Id: 349874708' \
--header 'X-Request-Id: {your-request-id}'
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|
channel | string | No | — | Kênh hiển thị (filter banner theo channel) |
page | int | No | 0 | Trang (0-indexed) |
size | int | No | 10 | Số lượng mỗi trang |
Response
{
"code": "SUCCESS",
"data": {
"content": [
{
"id": 1171,
"code": "VGREEN-SALE-2026",
"name": "Voucher giảm giá của Vgreen khi sạc",
"description": "Mô tả campaign",
"campaignTag": "HOT_DEAL",
"displayPriority": 1,
"themeUrl": "https://example.com/theme.png",
"startAt": "2026-03-15T08:25:00Z",
"endAt": "2028-05-31T16:59:00Z",
"presale": null,
"banners": [
{
"id": 101,
"channelCode": "APP",
"positionCode": "CAROUSEL",
"assetUrl": "https://core-services-uploader-public.sit.vsf.services/promotion/.../banner.png",
"assetType": "IMAGE",
"altText": "Banner mô tả",
"clickUrl": null,
"displayOrder": 1
}
],
"voucherSummary": {
"totalCount": 3,
"availableCount": 2,
"soldCount": 15,
"displayedCount": 3
},
"vouchers": null
}
],
"page": {
"size": 5,
"number": 0,
"totalElements": 3,
"totalPages": 1
}
},
"message": "Success"
}
Response Fields — ShopCampaignResponse
| Field | Type | Description |
|---|
id | number | Campaign ID |
code | string | Mã campaign |
name | string | Tên campaign |
description | string | Mô tả campaign |
campaignTag | string | Tag phân loại (HOT_DEAL, FLASH_SALE) |
displayPriority | number | Thứ tự ưu tiên (1 = cao nhất) |
themeUrl | string | URL theme/background campaign |
startAt | string (ISO 8601) | Ngày bắt đầu |
endAt | string (ISO 8601) | Ngày kết thúc |
presale | object/null | Thông tin presale (nếu có) |
presale.enabled | boolean | Presale đang bật |
presale.startAt | string (ISO 8601) | Thời gian bắt đầu presale |
presale.countdownEnabled | boolean | Hiển thị countdown |
presale.teaserText | string | Text teaser presale |
presale.teaserImageUrl | string | URL ảnh teaser |
banners | array | Danh sách banner |
banners[].id | number | Banner ID |
banners[].channelCode | string | Kênh (APP, WEB) |
banners[].positionCode | string | Vị trí (CAROUSEL, DETAIL_TOP) |
banners[].assetUrl | string | URL asset (ảnh/video) |
banners[].assetType | string | Loại asset (IMAGE, VIDEO) |
banners[].altText | string | Alt text |
banners[].clickUrl | string | URL khi click vào banner |
banners[].displayOrder | number | Thứ tự hiển thị |
voucherSummary | object | Tổng hợp voucher trong campaign |
voucherSummary.totalCount | number | Tổng số voucher pool |
voucherSummary.availableCount | number | Số voucher pool còn hàng |
voucherSummary.soldCount | number | Tổng số voucher đã bán |
voucherSummary.displayedCount | number | Số voucher đang hiển thị |
vouchers | array/null | Danh sách voucher (khi includeVouchers=true) |
5. GET /api/v1/campaigns
Lấy danh sách campaign theo tag. Hỗ trợ kèm danh sách voucher trong mỗi campaign khi bật includeVouchers=true.
cURL Example
curl --location 'https://promotion-shop-service.sit.vsf.services/api/v1/campaigns?tags=HOT_DEAL,FLASH_SALE&includeVouchers=true&vouchersPerCampaign=5&page=0&size=10' \
--header 'client_id: {your-client-id}' \
--header 'client_secret: {your-client-secret}' \
--header 'X-Promotion-Api-Key: {your-api-key}' \
--header 'X-Merchant-Id: {your-merchant-id}' \
--header 'X-User-Id: 349874708' \
--header 'X-Request-Id: {your-request-id}'
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|
tag | string | No | — | Filter single tag |
tags | string | No | — | Filter multiple tags (phân cách bởi ,). Ưu tiên hơn tag |
includeVouchers | boolean | No | false | Kèm danh sách voucher trong mỗi campaign |
vouchersPerCampaign | int | No | 10 | Số voucher tối đa mỗi campaign (max 50) |
channel | string | No | — | Filter banner theo channel |
page | int | No | 0 | Trang (0-indexed) |
size | int | No | 20 | Số lượng mỗi trang |
Response
Cùng cấu trúc ShopCampaignResponse như endpoint carousel (xem mục 4). Khi includeVouchers=true, field vouchers sẽ chứa mảng ShopVoucherResponse.
6. GET /api/v1/campaigns/{id}
Lấy thông tin chi tiết một campaign, bao gồm banner, presale info, và tổng hợp voucher.
cURL Example
curl --location 'https://promotion-shop-service.sit.vsf.services/api/v1/campaigns/1171' \
--header 'client_id: {your-client-id}' \
--header 'client_secret: {your-client-secret}' \
--header 'X-Promotion-Api-Key: {your-api-key}' \
--header 'X-Merchant-Id: {your-merchant-id}' \
--header 'X-User-Id: 349874708' \
--header 'X-Request-Id: {your-request-id}'
Path Parameters
| Parameter | Type | Required | Description |
|---|
id | long | Yes | Campaign ID |
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|
channel | string | No | — | Filter banner theo channel |
Response
{
"code": "SUCCESS",
"data": {
"id": 1171,
"code": "VGREEN-SALE-2026",
"name": "Voucher giảm giá của Vgreen khi sạc",
"description": "Mô tả chi tiết campaign...",
"campaignTag": "HOT_DEAL",
"displayPriority": 1,
"themeUrl": null,
"startAt": "2026-03-15T08:25:00Z",
"endAt": "2028-05-31T16:59:00Z",
"presale": null,
"banners": [ ... ],
"voucherSummary": {
"totalCount": 3,
"availableCount": 2,
"soldCount": 15,
"displayedCount": 3
},
"vouchers": null
},
"message": "Success"
}
Error Responses
| HTTP | Code | Mô tả |
|---|
| 404 | SHOP-CAM-001 | Campaign không tồn tại hoặc không APPROVED + ACTIVE |
III. Purchase APIs
Purchase Flow Overview
Quy trình mua voucher trên V-App tuân theo flow Reserve → Commit/Release:
- Reserve — User chọn voucher + số lượng → app gọi
/reserve để hold voucher
- Payment — User thanh toán qua payment gateway
- Commit — Payment thành công → app gọi
/commit để xác nhận mua
- Release — Payment thất bại/cancel → app gọi
/release để huỷ reservation
Ghi chú: Một số tính năng mở rộng (batch purchase, partial commit, auto-expire reservation, v.v.) hiện chưa được hỗ trợ. Nếu client có yêu cầu riêng, vui lòng liên hệ team backend để thảo luận.
7. POST /api/v1/vouchers/reserve
Reserve voucher cho checkout. Tạo reservation key, hold inventory, issue voucher codes.
cURL Example
curl --location 'https://promotion-shop-service.sit.vsf.services/api/v1/vouchers/reserve' \
--header 'client_id: {your-client-id}' \
--header 'client_secret: {your-client-secret}' \
--header 'X-Promotion-Api-Key: {your-api-key}' \
--header 'X-Merchant-Id: {your-merchant-id}' \
--header 'X-User-Id: 349874708' \
--header 'X-Request-Id: {your-request-id}' \
--header 'X-Idempotency-Key: {your-idempotency-key}' \
--header 'Content-Type: application/json' \
--data '{
"orderId": "ORDER-20260319-001",
"amount": 54000,
"currency": "VND",
"items": [
{
"voucherPoolId": 402,
"unitPrice": 27000,
"quantity": 2
}
]
}'
Request
{
"orderId": "ORDER-20260319-001",
"amount": 54000,
"currency": "VND",
"items": [
{
"voucherPoolId": 402,
"unitPrice": 27000.00,
"quantity": 2
}
]
}
Request Fields:
| Field | Type | Required | Description |
|---|
orderId | string | Yes | Order ID từ hệ thống đặt hàng (unique) |
amount | number | Yes | Tổng tiền thanh toán (≥ 0) |
currency | string | No | Loại tiền (VND, VPOINT). Default: VND |
items | array | Yes | Danh sách voucher cần reserve |
items[].voucherPoolId | number | Yes | Voucher pool ID (từ API list vouchers) |
items[].unitPrice | number | Yes | Giá đơn vị (phải khớp với sellingPrice của pool) |
items[].quantity | number | Yes | Số lượng (≥ 1) |
Response
{
"code": "SUCCESS",
"data": {
"reservationKey": "{your-reservation-key}",
"orderId": "ORDER-20260319-001",
"reservedAt": "2026-03-19T01:30:00Z",
"items": [
{
"voucherPoolId": 402,
"quantity": 2,
"vouchers": [
{
"issuedPromotionId": 1500,
"voucherCode": "VCH-ABC123",
"faceValue": 30000.00,
"sellingPrice": 27000.00,
"currency": "VND",
"purchasedAt": "2026-03-19T01:30:00Z",
"expiresAt": "2026-03-20T01:30:00Z"
},
{
"issuedPromotionId": 1501,
"voucherCode": "VCH-DEF456",
"faceValue": 30000.00,
"sellingPrice": 27000.00,
"currency": "VND",
"purchasedAt": "2026-03-19T01:30:00Z",
"expiresAt": "2026-03-20T01:30:00Z"
}
]
}
]
},
"message": "Success"
}
Response Fields:
| Field | Type | Description |
|---|
reservationKey | string | UUID unique cho reservation. Dùng cho /commit và /release |
orderId | string | Order ID (trùng request) |
reservedAt | string (ISO 8601) | Thời điểm reserve |
items | array | Danh sách voucher đã reserve |
items[].voucherPoolId | number | Voucher pool ID |
items[].quantity | number | Số lượng đã reserve |
items[].vouchers | array | Chi tiết từng voucher code đã issue |
items[].vouchers[].issuedPromotionId | number | ID voucher đã cấp |
items[].vouchers[].voucherCode | string | Mã voucher (đã decrypt) |
items[].vouchers[].faceValue | number | Mệnh giá |
items[].vouchers[].sellingPrice | number | Giá bán |
items[].vouchers[].currency | string | Loại tiền |
items[].vouchers[].purchasedAt | string (ISO 8601) | Thời điểm mua |
items[].vouchers[].expiresAt | string (ISO 8601) | Thời điểm hết hạn voucher |
Error Responses
| HTTP | Code | Mô tả |
|---|
| 400 | SHOP-PUR-001 | Tổng tiền amount không khớp với giá × số lượng |
| 400 | SHOP-PUR-012 | Voucher pool hết hàng hoặc không đủ số lượng |
| 400 | SHOP-PUR-014 | Giá unitPrice không khớp với sellingPrice hiện tại |
| 400 | SHOP-PUR-015 | Loại tiền currency không khớp với voucher pool |
| 400 | SHOP-PUR-016 | Vượt giới hạn mua/ngày hoặc tổng giới hạn mua |
| 404 | SHOP-VOU-001 | Voucher pool không tồn tại hoặc đã bị xóa |
| 409 | SHOP-PUR-011 | Order ID đã được sử dụng cho reservation khác |
8. POST /api/v1/vouchers/commit
Xác nhận mua voucher sau khi payment thành công. Chuyển reservation sang trạng thái COMMITTED, voucher được cấp vào wallet user.
cURL Example
curl --location 'https://promotion-shop-service.sit.vsf.services/api/v1/vouchers/commit' \
--header 'client_id: {your-client-id}' \
--header 'client_secret: {your-client-secret}' \
--header 'X-Promotion-Api-Key: {your-api-key}' \
--header 'X-Merchant-Id: {your-merchant-id}' \
--header 'X-User-Id: 349874708' \
--header 'X-Request-Id: {your-request-id}' \
--header 'Content-Type: application/json' \
--data '{
"reservationKey": "{your-reservation-key}"
}'
Request
{
"reservationKey": "{your-reservation-key}"
}
Request Fields:
| Field | Type | Required | Description |
|---|
reservationKey | string | Yes | Reservation key từ /reserve |
Response
{
"code": "SUCCESS",
"data": {
"reservationKey": "{your-reservation-key}",
"orderId": "ORDER-20260319-001",
"committedAt": "2026-03-19T01:35:00Z",
"items": [
{
"voucherPoolId": 402,
"quantity": 2,
"vouchers": [
{
"issuedPromotionId": 1500,
"voucherCode": "VCH-ABC123",
"faceValue": 30000.00,
"sellingPrice": 27000.00,
"currency": "VND",
"purchasedAt": "2026-03-19T01:30:00Z",
"expiresAt": "2026-03-20T01:30:00Z"
}
]
}
]
},
"message": "Success"
}
Response Fields:
| Field | Type | Description |
|---|
reservationKey | string | Reservation key (trùng request) |
orderId | string | Order ID gắn với reservation |
committedAt | string (ISO 8601) | Thời điểm commit |
items | array | Danh sách voucher đã commit (cùng cấu trúc như /reserve) |
Error Responses
| HTTP | Code | Mô tả |
|---|
| 400 | SHOP-PUR-006 | Reservation đã commit hoặc đã release (không thể commit lại) |
| 404 | SHOP-PUR-003 | Reservation không tồn tại hoặc không thuộc user |
9. POST /api/v1/vouchers/release
Huỷ reservation, trả voucher về inventory. Gọi khi payment thất bại hoặc user cancel đơn hàng.
cURL Example
curl --location 'https://promotion-shop-service.sit.vsf.services/api/v1/vouchers/release' \
--header 'client_id: {your-client-id}' \
--header 'client_secret: {your-client-secret}' \
--header 'X-Promotion-Api-Key: {your-api-key}' \
--header 'X-Merchant-Id: {your-merchant-id}' \
--header 'X-User-Id: 349874708' \
--header 'X-Request-Id: {your-request-id}' \
--header 'Content-Type: application/json' \
--data '{
"reservationKey": "{your-reservation-key}",
"reason": "PAYMENT_FAILED"
}'
Request
{
"reservationKey": "{your-reservation-key}",
"reason": "PAYMENT_FAILED"
}
Request Fields:
| Field | Type | Required | Description |
|---|
reservationKey | string | Yes | Reservation key từ /reserve |
reason | string | No | Lý do release (PAYMENT_FAILED, USER_CANCELLED, TIMEOUT, ...). Dùng cho logging |
Response
{
"code": "SUCCESS",
"data": {
"reservationKey": "{your-reservation-key}",
"orderId": "ORDER-20260319-001",
"releasedAt": "2026-03-19T01:40:00Z"
},
"message": "Success"
}
Response Fields:
| Field | Type | Description |
|---|
reservationKey | string | Reservation key (trùng request) |
orderId | string | Order ID gắn với reservation |
releasedAt | string (ISO 8601) | Thời điểm release |
Error Responses
| HTTP | Code | Mô tả |
|---|
| 400 | SHOP-PUR-007 | Reservation đã release hoặc đã commit (không thể release) |
| 400 | SHOP-PUR-006 | Reservation đã bị hết hạn |
| 404 | SHOP-PUR-003 | Reservation không tồn tại hoặc không thuộc user |
10. GET /api/v1/vouchers/reservation/{key}
Check trạng thái reservation hiện tại. Dùng để polling hoặc verify trạng thái reservation trước khi thực hiện hành động.
cURL Example
curl --location 'https://promotion-shop-service.sit.vsf.services/api/v1/vouchers/reservation/{your-reservation-key}' \
--header 'client_id: {your-client-id}' \
--header 'client_secret: {your-client-secret}' \
--header 'X-Promotion-Api-Key: {your-api-key}' \
--header 'X-Merchant-Id: {your-merchant-id}' \
--header 'X-User-Id: 349874708' \
--header 'X-Request-Id: {your-request-id}'
Path Parameters
| Parameter | Type | Required | Description |
|---|
key | string | Yes | Reservation key |
Response
{
"code": "SUCCESS",
"data": {
"reservationKey": "{your-reservation-key}",
"status": "RESERVED",
"reservedAt": "2026-03-19T01:30:00Z",
"expiresAt": "2026-03-19T01:45:00Z",
"items": [
{
"voucherPoolId": 402,
"quantity": 2,
"vouchers": [ ... ]
}
]
},
"message": "Success"
}
Response Fields:
| Field | Type | Description |
|---|
reservationKey | string | Reservation key |
status | string | Trạng thái: RESERVED, COMMITTED, RELEASED |
reservedAt | string (ISO 8601) | Thời điểm tạo reservation |
expiresAt | string (ISO 8601) | Thời điểm reservation hết hạn |
items | array | Danh sách voucher trong reservation |
Error Responses
| HTTP | Code | Mô tả |
|---|
| 404 | SHOP-PUR-003 | Reservation không tồn tại hoặc không thuộc user |
IV. Integration Notes
Authentication
Xem mục Authentication ở đầu tài liệu để biết cách lấy X-Promotion-Api-Key.
Encoding
- Tất cả request/response đều sử dụng UTF-8
- Query parameters tiếng Việt phải được URL-encoded đúng (RFC 3986)
- Ví dụ:
giảm giá → gi%E1%BA%A3m+gi%C3%A1
- Tất cả list APIs đều hỗ trợ pagination
- Page bắt đầu từ 0 (zero-indexed)
- Default size: 20, max size: 50 (search: max 100)
Idempotency
/reserve — Hỗ trợ idempotency qua header X-Idempotency-Key. Gửi lại cùng key sẽ trả lỗi SHOP-PUR-011 (409 Conflict) thay vì tạo reservation mới
/commit — Idempotent. Gọi lại với cùng reservationKey không tạo side effect mới
/release — Idempotent. Gọi lại với cùng reservationKey không tạo side effect mới
Reservation Lifecycle
- Reservation hiện không có auto-expire (TTL chưa được hỗ trợ)
- Client phải gọi
/release explicitly khi cancel hoặc payment thất bại
- Client phải gọi
/commit khi payment thành công
Currency Support
| Currency | Mô tả | Payment | Trạng thái |
|---|
VND | Thanh toán bằng VNĐ | Qua payment gateway | Mặc định, luôn hỗ trợ |
VPOINT | Thanh toán bằng VPoint | Trừ trực tiếp từ wallet | Hỗ trợ |
V. Phụ lục
A. Bảng mã lỗi
| HTTP | Code | Mô tả |
|---|
| 200 | SUCCESS | Thành công |
| 400 | SHOP-001 | Lỗi validation chung |
| 400 | SHOP-PUR-001 | Tổng tiền amount không khớp |
| 400 | SHOP-PUR-002 | Giá voucher đã thay đổi (stale price) |
| 400 | SHOP-PUR-006 | Reservation đã commit/release/hết hạn |
| 400 | SHOP-PUR-007 | Reservation đã release/commit (không thể hủy) |
| 400 | SHOP-PUR-012 | Voucher hết hàng hoặc không đủ số lượng |
| 400 | SHOP-PUR-013 | Lỗi khi tạo issued promotion |
| 400 | SHOP-PUR-014 | Giá unitPrice không khớp sellingPrice |
| 400 | SHOP-PUR-015 | Loại tiền currency không khớp |
| 400 | SHOP-PUR-016 | Vượt giới hạn mua (daily limit / total limit) |
| 400 | SHOP-VOU-002 | Từ khóa tìm kiếm quá ngắn (< 2 ký tự) |
| 404 | SHOP-VOU-001 | Voucher không tồn tại hoặc đã xóa |
| 404 | SHOP-CAM-001 | Campaign không tồn tại hoặc không active |
| 404 | SHOP-PUR-003 | Reservation không tồn tại hoặc không thuộc user |
| 409 | SHOP-PUR-011 | Order ID đã được sử dụng (duplicate reservation) |
| Tag | Mô tả | Hiển thị |
|---|
HOT_DEAL | Ưu đãi hot | Trang chủ + Tab Hot Deal |
FLASH_SALE | Flash sale (có countdown) | Trang chủ + Tab Flash Sale |
C. Voucher Usage Type
| Type | Mô tả |
|---|
ONLINE | Sử dụng online trên V-App |
OFFLINE | Sử dụng tại cửa hàng |
D. Reservation Status
| Status | Mô tả | Transition |
|---|
RESERVED | Đang giữ chỗ, chờ thanh toán | → COMMITTED / RELEASED |
COMMITTED | Đã thanh toán thành công | Terminal state |
RELEASED | Đã huỷ bởi user/system | Terminal state |