mcard.vn

Đặc tả S2Service API

Quy tắc, cấu trúc và các hỗ trợ kết nối API...
# Mục đích

S2Service là bộ RESTful API mới của mCard.vn, được cung cấp cho các đối tác có nhu cầu sử dụng dịch vụ như nạp tiền điện thoại và game, kiểm tra số dư, nạp tiền hộ đại lý (*sẽ triển khai sau*). Các dịch vụ này mCard đang cung cấp cho khách hàng của mình thông qua giao diện Web hoặc ứng dụng Mobile App, nay được mở rộng cung cấp cho đối tác lập trình khác. Về API mua thẻ từ kho thì đã có [S2Sell] từ vài năm rồi và được khai thác bởi một số đơn vị khác nhau.

# Tổ chức phân loại dịch vụ

Mỗi mục dịch vụ được gắn mã gọi là TOPUPTYPEID, thông tin về dịch vụ gồm nhiều các bản ghi có các tính chất kèm theo là:

1. **TOPUPTYPEID**: mã ID dịch vụ, có **duy nhất toàn hệ thống**. Mã này ít khi thay đổi, mỗi khi thay đổi sẽ có thông báo trước, nên đại lý có thể lưu lại hoặc kiểm tra tự động mỗi ngày. Mã này thường kèm theo số tiền mệnh giá (nhưng đặt sẽ không theo quy tắc nào cả). Ví dụ TVTEPRE10 là "nạp tiền Viettel 10k", TVTEPRE20 là "nạp tiền Vietel trả trước 20k", TVTVIN20 là "nạp tiền Viettel trả trước giá rẻ 20k" nhưng giá cạnh tranh hơn nhưng với điều kiện dịch vụ khác (ví dụ bị áp sản lượng mỗi ngày). Lưu ý rằng cùng một mệnh giá, một nhà mạng có thể có nhiều hơn 1 mã này, tuỳ theo kênh mà mCard kết nối đến và sẽ có giá chiết khấu là khác nhau, tính chất dịch vụ áp sản lượng và thời gian đáp ứng là khác nhau.
2. **TOPUPNAME**: Tên dịch vụ (ví dụ "Nạp tiền Viettel giá rẻ 10k")
3. **TOPUPVALUE**: Số tiền mệnh giá để tham khảo (thường là tròn theo nhà mạng cung cấp, ví dụ 10000, 20000, 100000, 500000).
4. **SELLVALUE**: Số tiền giá chiết khấu: là số tiền mà mcard sẽ trừ đi trong tài khoản, ví dụ 9420 (cho mệnh giá 10k).
5. **SERVICEAVAIL**: giá trị số nguyên này cho biết trạng thái dịch vụ có thể sử dụng hay không, nếu > 0 là đang hoạt động được. Đại lý có thể truy vấn lấy thông tin trực tuyến của dịch vụ, và trạng thái dịch vụ có thể thay đổi trong ngày, ví dụ như hết sản lượng, hết tiền mà mCard ký quỹ với nhà mạng.
5. **CATECODE**: mã phân loại category, do danh sách dịch vụ là dạng phẳng (1 danh sách dài), nên mã này cho phép chia nhóm các dịch vụ để dễ quản lý (Ví dụ tất cả topup Viettel có mã CATECODE là TVTE, nếu nhóm Viettel giá rẻ thì là TVTV - nhóm này đi theo kênh nạp tiền khác). Đại lý tuỳ ý có thể dùng hay không dùng mã này để phân loại, hoặc tự phân loại theo kiểu của mình.

Để lấy danh sách toàn bộ loại, thì truy vấn đường dẫn */topup*, để lấy thông tin 1 loại mã, thì truy vấn */topup/{TOPUPTYPEID}*. Chi tiết cấu trúc dữ liệu, xin xem đặc tả bên dưới. Cách tổ chức theo nhóm CATECODE, có thể xem [Bảng giá](prices) để thấy các sắp xếp hàng hoá của mCard.vn.

# Thiết kế kỹ thuật API

Do các dịch vụ nạp tiền mCard thì không sở hữu, mà phải kết nối đến nhà mạng hoặc trung gian qua đối tác bán sỉ của nhà mạng, thông qua sự đầu tư vốn, hợp tác triển khai phân phối dịch vụ cho khách hàng mua lẻ hoặc có nhu cầu API. Để đáp ứng nhu cầu này, tất cả các dịch vụ của mCard.vn cung cấp qua API hay qua Web thì đều được thiết kế theo cách tối ưu nhất cho xử lý các yêu cầu topup: đó là xử lý **bất đồng bộ** (asynchronous processing). Các bước xử lý nói chung gồm 3 giai đoạn chính:

