diff --git a/.gitignore b/.gitignore index 5884ddd..b04699f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,20 @@ /node_modules package-lock.json +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# TypeScript v1 declaration files +types/ + +# TypeScript cache +*.tsbuildinfo + +# production +/build +/lib \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cf98500..451d4fd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,7 @@ To adding a new api, for example TokenizationAPI - Update Open API specification in `specs` folder to latest. - Generate request, response models by run the script in `scripts/generate.sh` +- In the generated request files, find and replace `app_id`, `payment_id`, `mac`, `sig` required fields to optional by using the `?` syntax. Because those fields are added in the service file. For example, from `app_id` to `app_id:?` . - Add new service and related resources in `services` folder. - Update `services/index.ts` to expose new service API to client. - Add unit tests for new service in `__tests__` folder and verify by run `npm run test`. diff --git a/package.json b/package.json index dedbd20..4a7b780 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "@zalopay-oss/zalopay-nodejs", - "version": "0.1.2", + "version": "0.2.0", "description": "", - "main": "./lib/ZaloPay.js", - "types": "./types/ZaloPay.d.ts", + "main": "./lib/src/zalopay/index.js", + "types": "./types/src/zalopay/index.d.ts", "files": [ "lib", "types", @@ -24,6 +24,7 @@ "@types/jest": "^29.5.0", "@types/node": "^18.15.5", "@types/qs": "^6.9.7", + "@types/node-rsa": "^1.1.1", "@typescript-eslint/eslint-plugin": "^5.59.2", "@typescript-eslint/parser": "^5.59.2", "eslint": "^8.39.0", @@ -35,6 +36,7 @@ "dependencies": { "axios": "^1.3.4", "crypto-js": "^4.1.1", + "node-rsa": "^1.1.1", "qs": "^6.11.1" }, "repository": { diff --git a/specs/zlp.yaml b/specs/zlp.yaml index 96fe699..c4824a5 100644 --- a/specs/zlp.yaml +++ b/specs/zlp.yaml @@ -294,6 +294,110 @@ paths: 405: description: Invalid input content: {} + /v2/disbursement/user: + post: + tags: + - disbursement + summary: Query user account info + operationId: DisbursementQueryUser + requestBody: + description: Query user ZaloPay account info + content: + application/json: + schema: + $ref: '#/components/schemas/DisbursementQueryUserRequest' + application/xml: + schema: + $ref: '#/components/schemas/DisbursementQueryUserRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DisbursementQueryUserRequest' + required: true + responses: + 200: + content: + application/json: + schema: + $ref: '#/components/schemas/DisbursementQueryUserResponse' + description: OK + /v2/disbursement/topup: + post: + tags: + - disbursement + summary: Topup for a user + operationId: DisbursementTopup + requestBody: + description: Topup for a user + content: + application/json: + schema: + $ref: '#/components/schemas/DisbursementTopupRequest' + application/xml: + schema: + $ref: '#/components/schemas/DisbursementTopupRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DisbursementTopupRequest' + required: true + responses: + 200: + content: + application/json: + schema: + $ref: '#/components/schemas/DisbursementTopupResponse' + description: OK + /v2/disbursement/txn: + post: + tags: + - disbursement + summary: Query order status + operationId: DisbursementQueryOrder + requestBody: + description: Query order status + content: + application/json: + schema: + $ref: '#/components/schemas/DisbursementQueryOrderRequest' + application/xml: + schema: + $ref: '#/components/schemas/DisbursementQueryOrderRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DisbursementQueryOrderRequest' + required: true + responses: + 200: + content: + application/json: + schema: + $ref: '#/components/schemas/DisbursementQueryOrderResponse' + description: OK + /v2/disbursement/balance: + post: + tags: + - disbursement + summary: Query merchant's account balance + operationId: DisbursementQueryMerchantBalance + requestBody: + description: Query merchant's account balance + content: + application/json: + schema: + $ref: '#/components/schemas/DisbursementQueryMerchantBalanceRequest' + application/xml: + schema: + $ref: '#/components/schemas/DisbursementQueryMerchantBalanceRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DisbursementQueryMerchantBalanceRequest' + required: true + responses: + 200: + content: + application/json: + schema: + $ref: '#/components/schemas/DisbursementQueryMerchantBalanceResponse' + description: OK components: schemas: OAQueryOrderRequest: @@ -1038,4 +1142,380 @@ components: description: "TXID of order transaction" zp_trans_id: type: integer - description: "The ZaloPay's transaction code" \ No newline at end of file + description: "The ZaloPay's transaction code" + DisbursementQueryUserRequest: + type: object + properties: + request_id: + type: string + description: "Client request identity, using for tracing request" + app_id: + type: integer + format: int64 + description: "The unique ID of the partner will be provided after the partner registered successfully with ZaloPay" + phone: + type: string + description: "The user's phone" + time: + type: integer + format: int64 + description: "Requests timestamp in ms" + mac: + type: string + description: >- + "It is signature of order. It’s calculated by following input: hmacInput = (app_id + “|” + phone + “|” + time) and use sha256 with app’s hmac key" + required: + - app_id + - phone + - time + - mac + xml: + name: DisbursementQueryUserRequest + DisbursementQueryUserResponse: + type: object + properties: + return_code: + type: integer + description: |- + 1 - SUCCESS + + 2 - FAIL + return_message: + type: string + description: "Return code description" + sub_return_code: + type: integer + description: |- + -101 - User wallet account not exists + + -401 - Request param illegal + + -402 - Unauthorized + + -500 - ZaloPay system error + + -503 - The system is maintenance + + -1011 - User wallet account has been locked + sub_return_message: + type: string + description: "Sub return code description" + data: + type: object + properties: + reference_id: + type: string + description: "ZaloPay reference id" + m_u_id: + type: string + description: "User’s identity in ZaloPay system" + name: + type: string + description: "User’s full name" + phone: + type: string + description: "User's phone" + onboarding_url: + type: string + description: >- + "ZaloPay middle_page url to onboard new users" + "Displayed when receiving return_code =2 && sub_return_code = -101" + DisbursementTopupRequest: + type: object + properties: + app_id: + type: integer + format: int64 + description: "The unique ID of the partner will be provided after the partner registered successfully with ZaloPay" + payment_id: + type: string + description: "The unique ID of the partner will be provided after the partner registered successfully with ZaloPay" + partner_order_id: + type: string + description: "The unique id, generate in partner system. Using for reconcile" + m_u_id: + type: string + description: "The user's identity in the response of QueryUser API" + amount: + type: integer + format: int64 + description: "The amount top-up to receiver’s wallet" + description: + type: string + description: "Extend information" + partner_embed_data: + type: string + description: |- + Partner's specify info, a json string. + + Example: “{\“store_id\”:\“s2\”,\“store_name\”:\ “name\”} + reference_id: + type: string + description: "Zalopay reference id response in query user response" + extra_info: + type: string + description: "Using for extend purpose, a json string" + time: + type: integer + format: int64 + description: "Requests timestamp in ms" + sig: + type: string + description: >- + It’s calculated by following input: hmacinput = (app_id + “|” + payment_id + “|” + partner_order_id + “|” + m_u_id + “|” + amount + “|” + description + “|” + partner_embed_data + “|” + extra_info + “|” + time) + and use sha256 with app’s hmac key + then RSA with app’s private key ZaloPay provide + required: + - app_id + - payment_id + - partner_order_id + - m_u_id + - amount + - description + - partner_embed_data + - extra_info + - time + - sig + xml: + name: DisbursementTopupRequest + DisbursementTopupResponse: + type: object + properties: + return_code: + type: integer + description: |- + 1 - SUCCESS + + 2 - FAIL + return_message: + type: string + description: "Return code description" + sub_return_code: + type: integer + description: |- + -68 - Duplicate resource + + -101 - User wallet account not exists + + -401 - Request param illegal + + -402 - Unauthorized + + -406 - User wallet reaches a fund-in limitation + + -500 - ZaloPay system error + + -503 - The system is maintenance + sub_return_message: + type: string + description: "Sub return code description" + data: + type: object + properties: + order_id: + type: string + description: "Merchant transaction code" + status: + type: integer + description: |- + 1 – SUCCESS + + 2 – FAIL + + 3 – PROCESSING: Has to repeat query order status in a period of time until final status (configured interval and number of query) + + 4 – PENDING: Pending transactions, needs to be manually fixed by internal teams + m_u_id: + type: string + description: "User's identity" + phone: + type: string + description: "User’s phone" + amount: + type: integer + format: int64 + description: "Transaction amount" + description: + type: string + description: "Transaction description" + partner_fee: + type: integer + format: int64 + description: "Partner fee" + zlp_fee: + type: integer + format: int64 + description: "ZaloPay fee" + extra_info: + type: string + description: "Order extra info, json string" + time: + type: integer + format: int64 + description: "Transaction timestamp in millisecond" + upgrade_url: + type: string + description: |- + ZaloPay middle_page url to upgrade for users with fund-in limit + + Displayed when receiving return_code =2 && sub_return_code = -406 + DisbursementQueryOrderRequest: + type: object + properties: + app_id: + type: integer + format: int64 + description: "The unique ID of the partner will be provided after the partner registered successfully with ZaloPay" + partner_order_id: + type: string + description: "The unique id in partner system" + time: + type: integer + format: int64 + description: "Requests timestamp in ms" + mac: + type: string + description: "It is a signature of the order. It’s calculated by following input: hmacInput = (app_id+ “|” + partner_order_id+ “|” + time) and use sha256 with app’s hmac key" + required: + - app_id + - partner_order_id + - time + - mac + xml: + name: DisbursementQueryOrderRequest + DisbursementQueryOrderResponse: + type: object + properties: + return_code: + type: integer + description: |- + 1 - SUCCESS + + 2 - FAIL + return_message: + type: string + description: "Return code description" + sub_return_code: + type: integer + description: |- + -101 - Order not found + + -401 - Request param illegal + + -402 - Unauthorized + + -500 - ZaloPay system error + + -503 - The system is maintenance + sub_return_message: + type: string + description: "Sub return code description" + data: + type: object + properties: + order_id: + type: string + description: "Merchant transaction code" + status: + type: integer + description: |- + 1 – SUCCESS + + 2 – FAIL + + 3 – PROCESSING: Has to repeat query order status in a period of time until final status (configured interval and number of query) + + 4 – PENDING: Pending transactions, needs to be manually fixed by internal teams + m_u_id: + type: string + description: "User's identity" + phone: + type: string + description: "User’s phone" + amount: + type: integer + format: int64 + description: "Transaction amount" + description: + type: string + description: "Transaction description" + partner_fee: + type: integer + format: int64 + description: "Partner fee" + zlp_fee: + type: integer + format: int64 + description: "ZaloPay fee" + extra_info: + type: string + description: "Order extra info, json string" + time: + type: integer + format: int64 + description: "Transaction timestamp in millisecond" + zp_trans_id: + type: string + description: "ZaloPay's transaction code" + result_url: + type: string + description: "ZaloPay middle_page url for notify merchant about success disbursement result" + DisbursementQueryMerchantBalanceRequest: + type: object + properties: + request_id: + type: string + description: "Client request identity, using for tracing request" + app_id: + type: integer + format: int64 + description: "The unique ID of the partner will be provided after the partner registered successfully with ZaloPay" + payment_id: + type: string + description: "The unique ID of the partner will be provided after the partner registered successfully with ZaloPay" + time: + type: integer + format: int64 + description: "Requests timestamp in ms" + mac: + type: string + description: "It is a signature of the order. It’s calculated by following input: hmacInput = (app_id+ “|” + payment_id+ “|” + time) and use sha256 with app’s hmac key" + required: + - app_id + - payment_id + - time + - mac + xml: + name: DisbursementQueryMerchantBalanceRequest + DisbursementQueryMerchantBalanceResponse: + type: object + properties: + return_code: + type: integer + description: |- + 1 - SUCCESS + + 2 - FAIL + return_message: + type: string + description: "Return code description" + sub_return_code: + type: integer + description: |- + -401 - Request param illegal + + -402 - Unauthorized + + -500 - ZaloPay system error + + -503 - The system is maintenance + sub_return_message: + type: string + description: "Sub return code description" + data: + type: object + properties: + balance: + type: integer + format: int64 + description: "Partner’s account balance" \ No newline at end of file diff --git a/src/zalopay/__tests__/__mocks__/base.ts b/src/zalopay/__tests__/__mocks__/base.ts index 3502f41..ab6f600 100644 --- a/src/zalopay/__tests__/__mocks__/base.ts +++ b/src/zalopay/__tests__/__mocks__/base.ts @@ -3,8 +3,16 @@ import {ZaloPayClient} from "../../zaloPayClient"; export const createClient = (): ZaloPayClient => { return new ZaloPayClient({ appId: "2554", + paymentId: "2554", key1: "sdngKKJmqEMzvh5QQcdD2A9XBSKUNaYn", key2: "trMrHtvjo6myautxDUiAcYsVtaeQ8nhf", + privateKey: "MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAuhr9fssauZOaef4HCEJ4OAJQ6g8y\ + O8de5KwB1LM/fIlRZGsnD0VO+YBGrdttnKsieErQPujmyV7Tnw19yLVGGwIDAQABAkEAgpPzbyZU\ + rwbIqXW6O2pf7XR6j29wII9nnmytsC7AicCd2uGAd+yHKEOQGEHBN+rm/IZ8F5WWT2OpnOTY3DZT\ + gQIhAPyzS24ahh0ogYuDy4VXsiLscEAngrAvZA5qpWWPFV9BAiEAvIkWcJzM9kaJ2YjoNcGvO3yd\ + DsepNeC79dfIA7tL6lsCIELaRChayARKxQrd0SfzrWLj3kZ6rW5i+zt9J0iY8/SBAiEAsw53c2hX\ + +KWxkhpGf5d9dz+4YisZ94OCv8+5tGGTjfUCIEs9pi1DsVZBi0HNibXIpVBO4KERvHBJ92bAbPep\ + lIb2", callbackUrl: "", env: "sandbox", }); diff --git a/src/zalopay/__tests__/__mocks__/disbursement/responses.ts b/src/zalopay/__tests__/__mocks__/disbursement/responses.ts new file mode 100644 index 0000000..3409a9b --- /dev/null +++ b/src/zalopay/__tests__/__mocks__/disbursement/responses.ts @@ -0,0 +1,64 @@ +export const queryMerchantBalanceSuccess = { + "return_code": 1, + "return_message": "Transaction successful.", + "sub_return_code": 1, + "sub_return_message": "Transaction successful.", + "data": { + "balance": 1000000000, + } +}; + +export const queryUserSuccess = { + "return_code": 1, + "return_message": "Transaction successful.", + "sub_return_code": 1, + "sub_return_message": "Transaction successful.", + "data": { + "reference_id": "23040713221300383", + "m_u_id": "230407qQe7vGnqp0agyforLAy0D2b1x3", + "name": "Nguyen Van A", + "phone": "0904123456", + "onboarding_url": "" + } +}; + +export const topupSuccess = { + "return_code": 1, + "return_message": "Transaction successful.", + "sub_return_code": 1, + "sub_return_message": "Transaction successful.", + "data": { + "order_id": "202305110902928474", + "status": 3, + "m_u_id": "230407qQe7vGnqp0agyforLAy0D2b1x3", + "phone": "0904123456", + "amount": 1000, + "description": "Topup for Nguyen Van A", + "partner_fee": 0, + "zlp_fee": 0, + "extra_info": "{}", + "time": 1680849004, + "upgrade_url": "" + } +}; + +export const queryOrderSuccess = { + "return_code": 1, + "return_message": "Transaction successful.", + "sub_return_code": 1, + "sub_return_message": "Transaction successful.", + "data": { + "order_id": "202305110902928474", + "status": 1, + "m_u_id": "230407qQe7vGnqp0agyforLAy0D2b1x3", + "phone": "0904123456", + "amount": 1000, + "description": "Topup for Nguyen Van A", + "partner_fee": 0, + "zlp_fee": 0, + "extra_info": "{}", + "time": 1680849004, + "zp_trans_id": "", + "result_url": "" + } +}; diff --git a/src/zalopay/__tests__/disbursement.test.ts b/src/zalopay/__tests__/disbursement.test.ts new file mode 100644 index 0000000..c5a1ec1 --- /dev/null +++ b/src/zalopay/__tests__/disbursement.test.ts @@ -0,0 +1,123 @@ +import { ZaloPayClient } from "../zaloPayClient"; +import { DisbursementAPI } from "../services"; +import nock from "nock"; +import { createClient } from "./__mocks__/base"; +import { + queryMerchantBalanceSuccess, + queryUserSuccess, + topupSuccess, + queryOrderSuccess, +} from "./__mocks__/disbursement/responses"; + +import { DisbursementQueryMerchantBalanceRequest } from "../models/disbursementQueryMerchantBalanceRequest"; +import { DisbursementQueryUserRequest } from "../models/disbursementQueryUserRequest"; +import { DisbursementTopupRequest } from "../models/disbursementTopupRequest"; +import { DisbursementQueryOrderRequest } from "../models/disbursementQueryOrderRequest"; +import { Config } from "../model/Config"; + +let client: ZaloPayClient; +let disbursementAPI: DisbursementAPI; +let scope: nock.Scope; + +beforeEach((): void => { + if (!nock.isActive()) { + nock.activate(); + } + client = createClient(); + disbursementAPI = new DisbursementAPI(client); + scope = nock("https://sb-openapi.zalopay.vn"); +}); + +afterEach(() => { + nock.cleanAll(); +}); + +describe("Disbursement API", (): void => { + test('should throw an error if paymentId or privateKey are not in the config', () => { + const errorMessage = 'The paymentId and privateKey config keys are required for Disbursement service'; + const clientWithoutPaymentIdAndPrivateKey = new ZaloPayClient({} as Config); + expect(() => new DisbursementAPI(clientWithoutPaymentIdAndPrivateKey)).toThrow(errorMessage); + const clientWithoutPrivateKey = new ZaloPayClient({ paymentId: "1234" } as Config); + expect(() => new DisbursementAPI(clientWithoutPrivateKey)).toThrow(errorMessage); + }); + + test("should query merchant balance", async (): Promise => { + scope.post("/v2/disbursement/balance") + .reply(200, queryMerchantBalanceSuccess); + // exclude app_id, mac + const request: DisbursementQueryMerchantBalanceRequest = { + time: Date.now(), + } + const response = await disbursementAPI.queryMerchantBalance(request); + expect(response.return_code).toEqual(1); + expect(response.sub_return_code).toEqual(1); + expect(response.data?.balance).toEqual(1000000000); + }); + + test("should query user succcessful", async (): Promise => { + scope.post("/v2/disbursement/user") + .reply(200, queryUserSuccess); + // exclude app_id, mac + const request: DisbursementQueryUserRequest = { + phone: "0904123456", + time: Date.now(), + }; + const response = await disbursementAPI.queryUser(request); + expect(response.return_code).toEqual(1); + expect(response.sub_return_code).toEqual(1); + expect(response.data?.name).toEqual("Nguyen Van A"); + expect(response.data?.phone).toEqual("0904123456"); + expect(response.data?.m_u_id).toEqual("230407qQe7vGnqp0agyforLAy0D2b1x3"); + }); + + test("should topup successful", async (): Promise => { + scope.post("/v2/disbursement/topup") + .reply(200, topupSuccess); + // exclude app_id, mac + const request: DisbursementTopupRequest = { + partner_order_id: "84903863801", + m_u_id: "230407qQe7vGnqp0agyforLAy0D2b1x3", + amount: 50000, + description: "Topup for Nguyen Van A", + partner_embed_data: "{}", + reference_id: "Topup", + extra_info: "{}", + time: Date.now(), + }; + const response = await disbursementAPI.topup(request); + expect(response.return_code).toEqual(1); + expect(response.sub_return_code).toEqual(1); + expect(response.data?.order_id).toEqual("202305110902928474"); + expect(response.data?.status).toEqual(3); + expect(response.data?.m_u_id).toEqual("230407qQe7vGnqp0agyforLAy0D2b1x3"); + expect(response.data?.phone).toEqual("0904123456"); + expect(response.data?.amount).toEqual(1000); + expect(response.data?.description).toEqual("Topup for Nguyen Van A"); + expect(response.data?.partner_fee).toEqual(0); + expect(response.data?.zlp_fee).toEqual(0); + expect(response.data?.extra_info).toEqual("{}"); + }); + + test("should query order successful", async (): Promise => { + scope.post("/v2/disbursement/txn") + .reply(200, queryOrderSuccess); + // exclude app_id, mac + const request: DisbursementQueryOrderRequest = { + partner_order_id: "84903863801", + time: Date.now(), + }; + const response = await disbursementAPI.queryOrder(request); + expect(response.return_code).toEqual(1); + expect(response.sub_return_code).toEqual(1); + expect(response.data?.order_id).toEqual("202305110902928474"); + expect(response.data?.status).toEqual(1); + expect(response.data?.m_u_id).toEqual("230407qQe7vGnqp0agyforLAy0D2b1x3"); + expect(response.data?.phone).toEqual("0904123456"); + expect(response.data?.amount).toEqual(1000); + expect(response.data?.description).toEqual("Topup for Nguyen Van A"); + expect(response.data?.partner_fee).toEqual(0); + expect(response.data?.zlp_fee).toEqual(0); + expect(response.data?.extra_info).toEqual("{}"); + }); + +}); diff --git a/src/zalopay/__tests__/utils/hmacUtils.test.ts b/src/zalopay/__tests__/utils/hmacUtils.test.ts new file mode 100644 index 0000000..3b78be2 --- /dev/null +++ b/src/zalopay/__tests__/utils/hmacUtils.test.ts @@ -0,0 +1,15 @@ +import HmacUtils from '../../utils/hmacUtils'; +import * as CryptoJS from 'crypto-js'; + +describe('HmacUtils', () => { + it('should calculate HMAC correctly', () => { + const data = 'example data'; + const key = 'example key'; + const expectedHmac = CryptoJS.HmacSHA256(data, key).toString(); + + const hmacUtils = new HmacUtils(); + const calculatedHmac = hmacUtils.calculateHmac(data, key); + + expect(calculatedHmac).toEqual(expectedHmac); + }); +}); \ No newline at end of file diff --git a/src/zalopay/__tests__/utils/rsaUtils.test.ts b/src/zalopay/__tests__/utils/rsaUtils.test.ts new file mode 100644 index 0000000..0b3ec38 --- /dev/null +++ b/src/zalopay/__tests__/utils/rsaUtils.test.ts @@ -0,0 +1,38 @@ +import RSAUtils from '../../utils/rsaUtils'; +import { Config } from '../../model/Config'; + +describe('RSAUtils', () => { + const key = "MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAuhr9fssauZOaef4HCEJ4OAJQ6g8y\ + O8de5KwB1LM/fIlRZGsnD0VO+YBGrdttnKsieErQPujmyV7Tnw19yLVGGwIDAQABAkEAgpPzbyZU\ + rwbIqXW6O2pf7XR6j29wII9nnmytsC7AicCd2uGAd+yHKEOQGEHBN+rm/IZ8F5WWT2OpnOTY3DZT\ + gQIhAPyzS24ahh0ogYuDy4VXsiLscEAngrAvZA5qpWWPFV9BAiEAvIkWcJzM9kaJ2YjoNcGvO3yd\ + DsepNeC79dfIA7tL6lsCIELaRChayARKxQrd0SfzrWLj3kZ6rW5i+zt9J0iY8/SBAiEAsw53c2hX\ + +KWxkhpGf5d9dz+4YisZ94OCv8+5tGGTjfUCIEs9pi1DsVZBi0HNibXIpVBO4KERvHBJ92bAbPep\ + lIb2"; + const config: Config = { + appId: '', + key1: '', + key2: '', + privateKey: key, + env: 'sandbox' + } + + const rsa = RSAUtils.fromConfig(config, { scheme: "pkcs8" }); + + it('should sign data with the specified encodings', () => { + const data = "cd7a32337eedd9763bda3a0654ac9855f36f8d759b7a505e4bfd646a5f5b8056"; + const signature = rsa.sign(data, 'base64', 'utf8'); + expect(typeof signature).toEqual('string'); + expect(signature.length).toBeGreaterThan(0); + expect(signature).toEqual("FUsBSTRZAIuz5A8F+KZixT8q3s7iLgLQJLqFzPfazdJnoHv9FJxCRlLaGk10b3SUnsEtLOAkhpQk9QXsepFotw=="); + const verify = rsa.verify(data, signature, 'utf8', 'base64'); + expect(verify).toEqual(true); + }); + + it('should sign data correctly', () => { + const data = 'test data'; + const signature = rsa.sign(data); + expect(Buffer.isBuffer(signature)).toEqual(true); + expect(signature.length).toBeGreaterThan(0); + }); +}); \ No newline at end of file diff --git a/src/zalopay/index.ts b/src/zalopay/index.ts index 19a06d3..0378e60 100644 --- a/src/zalopay/index.ts +++ b/src/zalopay/index.ts @@ -5,12 +5,14 @@ import { CreateQuickPayOrderRequest, CreateQuickPayOrderResponse, } from "./model/Order"; +export * from "./models/models"; import { Config } from "./model/Config"; +export { TokenizationAPI, DisbursementAPI } from "./services"; export { ZaloPayClient }; export type { CreateOrderRequest, CreateOrderResponse, Config, CreateQuickPayOrderRequest, - CreateQuickPayOrderResponse + CreateQuickPayOrderResponse, }; diff --git a/src/zalopay/model/Config.ts b/src/zalopay/model/Config.ts index 5aa6165..1434227 100644 --- a/src/zalopay/model/Config.ts +++ b/src/zalopay/model/Config.ts @@ -1,7 +1,9 @@ export interface Config { appId: string; + paymentId?: string; key1: string; key2: string; + privateKey?: string; callbackUrl?: string; env: "sandbox" | "production"; } diff --git a/src/zalopay/models/disbursementQueryMerchantBalanceRequest.ts b/src/zalopay/models/disbursementQueryMerchantBalanceRequest.ts new file mode 100644 index 0000000..a0bc311 --- /dev/null +++ b/src/zalopay/models/disbursementQueryMerchantBalanceRequest.ts @@ -0,0 +1,70 @@ +/* +* The version of the ZaloPay OpenAPI document: v1.0.0 +* Contact: developer@zalopay.vn +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit this class manually. +*/ + +export class DisbursementQueryMerchantBalanceRequest { + /** + * Client request identity, using for tracing request + */ + 'request_id'?: string; + + /** + * The unique ID of the partner will be provided after the partner registered successfully with ZaloPay + */ + 'app_id'?: number; + + /** + * The unique ID of the partner will be provided after the partner registered successfully with ZaloPay + */ + 'payment_id'?: string; + + /** + * Requests timestamp in ms + */ + 'time': number; + + /** + * It is a signature of the order. It’s calculated by following input: hmacInput = (app_id+ “|” + payment_id+ “|” + time) and use sha256 with app’s hmac key + */ + 'mac'?: string; + + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "request_id", + "baseName": "request_id", + "type": "string" + }, + { + "name": "app_id", + "baseName": "app_id", + "type": "number" + }, + { + "name": "payment_id", + "baseName": "payment_id", + "type": "string" + }, + { + "name": "time", + "baseName": "time", + "type": "number" + }, + { + "name": "mac", + "baseName": "mac", + "type": "string" + } ]; + + static getAttributeTypeMap() { + return DisbursementQueryMerchantBalanceRequest.attributeTypeMap; + } +} + diff --git a/src/zalopay/models/disbursementQueryMerchantBalanceResponse.ts b/src/zalopay/models/disbursementQueryMerchantBalanceResponse.ts new file mode 100644 index 0000000..5a09c28 --- /dev/null +++ b/src/zalopay/models/disbursementQueryMerchantBalanceResponse.ts @@ -0,0 +1,68 @@ +/* +* The version of the ZaloPay OpenAPI document: v1.0.0 +* Contact: developer@zalopay.vn +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit this class manually. +*/ +import { DisbursementQueryMerchantBalanceResponseData } from './disbursementQueryMerchantBalanceResponseData'; + +export class DisbursementQueryMerchantBalanceResponse { + /** + * 1 - SUCCESS 2 - FAIL + */ + 'return_code'?: number; + + /** + * Return code description + */ + 'return_message'?: string; + + /** + * -401 - Request param illegal -402 - Unauthorized -500 - ZaloPay system error -503 - The system is maintenance + */ + 'sub_return_code'?: number; + + /** + * Sub return code description + */ + 'sub_return_message'?: string; + + 'data'?: DisbursementQueryMerchantBalanceResponseData; + + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "return_code", + "baseName": "return_code", + "type": "number" + }, + { + "name": "return_message", + "baseName": "return_message", + "type": "string" + }, + { + "name": "sub_return_code", + "baseName": "sub_return_code", + "type": "number" + }, + { + "name": "sub_return_message", + "baseName": "sub_return_message", + "type": "string" + }, + { + "name": "data", + "baseName": "data", + "type": "DisbursementQueryMerchantBalanceResponseData" + } ]; + + static getAttributeTypeMap() { + return DisbursementQueryMerchantBalanceResponse.attributeTypeMap; + } +} + diff --git a/src/zalopay/models/disbursementQueryMerchantBalanceResponseData.ts b/src/zalopay/models/disbursementQueryMerchantBalanceResponseData.ts new file mode 100644 index 0000000..1710c56 --- /dev/null +++ b/src/zalopay/models/disbursementQueryMerchantBalanceResponseData.ts @@ -0,0 +1,30 @@ +/* +* The version of the ZaloPay OpenAPI document: v1.0.0 +* Contact: developer@zalopay.vn +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit this class manually. +*/ + +export class DisbursementQueryMerchantBalanceResponseData { + /** + * Partner’s account balance + */ + 'balance'?: number; + + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "balance", + "baseName": "balance", + "type": "number" + } ]; + + static getAttributeTypeMap() { + return DisbursementQueryMerchantBalanceResponseData.attributeTypeMap; + } +} + diff --git a/src/zalopay/models/disbursementQueryOrderRequest.ts b/src/zalopay/models/disbursementQueryOrderRequest.ts new file mode 100644 index 0000000..e137e62 --- /dev/null +++ b/src/zalopay/models/disbursementQueryOrderRequest.ts @@ -0,0 +1,60 @@ +/* +* The version of the ZaloPay OpenAPI document: v1.0.0 +* Contact: developer@zalopay.vn +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit this class manually. +*/ + +export class DisbursementQueryOrderRequest { + /** + * The unique ID of the partner will be provided after the partner registered successfully with ZaloPay + */ + 'app_id'?: number; + + /** + * The unique id in partner system + */ + 'partner_order_id': string; + + /** + * Requests timestamp in ms + */ + 'time': number; + + /** + * It is a signature of the order. It’s calculated by following input: hmacInput = (app_id+ “|” + partner_order_id+ “|” + time) and use sha256 with app’s hmac key + */ + 'mac'?: string; + + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "app_id", + "baseName": "app_id", + "type": "number" + }, + { + "name": "partner_order_id", + "baseName": "partner_order_id", + "type": "string" + }, + { + "name": "time", + "baseName": "time", + "type": "number" + }, + { + "name": "mac", + "baseName": "mac", + "type": "string" + } ]; + + static getAttributeTypeMap() { + return DisbursementQueryOrderRequest.attributeTypeMap; + } +} + diff --git a/src/zalopay/models/disbursementQueryOrderResponse.ts b/src/zalopay/models/disbursementQueryOrderResponse.ts new file mode 100644 index 0000000..6976329 --- /dev/null +++ b/src/zalopay/models/disbursementQueryOrderResponse.ts @@ -0,0 +1,68 @@ +/* +* The version of the ZaloPay OpenAPI document: v1.0.0 +* Contact: developer@zalopay.vn +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit this class manually. +*/ +import { DisbursementQueryOrderResponseData } from './disbursementQueryOrderResponseData'; + +export class DisbursementQueryOrderResponse { + /** + * 1 - SUCCESS 2 - FAIL + */ + 'return_code'?: number; + + /** + * Return code description + */ + 'return_message'?: string; + + /** + * -101 - Order not found -401 - Request param illegal -402 - Unauthorized -500 - ZaloPay system error -503 - The system is maintenance + */ + 'sub_return_code'?: number; + + /** + * Sub return code description + */ + 'sub_return_message'?: string; + + 'data'?: DisbursementQueryOrderResponseData; + + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "return_code", + "baseName": "return_code", + "type": "number" + }, + { + "name": "return_message", + "baseName": "return_message", + "type": "string" + }, + { + "name": "sub_return_code", + "baseName": "sub_return_code", + "type": "number" + }, + { + "name": "sub_return_message", + "baseName": "sub_return_message", + "type": "string" + }, + { + "name": "data", + "baseName": "data", + "type": "DisbursementQueryOrderResponseData" + } ]; + + static getAttributeTypeMap() { + return DisbursementQueryOrderResponse.attributeTypeMap; + } +} + diff --git a/src/zalopay/models/disbursementQueryOrderResponseData.ts b/src/zalopay/models/disbursementQueryOrderResponseData.ts new file mode 100644 index 0000000..7b81973 --- /dev/null +++ b/src/zalopay/models/disbursementQueryOrderResponseData.ts @@ -0,0 +1,140 @@ +/* +* The version of the ZaloPay OpenAPI document: v1.0.0 +* Contact: developer@zalopay.vn +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit this class manually. +*/ + +export class DisbursementQueryOrderResponseData { + /** + * Merchant transaction code + */ + 'order_id'?: string; + + /** + * 1 – SUCCESS 2 – FAIL 3 – PROCESSING: Has to repeat query order status in a period of time until final status (configured interval and number of query) 4 – PENDING: Pending transactions, needs to be manually fixed by internal teams + */ + 'status'?: number; + + /** + * User\'s identity + */ + 'm_u_id'?: string; + + /** + * User’s phone + */ + 'phone'?: string; + + /** + * Transaction amount + */ + 'amount'?: number; + + /** + * Transaction description + */ + 'description'?: string; + + /** + * Partner fee + */ + 'partner_fee'?: number; + + /** + * ZaloPay fee + */ + 'zlp_fee'?: number; + + /** + * Order extra info, json string + */ + 'extra_info'?: string; + + /** + * Transaction timestamp in millisecond + */ + 'time'?: number; + + /** + * ZaloPay\'s transaction code + */ + 'zp_trans_id'?: string; + + /** + * ZaloPay middle_page url for notify merchant about success disbursement result + */ + 'result_url'?: string; + + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "order_id", + "baseName": "order_id", + "type": "string" + }, + { + "name": "status", + "baseName": "status", + "type": "number" + }, + { + "name": "m_u_id", + "baseName": "m_u_id", + "type": "string" + }, + { + "name": "phone", + "baseName": "phone", + "type": "string" + }, + { + "name": "amount", + "baseName": "amount", + "type": "number" + }, + { + "name": "description", + "baseName": "description", + "type": "string" + }, + { + "name": "partner_fee", + "baseName": "partner_fee", + "type": "number" + }, + { + "name": "zlp_fee", + "baseName": "zlp_fee", + "type": "number" + }, + { + "name": "extra_info", + "baseName": "extra_info", + "type": "string" + }, + { + "name": "time", + "baseName": "time", + "type": "number" + }, + { + "name": "zp_trans_id", + "baseName": "zp_trans_id", + "type": "string" + }, + { + "name": "result_url", + "baseName": "result_url", + "type": "string" + } ]; + + static getAttributeTypeMap() { + return DisbursementQueryOrderResponseData.attributeTypeMap; + } +} + diff --git a/src/zalopay/models/disbursementQueryUserRequest.ts b/src/zalopay/models/disbursementQueryUserRequest.ts new file mode 100644 index 0000000..98321cd --- /dev/null +++ b/src/zalopay/models/disbursementQueryUserRequest.ts @@ -0,0 +1,70 @@ +/* +* The version of the ZaloPay OpenAPI document: v1.0.0 +* Contact: developer@zalopay.vn +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit this class manually. +*/ + +export class DisbursementQueryUserRequest { + /** + * Client request identity, using for tracing request + */ + 'request_id'?: string; + + /** + * The unique ID of the partner will be provided after the partner registered successfully with ZaloPay + */ + 'app_id'?: number; + + /** + * The user\'s phone + */ + 'phone': string; + + /** + * Requests timestamp in ms + */ + 'time': number; + + /** + * \"It is signature of order. It’s calculated by following input: hmacInput = (app_id + “|” + phone + “|” + time) and use sha256 with app’s hmac key\" + */ + 'mac'?: string; + + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "request_id", + "baseName": "request_id", + "type": "string" + }, + { + "name": "app_id", + "baseName": "app_id", + "type": "number" + }, + { + "name": "phone", + "baseName": "phone", + "type": "string" + }, + { + "name": "time", + "baseName": "time", + "type": "number" + }, + { + "name": "mac", + "baseName": "mac", + "type": "string" + } ]; + + static getAttributeTypeMap() { + return DisbursementQueryUserRequest.attributeTypeMap; + } +} + diff --git a/src/zalopay/models/disbursementQueryUserResponse.ts b/src/zalopay/models/disbursementQueryUserResponse.ts new file mode 100644 index 0000000..e61fc0d --- /dev/null +++ b/src/zalopay/models/disbursementQueryUserResponse.ts @@ -0,0 +1,68 @@ +/* +* The version of the ZaloPay OpenAPI document: v1.0.0 +* Contact: developer@zalopay.vn +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit this class manually. +*/ +import { DisbursementQueryUserResponseData } from './disbursementQueryUserResponseData'; + +export class DisbursementQueryUserResponse { + /** + * 1 - SUCCESS 2 - FAIL + */ + 'return_code'?: number; + + /** + * Return code description + */ + 'return_message'?: string; + + /** + * -101 - User wallet account not exists -401 - Request param illegal -402 - Unauthorized -500 - ZaloPay system error -503 - The system is maintenance -1011 - User wallet account has been locked + */ + 'sub_return_code'?: number; + + /** + * Sub return code description + */ + 'sub_return_message'?: string; + + 'data'?: DisbursementQueryUserResponseData; + + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "return_code", + "baseName": "return_code", + "type": "number" + }, + { + "name": "return_message", + "baseName": "return_message", + "type": "string" + }, + { + "name": "sub_return_code", + "baseName": "sub_return_code", + "type": "number" + }, + { + "name": "sub_return_message", + "baseName": "sub_return_message", + "type": "string" + }, + { + "name": "data", + "baseName": "data", + "type": "DisbursementQueryUserResponseData" + } ]; + + static getAttributeTypeMap() { + return DisbursementQueryUserResponse.attributeTypeMap; + } +} + diff --git a/src/zalopay/models/disbursementQueryUserResponseData.ts b/src/zalopay/models/disbursementQueryUserResponseData.ts new file mode 100644 index 0000000..25d1216 --- /dev/null +++ b/src/zalopay/models/disbursementQueryUserResponseData.ts @@ -0,0 +1,70 @@ +/* +* The version of the ZaloPay OpenAPI document: v1.0.0 +* Contact: developer@zalopay.vn +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit this class manually. +*/ + +export class DisbursementQueryUserResponseData { + /** + * ZaloPay reference id + */ + 'reference_id'?: string; + + /** + * User’s identity in ZaloPay system + */ + 'm_u_id'?: string; + + /** + * User’s full name + */ + 'name'?: string; + + /** + * User\'s phone + */ + 'phone'?: string; + + /** + * \"ZaloPay middle_page url to onboard new users\" \"Displayed when receiving return_code =2 && sub_return_code = -101\" + */ + 'onboarding_url'?: string; + + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "reference_id", + "baseName": "reference_id", + "type": "string" + }, + { + "name": "m_u_id", + "baseName": "m_u_id", + "type": "string" + }, + { + "name": "name", + "baseName": "name", + "type": "string" + }, + { + "name": "phone", + "baseName": "phone", + "type": "string" + }, + { + "name": "onboarding_url", + "baseName": "onboarding_url", + "type": "string" + } ]; + + static getAttributeTypeMap() { + return DisbursementQueryUserResponseData.attributeTypeMap; + } +} + diff --git a/src/zalopay/models/disbursementTopupRequest.ts b/src/zalopay/models/disbursementTopupRequest.ts new file mode 100644 index 0000000..f9474b0 --- /dev/null +++ b/src/zalopay/models/disbursementTopupRequest.ts @@ -0,0 +1,130 @@ +/* +* The version of the ZaloPay OpenAPI document: v1.0.0 +* Contact: developer@zalopay.vn +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit this class manually. +*/ + +export class DisbursementTopupRequest { + /** + * The unique ID of the partner will be provided after the partner registered successfully with ZaloPay + */ + 'app_id'?: number; + + /** + * The unique ID of the partner will be provided after the partner registered successfully with ZaloPay + */ + 'payment_id'?: string; + + /** + * The unique id, generate in partner system. Using for reconcile + */ + 'partner_order_id': string; + + /** + * The user\'s identity in the response of QueryUser API + */ + 'm_u_id': string; + + /** + * The amount top-up to receiver’s wallet + */ + 'amount': number; + + /** + * Extend information + */ + 'description': string; + + /** + * Partner\'s specify info, a json string. Example: “{\\“store_id\\”:\\“s2\\”,\\“store_name\\”:\\ “name\\”} + */ + 'partner_embed_data': string; + + /** + * Zalopay reference id response in query user response + */ + 'reference_id'?: string; + + /** + * Using for extend purpose, a json string + */ + 'extra_info': string; + + /** + * Requests timestamp in ms + */ + 'time': number; + + /** + * It’s calculated by following input: hmacinput = (app_id + “|” + payment_id + “|” + partner_order_id + “|” + m_u_id + “|” + amount + “|” + description + “|” + partner_embed_data + “|” + extra_info + “|” + time) and use sha256 with app’s hmac key then RSA with app’s private key ZaloPay provide + */ + 'sig'?: string; + + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "app_id", + "baseName": "app_id", + "type": "number" + }, + { + "name": "payment_id", + "baseName": "payment_id", + "type": "string" + }, + { + "name": "partner_order_id", + "baseName": "partner_order_id", + "type": "string" + }, + { + "name": "m_u_id", + "baseName": "m_u_id", + "type": "string" + }, + { + "name": "amount", + "baseName": "amount", + "type": "number" + }, + { + "name": "description", + "baseName": "description", + "type": "string" + }, + { + "name": "partner_embed_data", + "baseName": "partner_embed_data", + "type": "string" + }, + { + "name": "reference_id", + "baseName": "reference_id", + "type": "string" + }, + { + "name": "extra_info", + "baseName": "extra_info", + "type": "string" + }, + { + "name": "time", + "baseName": "time", + "type": "number" + }, + { + "name": "sig", + "baseName": "sig", + "type": "string" + } ]; + + static getAttributeTypeMap() { + return DisbursementTopupRequest.attributeTypeMap; + } +} + diff --git a/src/zalopay/models/disbursementTopupResponse.ts b/src/zalopay/models/disbursementTopupResponse.ts new file mode 100644 index 0000000..892fec3 --- /dev/null +++ b/src/zalopay/models/disbursementTopupResponse.ts @@ -0,0 +1,68 @@ +/* +* The version of the ZaloPay OpenAPI document: v1.0.0 +* Contact: developer@zalopay.vn +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit this class manually. +*/ +import { DisbursementTopupResponseData } from './disbursementTopupResponseData'; + +export class DisbursementTopupResponse { + /** + * 1 - SUCCESS 2 - FAIL + */ + 'return_code'?: number; + + /** + * Return code description + */ + 'return_message'?: string; + + /** + * -68 - Duplicate resource -101 - User wallet account not exists -401 - Request param illegal -402 - Unauthorized -406 - User wallet reaches a fund-in limitation -500 - ZaloPay system error -503 - The system is maintenance + */ + 'sub_return_code'?: number; + + /** + * Sub return code description + */ + 'sub_return_message'?: string; + + 'data'?: DisbursementTopupResponseData; + + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "return_code", + "baseName": "return_code", + "type": "number" + }, + { + "name": "return_message", + "baseName": "return_message", + "type": "string" + }, + { + "name": "sub_return_code", + "baseName": "sub_return_code", + "type": "number" + }, + { + "name": "sub_return_message", + "baseName": "sub_return_message", + "type": "string" + }, + { + "name": "data", + "baseName": "data", + "type": "DisbursementTopupResponseData" + } ]; + + static getAttributeTypeMap() { + return DisbursementTopupResponse.attributeTypeMap; + } +} + diff --git a/src/zalopay/models/disbursementTopupResponseData.ts b/src/zalopay/models/disbursementTopupResponseData.ts new file mode 100644 index 0000000..8224048 --- /dev/null +++ b/src/zalopay/models/disbursementTopupResponseData.ts @@ -0,0 +1,130 @@ +/* +* The version of the ZaloPay OpenAPI document: v1.0.0 +* Contact: developer@zalopay.vn +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit this class manually. +*/ + +export class DisbursementTopupResponseData { + /** + * Merchant transaction code + */ + 'order_id'?: string; + + /** + * 1 – SUCCESS 2 – FAIL 3 – PROCESSING: Has to repeat query order status in a period of time until final status (configured interval and number of query) 4 – PENDING: Pending transactions, needs to be manually fixed by internal teams + */ + 'status'?: number; + + /** + * User\'s identity + */ + 'm_u_id'?: string; + + /** + * User’s phone + */ + 'phone'?: string; + + /** + * Transaction amount + */ + 'amount'?: number; + + /** + * Transaction description + */ + 'description'?: string; + + /** + * Partner fee + */ + 'partner_fee'?: number; + + /** + * ZaloPay fee + */ + 'zlp_fee'?: number; + + /** + * Order extra info, json string + */ + 'extra_info'?: string; + + /** + * Transaction timestamp in millisecond + */ + 'time'?: number; + + /** + * ZaloPay middle_page url to upgrade for users with fund-in limit Displayed when receiving return_code =2 && sub_return_code = -406 + */ + 'upgrade_url'?: string; + + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "order_id", + "baseName": "order_id", + "type": "string" + }, + { + "name": "status", + "baseName": "status", + "type": "number" + }, + { + "name": "m_u_id", + "baseName": "m_u_id", + "type": "string" + }, + { + "name": "phone", + "baseName": "phone", + "type": "string" + }, + { + "name": "amount", + "baseName": "amount", + "type": "number" + }, + { + "name": "description", + "baseName": "description", + "type": "string" + }, + { + "name": "partner_fee", + "baseName": "partner_fee", + "type": "number" + }, + { + "name": "zlp_fee", + "baseName": "zlp_fee", + "type": "number" + }, + { + "name": "extra_info", + "baseName": "extra_info", + "type": "string" + }, + { + "name": "time", + "baseName": "time", + "type": "number" + }, + { + "name": "upgrade_url", + "baseName": "upgrade_url", + "type": "string" + } ]; + + static getAttributeTypeMap() { + return DisbursementTopupResponseData.attributeTypeMap; + } +} + diff --git a/src/zalopay/models/models.ts b/src/zalopay/models/models.ts index 9f8579c..c019c18 100644 --- a/src/zalopay/models/models.ts +++ b/src/zalopay/models/models.ts @@ -21,6 +21,18 @@ export * from './agreementQueryUserRequest'; export * from './agreementQueryUserResponse'; export * from './agreementUnbindRequest'; export * from './agreementUnbindResponse'; +export * from './disbursementQueryMerchantBalanceRequest'; +export * from './disbursementQueryMerchantBalanceResponse'; +export * from './disbursementQueryMerchantBalanceResponseData'; +export * from './disbursementQueryOrderRequest'; +export * from './disbursementQueryOrderResponse'; +export * from './disbursementQueryOrderResponseData'; +export * from './disbursementQueryUserRequest'; +export * from './disbursementQueryUserResponse'; +export * from './disbursementQueryUserResponseData'; +export * from './disbursementTopupRequest'; +export * from './disbursementTopupResponse'; +export * from './disbursementTopupResponseData'; export * from './oACommonResponse'; export * from './oACreateOrderRequest'; export * from './oACreateOrderResponse'; @@ -45,6 +57,18 @@ import { AgreementQueryUserRequest } from './agreementQueryUserRequest'; import { AgreementQueryUserResponse } from './agreementQueryUserResponse'; import { AgreementUnbindRequest } from './agreementUnbindRequest'; import { AgreementUnbindResponse } from './agreementUnbindResponse'; +import { DisbursementQueryMerchantBalanceRequest } from './disbursementQueryMerchantBalanceRequest'; +import { DisbursementQueryMerchantBalanceResponse } from './disbursementQueryMerchantBalanceResponse'; +import { DisbursementQueryMerchantBalanceResponseData } from './disbursementQueryMerchantBalanceResponseData'; +import { DisbursementQueryOrderRequest } from './disbursementQueryOrderRequest'; +import { DisbursementQueryOrderResponse } from './disbursementQueryOrderResponse'; +import { DisbursementQueryOrderResponseData } from './disbursementQueryOrderResponseData'; +import { DisbursementQueryUserRequest } from './disbursementQueryUserRequest'; +import { DisbursementQueryUserResponse } from './disbursementQueryUserResponse'; +import { DisbursementQueryUserResponseData } from './disbursementQueryUserResponseData'; +import { DisbursementTopupRequest } from './disbursementTopupRequest'; +import { DisbursementTopupResponse } from './disbursementTopupResponse'; +import { DisbursementTopupResponseData } from './disbursementTopupResponseData'; import { OACommonResponse } from './oACommonResponse'; import { OACreateOrderRequest } from './oACreateOrderRequest'; import { OACreateOrderResponse } from './oACreateOrderResponse'; @@ -86,6 +110,18 @@ let typeMap: {[index: string]: any} = { "AgreementQueryUserResponse": AgreementQueryUserResponse, "AgreementUnbindRequest": AgreementUnbindRequest, "AgreementUnbindResponse": AgreementUnbindResponse, + "DisbursementQueryMerchantBalanceRequest": DisbursementQueryMerchantBalanceRequest, + "DisbursementQueryMerchantBalanceResponse": DisbursementQueryMerchantBalanceResponse, + "DisbursementQueryMerchantBalanceResponseData": DisbursementQueryMerchantBalanceResponseData, + "DisbursementQueryOrderRequest": DisbursementQueryOrderRequest, + "DisbursementQueryOrderResponse": DisbursementQueryOrderResponse, + "DisbursementQueryOrderResponseData": DisbursementQueryOrderResponseData, + "DisbursementQueryUserRequest": DisbursementQueryUserRequest, + "DisbursementQueryUserResponse": DisbursementQueryUserResponse, + "DisbursementQueryUserResponseData": DisbursementQueryUserResponseData, + "DisbursementTopupRequest": DisbursementTopupRequest, + "DisbursementTopupResponse": DisbursementTopupResponse, + "DisbursementTopupResponseData": DisbursementTopupResponseData, "OACommonResponse": OACommonResponse, "OACreateOrderRequest": OACreateOrderRequest, "OACreateOrderResponse": OACreateOrderResponse, diff --git a/src/zalopay/services/disbursement.ts b/src/zalopay/services/disbursement.ts new file mode 100644 index 0000000..51b7867 --- /dev/null +++ b/src/zalopay/services/disbursement.ts @@ -0,0 +1,128 @@ +import {ZaloPayClient} from "../zaloPayClient"; +import Service from "../service"; +import getJsonResponse from "../helpers/getJsonResponse"; +import { + DisbursementQueryMerchantBalanceRequest, + DisbursementQueryMerchantBalanceResponse, + DisbursementQueryOrderRequest, + DisbursementQueryOrderResponse, + DisbursementQueryUserRequest, + DisbursementQueryUserResponse, + DisbursementTopupRequest, + DisbursementTopupResponse, + ObjectSerializer +} from "../models/models"; +import HmacUtils from "../utils/hmacUtils"; +import DisbursementResource from "./resource/disbursementResource"; +import RSAUtils from "../utils/rsaUtils"; + +class Disbursement extends Service { + private readonly _query_merchant_balance: DisbursementResource; + private readonly _query_user: DisbursementResource; + + private readonly _topup: DisbursementResource; + private readonly _query_order: DisbursementResource; + + private readonly hmacUtils: HmacUtils; + private readonly rsaUtils: RSAUtils; + + public constructor(client: ZaloPayClient) { + super(client); + if(!client.config.paymentId || !client.config.privateKey) { + throw new Error("The paymentId and privateKey config keys are required for Disbursement service"); + } + this._query_merchant_balance = new DisbursementResource(this, "/v2/disbursement/balance"); + this._query_user = new DisbursementResource(this, "/v2/disbursement/user"); + this._topup = new DisbursementResource(this, "/v2/disbursement/topup"); + this._query_order = new DisbursementResource(this, "/v2/disbursement/txn"); + + this.hmacUtils = new HmacUtils(); + this.rsaUtils = RSAUtils.fromConfig(client.config, {scheme: "pkcs8"}); + } + + public async queryMerchantBalance(balanceRequest: DisbursementQueryMerchantBalanceRequest): Promise { + balanceRequest.app_id ||= +this.config.appId; + balanceRequest.payment_id = balanceRequest.payment_id ?? this.config.paymentId; + const dataSign: string = this.getDataToSign(balanceRequest); + balanceRequest.mac = this.hmacUtils.calculateHmac(dataSign, this.config.key1); + const response = await getJsonResponse( + this._query_merchant_balance, + "post", + balanceRequest, + ); + return ObjectSerializer.deserialize(response, "DisbursementQueryMerchantBalanceResponse"); + } + + + public async queryUser(queryUserRequest: DisbursementQueryUserRequest): Promise { + queryUserRequest.app_id ||= +this.config.appId; + const dataSign: string = this.getDataToSign(queryUserRequest); + queryUserRequest.mac = this.hmacUtils.calculateHmac(dataSign, this.config.key1); + const response = await getJsonResponse( + this._query_user, + "post", + queryUserRequest, + ); + return ObjectSerializer.deserialize(response, "DisbursementQueryUserResponse"); + } + + public async topup(payRequest: DisbursementTopupRequest): Promise { + payRequest.app_id ||= +this.config.appId; + payRequest.payment_id = payRequest.payment_id ?? this.config.paymentId; + const dataSign: string = this.getDataToSign(payRequest); + const mac = this.hmacUtils.calculateHmac(dataSign, this.config.key1); + payRequest.sig = this.rsaUtils.sign(mac, 'base64', 'utf8'); + const response = await getJsonResponse( + this._topup, + "post", + payRequest, + ); + return ObjectSerializer.deserialize(response, "DisbursementTopupResponse"); + } + + public async queryOrder(queryRequest: DisbursementQueryOrderRequest): Promise { + queryRequest.app_id ||= +this.config.appId; + const dataSign: string = this.getDataToSign(queryRequest); + queryRequest.mac = this.hmacUtils.calculateHmac(dataSign, this.config.key1); + const response = await getJsonResponse( + this._query_order, + "post", + queryRequest, + ); + return ObjectSerializer.deserialize(response, "DisbursementQueryOrderResponse"); + } + + private getDataToSign(request: + DisbursementQueryMerchantBalanceRequest + | DisbursementQueryUserRequest + | DisbursementTopupRequest + | DisbursementQueryOrderRequest): string { + const data = []; + if (request instanceof DisbursementQueryMerchantBalanceRequest) { + data.push(request.app_id); + data.push(request.payment_id); + data.push(request.time); + } else if (request instanceof DisbursementQueryUserRequest) { + data.push(request.app_id); + data.push(request.phone); + data.push(request.time); + } else if (request instanceof DisbursementTopupRequest) { + data.push(request.app_id); + data.push(request.payment_id); + data.push(request.partner_order_id); + data.push(request.m_u_id); + data.push(request.amount); + data.push(request.description); + data.push(request.partner_embed_data); + data.push(request.extra_info); + data.push(request.time); + } else if (request instanceof DisbursementQueryOrderRequest) { + data.push(request.app_id); + data.push(request.partner_order_id); + data.push(request.time); + } + return data.join("|"); + } +} + +export default Disbursement; \ No newline at end of file diff --git a/src/zalopay/services/index.ts b/src/zalopay/services/index.ts index eba4491..d01a26f 100644 --- a/src/zalopay/services/index.ts +++ b/src/zalopay/services/index.ts @@ -1 +1,2 @@ -export {default as TokenizationAPI} from "./tokenization"; \ No newline at end of file +export {default as TokenizationAPI} from "./tokenization"; +export {default as DisbursementAPI} from "./disbursement"; \ No newline at end of file diff --git a/src/zalopay/services/resource/disbursementResource.ts b/src/zalopay/services/resource/disbursementResource.ts new file mode 100644 index 0000000..6b289e4 --- /dev/null +++ b/src/zalopay/services/resource/disbursementResource.ts @@ -0,0 +1,13 @@ +import Resource from "../resource"; +import Service from "../../service"; + +class DisbursementResource extends Resource { + public constructor(service: Service, endpoint: string) { + super( + service, + endpoint + ); + } +} + +export default DisbursementResource; \ No newline at end of file diff --git a/src/zalopay/utils/rsaUtils.ts b/src/zalopay/utils/rsaUtils.ts new file mode 100644 index 0000000..2bd0fd8 --- /dev/null +++ b/src/zalopay/utils/rsaUtils.ts @@ -0,0 +1,52 @@ + +import { Config } from "../model/Config"; +import NodeRSA from "node-rsa"; + +type Encoding = 'ascii' | 'utf8' | 'utf16le' | 'ucs2' | 'latin1' | 'base64' | 'hex' | 'binary' | 'buffer'; + +export type Scheme = 'pkcs1' | 'pkcs8'; + +export interface RSAOptions { + /** + * The encryption scheme + */ + scheme: Scheme; + } + +class RSAUtils { + private rsa: NodeRSA + + static fromConfig(config: Config, options: RSAOptions): RSAUtils { + return new RSAUtils(config.privateKey ?? "", options.scheme); + } + + private constructor(privateKey: string, scheme?: Scheme) { + this.rsa = new NodeRSA(privateKey, scheme as NodeRSA.Format ?? "pkcs1"); + } + + /** + * + * @param data The data to sign + * @param encoding encoding for output result, may be 'buffer', 'binary', 'hex' or 'base64'. Default 'buffer' + * @param sourceEncoding source encoding, works only with string buffer. Can take standard Node.js Buffer encodings (hex, utf8, base64, etc). 'utf8' by default. + * @returns signed data + */ + public sign(data: string, encoding?: Encoding, sourceEncoding?: Encoding) { + return this.rsa.sign(Buffer.from(data), encoding ?? "buffer", sourceEncoding ?? "utf8"); + } + + /** + * + * @param sourceData The data for check + * @param signature The signature for check + * @param encoding encoding for output result, may be 'buffer', 'binary', 'hex' or 'base64'. Default 'buffer' + * @param sourceEncoding source encoding, works only with string buffer. Can take standard Node.js Buffer encodings (hex, utf8, base64, etc). 'utf8' by default. + * @param signatureEncoding encoding of given signature. May be 'buffer', 'binary', 'hex' or 'base64'. + * @returns True if signature is valid + */ + public verify(sourceData: string, signature: string, sourceEncoding?: Encoding, signatureEncoding?: Encoding) { + return this.rsa.verify(Buffer.from(sourceData), signature, sourceEncoding ?? "utf8", signatureEncoding ?? "buffer"); + } +} + +export default RSAUtils;