Chia sẻ thẻ & tự link thẻ
Trang này dành cho ba nhóm FE:
| FE | Phần cần đọc |
|---|---|
| Corporate portal — admin chia sẻ thẻ cho nhân viên | §2 |
| App xperise / XPay — nhân viên dùng thẻ được chia sẻ | §3 |
| App hoặc trang web mở từ Zalo OA — người dùng tự link thẻ | §4 |
Trang web công khai từ Zalo OA — không đăng nhập, xác thực SĐT bằng OTP rồi link thẻ — có trang riêng: Link thẻ bằng OTP SĐT.
Mọi field, quyền và mã lỗi ở đây đọc thẳng từ code card-core. Chỗ nào chưa có được gom ở §6 — Chưa có.
Base URL:
| Môi trường | URL |
|---|---|
| Staging | https://api-staging-core-card.uts.network |
| Local | http://localhost:3000 |
Spec máy đọc được: docs/openapi.generated.json trong repo card-core.
1. Mô hình
Mọi thẻ đều là thẻ bearer: thẻ của công ty, holder (cards.user_id) không phải một nhân viên
cụ thể. Một nhân viên muốn trả bằng thẻ thì thẻ phải được chia sẻ cho họ.
- Nhiều–nhiều: một thẻ chia sẻ cho nhiều nhân viên, một nhân viên giữ nhiều thẻ.
- Có ba cách để một người được link vào thẻ:
- Admin chia sẻ (§2): corporate admin hoặc platform.
- Tự link (§4): người dùng chứng minh mình cầm thẻ bằng số thẻ đầy đủ + PIN in kèm thẻ.
- Tổng đài / ops link giúp: chọn thẻ của công ty khách, hoặc nhập số thẻ + PIN khách đọc qua điện thoại. Xem Thanh toán chuyến xe bằng thẻ.
Người được link làm được gì
| Việc | Holder / người được link |
|---|---|
Thấy thẻ trong GET /my/cards, xem số dư, hạn mức | ✅ / ✅ |
Tạo mã QR một lần (POST /my/cards/{card_id}/tokens) | ✅ / ✅ |
Xác nhận QR order của merchant (POST /payment-intents/{intent_id}/confirm) | ✅ / ✅ |
Chọn thẻ để trả chuyến xe đặt qua app (POST /my/rides) | ✅ / ✅ |
Xem chính sách thẻ (GET /my/cards/{card_id}/policy) | ✅ / ✅ |
| Xem PAN, kích hoạt, khoá, đổi PIN, đổi cách xác thực | ✅ / ❌ 404 |
Các thao tác "của chủ thẻ" trả 404 not_found cho người được link, không phải 403, để không lộ
việc thẻ tồn tại.
Ai đã tiêu: acting_user_id
Mỗi giao dịch giữ hai người:
| Field | Nghĩa |
|---|---|
user_id | Holder của thẻ — không đổi |
acting_user_id | Người thực sự trả trong app: người tạo mã QR, xác nhận QR order hoặc đi chuyến xe. null khi không biết ai (quẹt/chạm thẻ vật lý ở POS, s2s) |
GET /my/payments của một nhân viên trả giao dịch trên thẻ của chính họ cộng giao dịch do chính
họ thực hiện trên thẻ được chia sẻ — không bao giờ thấy giao dịch của người dùng chung khác.
2. Corporate portal — chia sẻ thẻ cho nhân viên
2.1 Quyền
| Credential | Header | Phạm vi |
|---|---|---|
| Corporate admin | Authorization: <raw corporate access token> (không prefix Bearer) | Chỉ thẻ của corporate mình; thẻ corporate khác → 404 |
| Platform key | X-API-Key: <PLATFORM_API_KEY> | Mọi thẻ. Chỉ backend giữ — không để lộ ra trình duyệt |
System admin (token Store) không gọi được nhóm route /cards/* này. Tổng đài viên dùng route
riêng bên dispatch — xem Thanh toán chuyến xe bằng thẻ.
2.2 Endpoint
| Method | Path | Mô tả |
|---|---|---|
GET | /cards/{card_id}/users | Danh sách nhân viên đang được chia sẻ thẻ, kèm hạn link |
POST | /cards/{card_id}/users | Chia sẻ thẻ cho một nhân viên, có thể kèm hạn link (§2.3) |
PATCH | /cards/{card_id}/users/{user_id} | Đổi hạn link (§2.3) |
DELETE | /cards/{card_id}/users/{user_id} | Ngừng chia sẻ |
Chia sẻ thẻ
POST /cards/{card_id}/users
Authorization: <corporate access token>
Content-Type: application/json
{
"user_id": "4242",
"windows": [
{ "valid_from": "2026-11-01T00:00:00+07:00", "valid_until": "2026-11-06T00:00:00+07:00" },
{ "valid_from": "2026-11-20T00:00:00+07:00", "valid_until": "2026-11-26T00:00:00+07:00" }
]
}
| Field | Type | Required | Mô tả |
|---|---|---|---|
user_id | string (chữ số) | ✅ | User id (Store) của nhân viên |
windows | mảng window | — | Các khoảng nhân viên được trả bằng thẻ. Bỏ trống hoặc [] = trả lúc nào cũng được. Luật ở §2.3 |
{
"card_id": "f4eedbf6-97e7-4ac9-934c-f1cce01cce98",
"user_id": "4242",
"name": "Lê Hoàng Tuấn",
"phone": "+84901234567",
"email": "tuan.le@acme.vn",
"linked_by": "1093",
"linked_by_type": "corporate_admin",
"windows": [
{ "valid_from": "2026-10-31T17:00:00.000Z", "valid_until": "2026-11-05T17:00:00.000Z" },
{ "valid_from": "2026-11-19T17:00:00.000Z", "valid_until": "2026-11-25T17:00:00.000Z" }
],
"link_status": "not_started",
"created_at": "2026-10-30T03:00:00.000Z"
}
- Corporate admin: backend kiểm
user_idlà nhân viên active của corporate qua backend corporate, bằng chính token của admin, rồi lưu cache thông tin nhân viên (tên, SĐT). - Platform key: được tin, chỉ từ chối khi
user_idđã được biết là nhân viên của corporate khác. - Gọi lại nhiều lần an toàn: link đã có thì trả về link cũ. Nhưng gọi lại với
windowskhác hạn đang có thì bị409 card_user_exists. Muốn đổi hạn thì dùngPATCH(§2.3). namelấy từ cache nhân viên, họ trước tên sau.nullkhi chưa có.phone(E.164) vàemaillấy từ cache nhân viên, và chỉ có khi đó là nhân viên của chính corporate sở hữu thẻ. Còn lại lànull.GET /cards/{card_id}/userstrả mảng các phần tử cùng shape như trên.- Thời gian trả về luôn ở UTC (
…Z). FE tự đổi sang giờ Việt Nam để hiển thị.
2.3 Hạn dùng (windows)
Có hai lớp hạn, đều do admin đặt và đều là danh sách các khoảng thời gian (window):
| Lớp | Lưu ở | Đặt qua | Áp cho |
|---|---|---|---|
| Hạn thẻ | card_validity_windows | validity_windows trên POST /cards, POST /physical-cards/activate, PATCH /cards/{card_id} | Mọi người trả bằng thẻ: holder, người được link, quẹt/chạm ở POS, s2s |
| Hạn link | card_user_windows | windows trên POST /cards/{card_id}/users (§2.2) và PATCH /cards/{card_id}/users/{user_id} | Chỉ một nhân viên được link |
Một window:
{ "valid_from": "2026-11-01T00:00:00+07:00", "valid_until": "2026-11-06T00:00:00+07:00" }
| Field | Type | Mô tả |
|---|---|---|
valid_from | date-time có UTC offset, hoặc null | Từ lúc này. null hoặc bỏ trống = không giới hạn đầu |
valid_until | date-time có UTC offset, hoặc null | Tới lúc này, không tính chính thời điểm đó. null hoặc bỏ trống = không có hạn cuối. Phải sau valid_from |
Luật chung cho cả hai lớp. Sai luật thì trả 422 validation_failed và không ghi gì:
- Mỗi window phải có ít nhất một đầu. Muốn bỏ giới hạn thì gửi danh sách rỗng, không gửi
window
{}. - Thời gian bắt buộc có offset:
2026-11-01T00:00:00+07:00hoặc…Z. Gửi2026-11-01hay2026-11-01T00:00:00(không offset) bị422. - Window đã kết thúc bị bỏ khi ghi. Vì vậy FE gửi lại nguyên danh sách
GETtrả về, thêm window mới, mà không cần tự lọc window cũ. Nếu mọi window gửi lên đều đã qua, backend giữ window kết thúc muộn nhất để danh sách vẫn là "hết hạn", không biến thành "không giới hạn". - Tối đa 20 window chưa kết thúc, và tối đa 100 window mỗi request (tính cả window đã qua).
- Các window không được chồng nhau. Kề nhau thì được: một window kết thúc đúng lúc window sau bắt đầu.
- Đây là các khoảng rời nhau, không phải lịch lặp (kiểu "thứ 2–6, 8h–17h").
Đổi hạn link
PATCH /cards/{card_id}/users/{user_id}
Authorization: <corporate access token>
Content-Type: application/json
{ "windows": [ { "valid_from": "2026-12-01T00:00:00+07:00", "valid_until": null } ] }
windowsbắt buộc và thay toàn bộ hạn link.[]= bỏ giới hạn.- Trả về link cùng shape với
POST(§2.2),200. Link không tồn tại →404 not_found. - Thêm một window phía trước cho link đã
expiredthì nhân viên trả được lại trong window đó.
Hạn thẻ (thẻ gốc)
Hạn thẻ gắn trên chính thẻ, nên áp cho mọi người trả bằng thẻ, kể cả holder. Mọi người được link đều chịu chung hạn này, cộng thêm hạn link riêng của từng người.
| Lúc | Request | Ai gọi |
|---|---|---|
| Tạo thẻ ảo | POST /cards với validity_windows | Corporate admin |
| Kích hoạt thẻ vật lý | POST /physical-cards/activate với validity_windows | Corporate admin |
| Sửa | PATCH /cards/{card_id} với validity_windows | Corporate admin hoặc platform key |
PATCH /cards/{card_id}
Authorization: <corporate access token>
Content-Type: application/json
{
"validity_windows": [
{ "valid_from": "2026-11-03T00:00:00+07:00", "valid_until": "2026-11-11T00:00:00+07:00" }
]
}
{
"card_id": "f4eedbf6-97e7-4ac9-934c-f1cce01cce98",
"status": "active",
"expires_at": "2028-10-31T16:59:59.000Z",
"validity": {
"status": "not_started",
"windows": [
{ "valid_from": "2026-11-02T17:00:00.000Z", "valid_until": "2026-11-10T17:00:00.000Z" }
]
}
}
PATCHcó gửivalidity_windowsthì thay toàn bộ hạn thẻ,[]= bỏ giới hạn. Không gửi field này thì giữ nguyên. Cùng luật window như trên.validitycó trên mọi response thẻ:POST /cards,PATCH /cards/{card_id},GET /cards,GET /cards/{card_id}, vàGET /my/cardsphía app (§3.1).- Đổi hạn thẻ không đụng tới hạn link của ai.
Thẻ có ba điều kiện riêng, cái nào không qua cũng không trả được:
| Điều kiện | Field | Không qua thì decline | Ai sửa |
|---|---|---|---|
| Trạng thái | status | Theo trạng thái thẻ (khoá, chưa kích hoạt…) | Route activate / block / unblock |
| Ngày hết hạn | expires_at | card_expired | Thẻ ảo: PATCH expires_at. Thẻ vật lý: không sửa được (in trên thẻ và trong chip) — 422 expiry_issuer_owned |
| Hạn thẻ | validity_windows | card_outside_validity_window | PATCH validity_windows |
validity.statuschỉ tính theo window. Thẻ đã quáexpires_athoặc đang khoá vẫn có thể cóvalidity.status = "active". FE phải xét cả ba.- Window kéo dài quá
expires_atkhông gia hạn thẻ. Sauexpires_atthẻ bị declinecard_expired, dù window còn.
Link bằng PIN không có hạn. Tự link (§4), link qua trang Zalo OA và tổng đài link giúp đều tạo link không giới hạn. Chúng cũng không bao giờ đổi hạn admin đã đặt cho một link có sẵn. Chỉ API admin ở trên đặt được hạn link.
Trạng thái
Thẻ có validity: { status, windows }, link có link_status + windows. Cả hai cùng dùng ba giá trị:
status | Nghĩa |
|---|---|
active | Không có window nào (không giới hạn), hoặc đang ở trong một window. Chỉ trạng thái này mới trả được |
not_started | Không ở trong window nào, nhưng còn ít nhất một window phía trước: trước window đầu, hoặc đang ở giữa hai window |
expired | Mọi window đã qua |
Ngoài hạn, thẻ vẫn hiện ở mọi danh sách (portal lẫn app), chỉ là không trả được. Thẻ chỉ biến khỏi app của nhân viên khi xoá link.
Trên portal:
GET /cardstrảvaliditycủa từng thẻ vàlinked_users: [{ user_id, name, windows, link_status }].GET /cards/{card_id}trảvalidity.GET /cards/{card_id}/userstrảwindowsvàlink_statuscủa từng link.
Hai lớp hạn cùng lúc
Một nhân viên được link trả được bằng thẻ khi cả hai lớp đều active:
trả được = thẻ status "active" VÀ chưa quá expires_at VÀ validity.status = "active" VÀ link.status = "active"
Holder (thẻ của chính họ) chỉ chịu hạn thẻ.
Hai lớp được lưu và kiểm độc lập. Admin có thể lưu một hạn link nằm ngoài hạn thẻ, và backend không báo lỗi. Thực tế nhân viên chỉ trả được ở phần giao nhau:
| Hạn thẻ | Hạn link | Nhân viên trả được |
|---|---|---|
| 03/11 – 10/11 | không giới hạn | 03/11 – 10/11 |
| không giới hạn | 01/11 – 05/11 | 01/11 – 05/11 |
| 03/11 – 10/11 | 01/11 – 05/11 | 03/11 – 05/11 |
| 03/11 – 10/11 | 20/11 – 25/11 | Không lúc nào — link active từ 20/11 nhưng thẻ đã expired |
Portal nên tự so hai lớp và cảnh báo admin khi một window của link nằm ngoài mọi window của thẻ. Đổi hạn thẻ không đụng tới hạn link, và ngược lại.
Từng đường trả tiền xử lý hai lớp như sau:
| Đường trả | Ngoài hạn link | Ngoài hạn thẻ |
|---|---|---|
Tạo mã QR (POST /my/cards/{card_id}/tokens) | 403 card_link_inactive | 422 card_outside_validity_window |
| Mã QR đã tạo | Sống tối đa tới cuối window nào kết thúc sớm hơn (thẻ hoặc link) đang chứa lúc tạo. Admin rút hạn hay xoá link sau đó thì merchant quét bị decline card_link_inactive | Merchant quét bị decline card_outside_validity_window |
Xác nhận QR order (POST /payment-intents/{intent_id}/confirm) | 403 card_link_inactive | Giao dịch bị decline card_outside_validity_window |
| Quẹt/chạm thẻ, nhập số thẻ ở POS, s2s | Không áp (không biết ai đang cầm thẻ) | Decline card_outside_validity_window |
Chọn thẻ khi đặt xe (POST /my/rides) | 422 invalid_payment_card | 422 invalid_payment_card |
Trừ thẻ lúc hoàn tất chuyến, thẻ đã chọn lúc đặt (payment_card_id) | Không kiểm lại — đặt xe trong hạn link thì vẫn trừ dù hạn đã hết, miễn link chưa bị xoá | Kiểm lại — hạn thẻ đã hết thì 409 payment_declined, reason = card_outside_validity_window, ticket vẫn mở |
| Trừ thẻ lúc hoàn tất chuyến, không chọn thẻ | Lúc hoàn tất mới chọn thẻ active duy nhất đang trong cả hai lớp hạn. Không còn thẻ nào thì 409 no_payment_card | Như cột bên trái |
| Nhận OTP của thẻ | Nhân viên ngoài hạn link không có trong danh sách người nhận OTP | Không đổi danh sách người nhận, nhưng giao dịch vẫn bị decline |
Ngừng chia sẻ
DELETE /cards/{card_id}/users/{user_id} → 200 { "card_id": "…", "user_id": "4242", "unlinked": true }
Ngừng chia sẻ làm hết hạn ngay các mã QR mà nhân viên đó đã tạo trên thẻ mà chưa dùng (mã có PIN sống tới 7 ngày). Giao dịch cũ vẫn nằm trong lịch sử của họ.
2.4 Mã lỗi
| HTTP | code | Khi nào |
|---|---|---|
| 404 | not_found | Thẻ không có / không thuộc corporate của admin; PATCH hoặc DELETE một người chưa được link |
| 422 | validation_failed | user_id sai định dạng, hoặc là chính holder của thẻ. Hoặc windows / validity_windows sai luật §2.3: window không có đầu nào, thiếu UTC offset, valid_until không sau valid_from, chồng nhau, quá 20 window chưa kết thúc, quá 100 window mỗi request |
| 409 | card_user_exists | POST lại cho người đã được link, với windows khác hạn đang có. details.windows = hạn hiện tại. Dùng PATCH |
| 422 | user_not_in_corporate | Không phải nhân viên active của corporate (admin), hoặc là nhân viên corporate khác (platform) |
| 503 / 502 | admin_auth_unavailable / corporate_upstream_error | Không hỏi được backend corporate |
2.5 Sự kiện
Qua outbox (GET /events, topic card-core.cards):
event_type | Payload |
|---|---|
card.user_linked | { card_id, corporate_id, user_id, linked_by: { id, type }, via, windows } — via = admin (§2) hoặc card_pin (tự link, §4); windows = hạn link lúc tạo ([] = không giới hạn) |
card.user_link_updated | { card_id, corporate_id, user_id, windows, updated_by: { id, type } } — chỉ phát khi hạn link thực sự đổi |
card.user_unlinked | { card_id, corporate_id, user_id, unlinked_by: { id, type } } |
card.validity_updated | { card_id, corporate_id, validity_windows } — chỉ phát khi PATCH làm hạn thẻ thực sự đổi |
card.issued | Tạo thẻ ảo (POST /cards): có validity_windows. Kích hoạt thẻ vật lý: không có validity_windows (chỉ form_factor: "physical") — muốn biết hạn thì đọc GET /cards/{card_id} |
Portal nên dựa vào via = card_pin để báo admin "có người vừa tự link thẻ X", vì đó là lần duy nhất
admin biết.
3. App — dùng thẻ được chia sẻ
Header: Authorization: <raw Store access token của user>.
3.1 GET /my/cards, GET /my/cards/{card_id}
Mỗi thẻ có thêm field access:
access | Nghĩa | App hiện |
|---|---|---|
owner | Thẻ của chính user | Đủ chức năng |
linked | Thẻ được chia sẻ cho user | Ẩn xem PAN, kích hoạt, khoá, đổi PIN, đổi cách xác thực |
Và hai field về hạn (§2.3):
| Field | Có ở | Nghĩa |
|---|---|---|
validity: { status, windows } | Mọi thẻ | Hạn thẻ. Áp cho cả holder |
link: { status, windows } | Thẻ linked. null trên thẻ của chính user | Hạn link của chính user |
{
"card_id": "f4eedbf6-97e7-4ac9-934c-f1cce01cce98",
"access": "linked",
"status": "active",
"validity": { "status": "active", "windows": [] },
"link": {
"status": "not_started",
"windows": [
{ "valid_from": "2026-10-31T17:00:00.000Z", "valid_until": "2026-11-05T17:00:00.000Z" },
{ "valid_from": "2026-11-19T17:00:00.000Z", "valid_until": "2026-11-25T17:00:00.000Z" }
]
}
}
Thẻ trả được khi status = "active", chưa quá expires_at, validity.status = "active" và
link là null hoặc link.status = "active". Ngoài hạn, thẻ vẫn nằm trong danh sách. App nên làm mờ thẻ, chặn
nút trả tiền và ghi lý do:
| Trường hợp | Gợi ý hiển thị |
|---|---|
link.status = "not_started" hoặc validity.status = "not_started" | "Dùng được từ …" — lấy valid_from của window kế tiếp |
link.status = "expired" hoặc validity.status = "expired" | "Đã hết hạn" — hướng dẫn liên hệ admin công ty |
Cả hai lớp đều có window phía trước thì thẻ chỉ dùng được lại khi cả hai cùng active: FE tính
phần giao nhau của hai danh sách window. Một lớp expired thì thẻ không dùng được nữa, dù lớp kia
còn window phía trước.
3.2 Trả tiền
Không có gì khác với thẻ của mình:
- Mã QR:
POST /my/cards/{card_id}/tokens. Mã ghi lại người tạo, nên giao dịch merchant quét mã đó cóacting_user_id= user. - QR order của merchant:
POST /payment-intents/{intent_id}/confirmvớicard_idcủa thẻ được chia sẻ. - Chuyến xe: xem Thanh toán chuyến xe bằng thẻ.
Ngoài hạn thì từng đường trả báo lỗi khác nhau: 403 card_link_inactive (hạn link),
422 card_outside_validity_window (hạn thẻ), 422 invalid_payment_card (chuyến xe). Bảng đầy đủ ở
cuối §2.3.
3.3 GET /my/payments
Trả giao dịch trên thẻ của user cộng giao dịch user thực hiện trên thẻ được chia sẻ. Lọc theo
card_id vẫn dùng như cũ.
4. Tự link thẻ bằng số thẻ + PIN
Người dùng tự thêm thẻ vào tài khoản: có số thẻ đầy đủ và PIN in kèm thẻ là đủ quyền, giống như ở quầy merchant. Không cần corporate admin chia sẻ trước.
4.1 Endpoint
POST /my/cards/link
Authorization: <raw Store access token của user>
Content-Type: application/json
{ "pan": "9686860012345678", "pin": "246810" }
| Field | Type | Required | Mô tả |
|---|---|---|---|
pan | string, 16 chữ số | ✅ | Số thẻ in trên mặt thẻ |
pin | string, 4–8 chữ số | ✅ | PIN in kèm thẻ |
200 — thẻ, cùng shape với GET /my/cards/{card_id}, access: "linked". Nếu user chính là
holder thì access: "owner" và không link gì thêm.
4.2 Chống dò đoán
| Luật | Hệ quả |
|---|---|
| Sai số thẻ và sai PIN trả cùng một lỗi | Không dò được số thẻ nào có thật |
| 3 lần sai PIN trên một thẻ trong 15 phút (tính từ lần đúng gần nhất) | Thẻ khoá 15 phút. Khoá này dùng chung với luồng nhập PIN ở merchant |
| Thẻ đang khoá | Từ chối ngay, không so PIN |
| 5 lần sai của một user trong 1 giờ, tính mọi thẻ kể cả số thẻ không tồn tại | User bị tạm chặn cho tới khi các lần sai đó cũ hơn 1 giờ |
| Thẻ bị khoá / hết hạn | Chỉ báo cho người nhập đúng PIN |
Mọi lần kiểm đều được ghi lại, lần sai vẫn được đếm dù request trả lỗi.
4.3 Mã lỗi
| HTTP | code | Khi nào | FE làm gì |
|---|---|---|---|
| 422 | validation_failed | pan / pin sai định dạng | Báo ở ô nhập |
| 422 | card_pin_invalid | Sai số thẻ hoặc sai PIN | "Số thẻ hoặc PIN không đúng" |
| 409 | card_locked_too_many_attempts | Thẻ đang khoá — details.locked_until | Báo thử lại sau giờ đó |
| 429 | too_many_pin_attempts | User đã sai 5 lần trong 1 giờ qua | Báo thử lại sau |
| 422 | card_has_no_pin | Thẻ không có PIN in kèm | Hướng dẫn liên hệ admin công ty |
| 422 | card_not_active | Thẻ bị khoá / hết hạn — details.status | Hướng dẫn liên hệ admin công ty |
card-core không lưu số thẻ hay PIN ở đâu cả, và không ghi body của route này vào log. FE cũng phải như vậy: không lưu vào storage, không gửi lên analytics hay crash log, xoá ô nhập ngay sau khi gửi.
4.4 Trang web mở từ Zalo OA
Trang web dùng đăng nhập OTP của Store (giống web xperise hiện tại, xem apis/otp.ts trong
bestravel-fe-nuxt). OTP tới khách qua Zalo (ZNS) hoặc SMS; SĐT chưa có tài khoản thì tài khoản được
tạo luôn.
- Thêm origin của trang web vào biến
CORS_ORIGINScủa card-core. - Không đưa token lên URL.
- Không muốn đăng nhập Store? Trang web dùng luồng Link thẻ bằng OTP SĐT: card-core tự gửi OTP tới SĐT, và tạo staff cho SĐT chưa có.
5. Kiểm tra trên staging
Chưa có bộ API test Bruno cho các route trong trang này: chúng chỉ có trên staging sau khi bản card-core mới được deploy.
6. Chưa có
| Hạng mục | Tình trạng |
|---|---|
| Cài đặt "cho phép tự link" theo từng corporate | Chưa có — corporate chỉ được báo (card.user_linked, via: card_pin) và tự bỏ link sau |
| Lưu cặp Zalo OA user id ↔ SĐT ↔ user id | Chưa có. Khi làm, cặp này chỉ dùng để gửi thông báo: OTP không chứng minh SĐT là số đăng ký của tài khoản Zalo đó |
| Tạo nhân viên (corporate staff) cho người tự link | Chưa có — tự link chỉ cần user id. Luồng OTP SĐT chỉ tạo staff trong corporate_staffs của card-core |
Lọc GET /cards theo người được link | Chưa có |
| Bắt hạn link nằm trong hạn thẻ | Chưa có — hai lớp lưu độc lập, portal tự cảnh báo (§2.3) |
| Hạn dùng lặp theo lịch (thứ, giờ trong ngày) | Chưa có — chỉ có các khoảng rời nhau |
| Thêm hoặc xoá một window lẻ | Chưa có — PATCH luôn gửi cả danh sách |