1. Giai đoạn nhận yêu cầu, kiểm tra hợp lệ dữ liệu, ghim giữ tiền và bắt đầu xử lý.
2. Giai đoạn thực thi, có nhiều công đoạn.
3. Giai đoạn trả lại kết quả, xác nhận trừ tiền hoặc hoàn trả.

Tất cả các công đoạn trên đều có thể kiểm tra trạng thái qua API và không có truy vấn nào (hoặc hàm nào) là "giữ chờ" cả (non-blocking). Có nghĩa là ở giai đoạn 1, gửi yêu cầu, thì API đó sẽ trả ra kết quả ngay với trạng thái được hiểu là "chờ đến lượt", mCard sẽ trả về 2 số nguyên có tên là **TXNID** (mã giao dịch) và **TOPUPID** (mã thực hiện topup). Mã giao dịch TXNID có một mã cho mỗi lần gửi yêu cầu (trừ khi chúng bị xé lẻ), còn mã TOPUPID có cho mỗi DEST (mỗi số/tài khoản nhận tiền) ở trong yêu cầu (tức là 1 TXN có thể chứa 1-nhiều TOPUPID). Xong việc tiếp nhận yêu cầu, thì tiếp theo ở giai đoạn 2, chương trình dịch vụ của mCard sẽ thực hiện yêu cầu nạp tiền đó (ví dụ gửi qua nhà mạng, đối tác bán sỉ, hoặc thao tác tay, bán thủ công bởi nhân viên), quá trình này có thể kéo dài đến cả 60 giây, do đó không phù hợp để chờ đợi với gọi API qua Internet, và cũng phản cảm cho khách hàng, nhưng ứng dụng mCard chạy ngầm thì có thể chờ và xử lý song song nhiều tiến trình dài hơn. Khi việc chờ nhà mạng nạp xong tiền, trả kết quả, hoặc báo lỗi thì đến giai đoạn 3. ghi nhận và báo kết quả. Kết quả thành công hay thất bại sẽ được cập nhật trạng thái vào bản ghi chỉ định liên quan, và đại lý có thể truy vấn kiểm tra trạng thái (polling) hoặc có thể đăng nhập vào Web mCard.vn, rồi vào menu Server API => nút "Callback Setup" để thiết lập.

Trường chỉ định trạng thái có tên là **EXEC_STAT** với ý nghĩa là trạng thái thực thi của nó, nó sẽ nhận 1 trong các giá trị trạng thái là: NEW, QUEUED, RUNNING, PENDING, **FINISH, REJECT, ERROR** (3 giá trị trạng thái sau là trạng thái cuối cùng của mỗi topup), theo hình thức biến đổi trạng thái như sau:

![Trạng thái S2Service](s2service-stat-flow.png)

Để xác thực thì sử dụng API Key được tạo tại trang "Server API" trong giao diện Web, và thiết lập IP được phép gọi.

# Xử lý với ngoại lệ (Exception), HTTP Status và Error code

Khác với nhiều nơi triển khai API, mCard **không sử dụng error code để phân biệt kết quả và tác động đến chu trình xử lý** (ví dụ hoàn tiền hay không). Lý do là error code không chỉ là kết quả gửi yêu cầu topup, mà có cả truy vấn hay nhiều API khác, giá trị lại có thể bổ sung thêm vào danh sách dài các loại lỗi khi phát sinh thêm chức năng, và error code là có tính tức thời, không biến đổi. **Thứ duy nhất quyết định rằng giao dịch thành công hay không thành công là EXEC_STAT** trên từng TOPUPID đã đại lý gửi đến mCard, mà giá trị này sẽ biến đổi dần **đến trạng thái cuối** mới ngưng biến đổi, mọi thứ lỗi thuộc loại là lỗi thực thi mới có thể khiến cho EXEC_STAT thành trạng thái ERROR, trường TOPUP_RESP thường có giá trị nào đó tương ứng với đối tác mCard, nó không có giá trị định trước mà mCard tự ánh xạ nó thành ERROR tuỳ theo, ví dụ với Viettel thì RESP=0 là lỗi, còn với Mobifone RESP=1 mới là lỗi, do đó chỉ cần dựa vào EXEC_STAT là đủ biết lỗi hay không lỗi, chi tiết biến đổi trạng thái xin xem ở mục trên. 

Error code trong trường hợp này chỉ giúp cho người vận hành hoặc lập trình viên đối tác biết là hệ thống thực thi đã mắc lỗi gì cho mỗi hành động, hoặc đơn giản biết là "có lỗi vận hành" và trả lại tiền (phía đại lý). Error code của mCard **không bao giờ** tác động đến EXEC_STAT cả, chỉ có quá trình thực thi của process mới tự quyết định xem EXEC_STAT là gì mà thôi.

Các mã lỗi vận hành logic và có thể có hoặc có thể không có tuỳ thời điểm và trạng thái. đôi khi nó kèm luôn trong kết quả gây khó hiểu. Một lỗi điển hình là logic đó là lỗi **-202 hết tiền**, khi gặp lỗi này, thì hệ thống dừng không chạy yêu cầu nữa, nó vẫn cho yêu cầu được xử lý bình thường các DEST khác đã lọt (vì đã đủ tiền để trừ đi).  Có thể phân giải tình huống đặc biệt này như sau:

1. Đại lý (các bạn) gửi 3 số điện thoại nạp tiền 50k, mà trong TK chỉ còn 130k.
2. API nhận yêu cầu, nó lần lượt trừ tiền, được cho 2 số đầu tiên thành công, đến cái thứ 3 thì hết tiền, nó dừng không trừ nữa và trả về lỗi -202, kèm array có 2 item chứa thông tin 2 TOPUPID đã trừ được (lúc này phía đại lý hoàn tiền lại cho số thứ 3 không đủ tiền đó).
3. Tuy nhiên 2 số trừ tiền thành công vẫn được đưa sang để thực thi topup, bỏ qua vấn đề lỗi -202 (đơn giản vì tiền trừ được rồi), và process ngầm sẽ chạy song song hoặc tuần tự với 2 số đó, với kết quả cuối cùng là 1 số thành công, 1 số thất bại (vì lý do thuê bao đó bị khoá - điều mà nhà mạng mới thấy).
4. Process xử lý tổng hợp lại, nó sẽ có 2 bản ghi đưa vào topup, 1 cái có EXEC_STAT = FINISH, 1 cái là EXEC_STAT = ERROR (vì thất bại). Do có 1 thất bại nên nó hoàn lại tiền giao dịch đó về cho đại lý.
5. Kết quả cuối cùng, đại lý còn 80K trong TK, kết quả cuối có 1 topup thành công FINISH, 1 thất bại ERROR và 1 bị từ chối (không có bản ghi là REJECT, nhưng có thể coi là như thế).

> Bạn có thể thắc mắc lý do tại sao ở bước 2 lại tính tổng rồi trừ ngay đi để có -202, một ăn cả, hai về không. Lý do ở chỗ mCard có tính năng tính điểm và khuyến mại với luật được áp dụng cho 1 số lượng nhất định các lệnh mua, nên phải trừ và tính toán tuần tự từng lần một mới tính được, như trường hợp trên, bạn có khuyến mại giảm giá cho 2 thẻ mua gần nhât, với giá khuyến mại chỉ còn 50%, hết số khuyến mại trở về bình thường. Thì chương trình chạy đơn giản sẽ giống như là 25 (KM) + 25 (KM) + 50 (hết số KM). Có thể việc này sẽ thay đổi về sau nhưng các bước bên trên luôn đúng cho mọi trường hợp.

Ví dụ bên trên, kiến chu trình kiểm tra lỗi trả về trở nên phức tạp. Để đơn giản, đại lý chỉ cần nạp 1 lần 1 DEST, đảm bảo lúc này tình huống là chỉ có kết quả hoặc là được chấp nhận và topup (đủ tiền, đủ sản lượng ..) chờ đến khi nó hoàn thành (FINISH, ERROR) là xong. Nếu có lỗi tức là có lỗi Error code trả ngay lập tức, không có TOPUPID nào được tạo ra khi có lỗi cả (đương nhiên, sẽ không còn EXEC_STAT cho trường hợp lỗi nữa).

## Đọc mô tả lỗi 

Hiện mCard có đến hàng trăm mã lỗi từ số âm -1 đến -999 (cứ gặp số nguyên âm ở các chỗ thì hầu như là mã lỗi), nên chỉ đưa ra tham khảo 1 số lỗi thường gặp ở bài #27, với quy tắc tóm tắt sau:

1. Số 0: không có lỗi, số dương báo sự thành công ở mức độ nào đó.
2. Các lỗi -100 ... -1: là lỗi dành cho báo lỗi API các kiểu, hiện có khoảng 25 mã trong dải này, xin tham khảo bài #27.
3. Các lỗi -200 ... -101: là lỗi về sự không tồn tại hay sai trạng thái một object nào đó (ví dụ -101 lỗi không tồn tại mã khách hàng).
4. Các lỗi -300 ... -201: là lỗi về sự thiếu tiền hoặc hết hạn, hoặc quá số lượng, nói chung là sai chính sách cài đặt trong hệ thống.
5. Các lỗi -500 ... -301: là lỗi nội bộ vận hành của mCard.
6. Các lỗi -900 ... -501: chưa xài đến.
7. Các lỗi -999 ... -901: lỗi hệ thống phần mềm hoặc hoạt động, ví dụ lỗi kết nối CSDL nội bộ mCard.

> Thứ tự bên trên có vẻ ngược, nhưng nó là từ số nhỏ đến số lớn hơn, vì nó là số nguyên âm.

Tất cả các bản ghi báo **lỗi đều đính kèm trường "message"**, bên cạnh trường "code" mã lỗi. Message này là thông báo lỗi bằng tiếng Việt hoặc tiếng Anh về lỗi (tuỳ do dev nào viết thích dùng tiếng nào). Hãy đọc nó để hiểu lỗi vì cái gì, chúng tôi sẵn sàng hỗ trợ giải thích (chứ không liệt kê được, dài quá).

## HTTP Status

Bên cạnh code ở trên, với API thì 1 kiểu mã nữa được sử dụng là HTTP Status. Lý do sử dụng là để đảm bảo tương thích với HTTP RESTful (tức phải tuân thủ theo chuẩn). Ví dụ tất cả các lỗi không tồn tại khi gọi API đều có HTTP Status là 404, mã code vẫn trả kèm theo về để lập trình viên biết mình đã sai thiếu mã gì. Do đó mã khác 2xx hoặc 3xx luôn là lỗi. Riêng với các mã Redirect (3xx), thì trừ 304 Not modified có ý nghĩa cho caching, các mã khác không được sử dụng với API này. Nếu hệ thống trả về status 503, và content đi kèm không phải là JSON, thì đó là mã của gateway (load balancer) trả về khi nó không thể kết nối tới các back-end server, không có ứng dụng chạy thì không tạo ra nội dung JSON được.

## Exception

Các ngôn ngữ như Java và .Net thì khi gặp HTTP Status khác 2xx, 3xx, thường sẽ có Exception được tạo ra. Ngoài ra các trường hợp khác: kết nối mạng không được, chứng chỉ SSL bất hợp lệ, sai tên miền, tên miền không phân giải được, hoặc sai xác thực, dịch vụ mCard đáp ứng chậm timeout... hâu như đều có Exception. Các đại lý có thể coi khi gặp Exception là lỗi và hoàn tiền (trừ timeout thì nên có động tác kiểm tra lại sau đó để chắc chắn hơn - timeout là trường hợp đặc biệt dạng "không rõ kết quả").

# Mã nguồn client mẫu

Để thuận tiện, thì các mẫu client sau đây được cung cấp (với mã nguồn thư viện client thực ra là được generate từ swagger, chỉ có test class (hoặc web app) là được viết làm ví dụ về quy trình xử lý. Có 2 loại triển khai:

* Triển khai gọi kiểu polling, theo 3 bước là: gửi lệnh yêu cầu, lặp lại n lần poll kiểm tra trạng thái, khi hết n hoặc có trạng thái kết thúc thì thoát -> cập nhật DB.
* Triển khai kiểu call back (đăng ký hàm trước): gửi lệnh yêu cầu, nhận lại 2 mã TXNID (1 TXNID) và TOPUPID (nhiều tương ứng số lượng DEST) ghi vào DB, chờ mCard thực thi xong gọi lại URL callback đã đăng ký -> cập nhật DB bản ghi có TXNID và TOPUPID tương ứng.

Theo cách thức triển khai mới, đặc tả kỹ thuật chính xác của API để test (và chạy thật) sử dụng Swagger để mô tả, liên kết sau là để test, đổi hostname thành mcard.vn thì là thử. Xin click để thử [mcard-s2service.yaml](https://mcard.vietfi.net/mcardstatic/swagger-ui.html?f=https://mcard.vietfi.net/mcapi/swagger/mcard-s2service.yaml) (ở trong có file swagger đuôi .yaml, có thể tải về và dùng công cụ generate client code từ web site [Swagger Codegen](https://swagger.io/tools/swagger-codegen/). Mã nguồn được generate hoặc code như sau:

1. [S2Service Java Client Demo](/ep/epfs/s2service-java-client-demo.7z)
2. [S2Service PHP Client Demo](/ep/epfs/s2service-php-client-demo.7z)
3. [S2Service .Net Client Demo](/ep/epfs/s2service-net-client-demo.7z)

Lưu đồ triển khai một kiểu client polling như sau:

![Lưu đồ S2Service Client](s2service-blocking-client-flow.png)

Kính chúc đại lý tích hợp tốt.

@MCardTeam