aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2022-11-04 13:49:28 +0700
committerShulhan <ms@kilabit.info>2022-11-04 19:01:07 +0700
commit0eef440900eda71b3a257707e49e0ad53be5353f (patch)
tree7682364bc2f6ac826afd27fe71980ede98160b73
parent1846d3fc5d6a364986c073afccc66936ab3e469d (diff)
downloadduitku-0eef440900eda71b3a257707e49e0ad53be5353f.tar.xz
all: implement API for online transfer inquiry
The RtolInquiry method get the information of the name of the account owner of the transfer destination. After getting this information, customers can determine whether the purpose of such a transfer is in accordance with the intended or not. If appropriate, the customer can proceed to the transfer process. Ref: https://docs.duitku.com/disbursement/en/#transfer-online
-rw-r--r--client.go65
-rw-r--r--client_options.go32
-rw-r--r--client_test.go33
-rw-r--r--duitku.go2
-rw-r--r--request.go16
-rw-r--r--rtol_inquiry.go46
-rw-r--r--rtol_inquiry_response.go30
-rw-r--r--testdata/disbursement_rtol_inquiry_test.txt16
8 files changed, 236 insertions, 4 deletions
diff --git a/client.go b/client.go
index 1a531dd..8768a5b 100644
--- a/client.go
+++ b/client.go
@@ -8,7 +8,9 @@ import (
"fmt"
"net/http"
"sort"
+ "strconv"
"strings"
+ "time"
libhttp "github.com/shuLhan/share/lib/http"
)
@@ -16,6 +18,10 @@ import (
const (
PathDisbursementListBank = `/disbursement/listBank`
PathDisbursementCheckBalance = `/disbursement/checkBalance`
+
+ // Path for transfer online.
+ PathDisbursementInquiry = `/disbursement/inquiry`
+ PathDisbursementInquirySandbox = `/disbursement/inquirysandbox` // Used when server URL is sandbox (testing).
)
type Client struct {
@@ -27,11 +33,17 @@ type Client struct {
// NewClient create and initialize new Client.
func NewClient(opts ClientOptions) (cl *Client, err error) {
var (
+ logp = `NewClient`
httpcOpts = libhttp.ClientOptions{
ServerUrl: opts.ServerUrl,
}
)
+ err = opts.validate()
+ if err != nil {
+ return nil, fmt.Errorf(`%s: %w`, logp, err)
+ }
+
cl = &Client{
Client: libhttp.NewClient(&httpcOpts),
opts: opts,
@@ -117,3 +129,56 @@ func (cl *Client) DisbursementListBank() (banks []Bank, err error) {
return banks, nil
}
+
+// RtolInquiry get the information of the name of the account owner of the
+// transfer destination.
+//
+// After getting this information, customers can determine whether the purpose
+// of such a transfer is in accordance with the intended or not.
+// If appropriate, the customer can proceed to the transfer process.
+//
+// Ref: https://docs.duitku.com/disbursement/en/#transfer-online
+func (cl *Client) RtolInquiry(req RtolInquiry) (res *RtolInquiryResponse, err error) {
+ var (
+ now = time.Now()
+ logp = `RtolInquiry`
+ path = PathDisbursementInquiry
+
+ resHttp *http.Response
+ resBody []byte
+ )
+
+ // Since the path is different in test environment, we check the host
+ // here to set it.
+ if cl.opts.host != hostLive {
+ path = PathDisbursementInquirySandbox
+ }
+
+ req.UserID, err = strconv.ParseInt(cl.opts.UserID, 10, 64)
+ if err != nil {
+ return nil, fmt.Errorf(`%s: %s`, logp, err)
+ }
+
+ req.Email = cl.opts.Email
+ req.Timestamp = now.UnixMilli()
+
+ req.sign(cl.opts.ApiKey)
+
+ resHttp, resBody, err = cl.PostJSON(path, nil, req)
+ if err != nil {
+ return nil, fmt.Errorf(`%s: %w`, logp, err)
+ }
+ if resHttp.StatusCode >= 500 {
+ return nil, fmt.Errorf(`%s: %s`, logp, resHttp.Status)
+ }
+
+ err = json.Unmarshal(resBody, &res)
+ if err != nil {
+ return nil, fmt.Errorf(`%s: %w`, logp, err)
+ }
+ if res.Code != resCodeSuccess {
+ return nil, fmt.Errorf(`%s: %s: %s`, logp, res.Code, res.Desc)
+ }
+
+ return res, nil
+}
diff --git a/client_options.go b/client_options.go
index 7f23c24..5ed7d2c 100644
--- a/client_options.go
+++ b/client_options.go
@@ -3,10 +3,42 @@
package duitku
+import (
+ "fmt"
+ "net/url"
+)
+
// ClientOptions configuration for HTTP client.
type ClientOptions struct {
ServerUrl string
UserID string
Email string
ApiKey string
+
+ // The hostname extracted from ServerUrl.
+ host string
+}
+
+// validate each field values.
+func (opts *ClientOptions) validate() (err error) {
+ var (
+ urlServer *url.URL
+ )
+
+ urlServer, err = url.Parse(opts.ServerUrl)
+ if err != nil {
+ return fmt.Errorf(`invalid or empty ServerUrl: %s`, opts.ServerUrl)
+ }
+ opts.host = urlServer.Host
+
+ if len(opts.UserID) == 0 {
+ return fmt.Errorf(`invalid or empty UserID: %s`, opts.UserID)
+ }
+ if len(opts.Email) == 0 {
+ return fmt.Errorf(`invalid or empty Email: %s`, opts.Email)
+ }
+ if len(opts.ApiKey) == 0 {
+ return fmt.Errorf(`invalid or empty ApiKey: %s`, opts.ApiKey)
+ }
+ return nil
}
diff --git a/client_test.go b/client_test.go
index 95f61a6..09db153 100644
--- a/client_test.go
+++ b/client_test.go
@@ -44,6 +44,39 @@ func TestClient_DisbursementCheckBalance(t *testing.T) {
test.Assert(t, `DisbursementCheckBalance`, string(exp), string(got))
}
+func TestClient_RtolInquiry_live(t *testing.T) {
+ t.Skip(`This test require external call to server`)
+
+ var (
+ inquiryReq RtolInquiry
+ err error
+ tdata *test.Data
+ inquiryRes *RtolInquiryResponse
+ )
+
+ tdata, err = test.LoadData(`testdata/disbursement_rtol_inquiry_test.txt`)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = json.Unmarshal(tdata.Input[`request.json`], &inquiryReq)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ inquiryRes, err = testClient.RtolInquiry(inquiryReq)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // We cannot compare the response, because for each call to server
+ // it will return different CustRefNumber and DisburseID.
+
+ t.Logf(`inquiryRes: %+v`, inquiryRes)
+
+ test.Assert(t, `AccountName`, `Test Account`, inquiryRes.AccountName)
+}
+
func TestClient_DisbursementListBank(t *testing.T) {
var (
tdata *test.Data
diff --git a/duitku.go b/duitku.go
index b4d9fd2..9a16fa6 100644
--- a/duitku.go
+++ b/duitku.go
@@ -9,4 +9,6 @@ package duitku
const (
ServerUrlLive = `https://passport.duitku.com/webapi/api`
ServerUrlSandbox = `https://sandbox.duitku.com/webapi/api`
+
+ hostLive = `passport.duitku.com`
)
diff --git a/request.go b/request.go
index 043a414..b446b32 100644
--- a/request.go
+++ b/request.go
@@ -7,19 +7,27 @@ import (
"crypto/sha256"
"encoding/hex"
"fmt"
+ "strconv"
"time"
)
// request define common HTTP request fields.
type request struct {
- UserID string `json:"userID"`
- Email string `json:"email"`
+ // Merchant email, filled from ClientOptions.Email.
+ Email string `json:"email"`
+
+ // Hash of some fields in the request along with its ApiKey.
Signature string `json:"signature"`
- Timestamp int64 `json:"timestamp"`
+
+ // Merchant ID, filled from ClientOptions.UserID.
+ UserID int64 `json:"userId"`
+
+ // Unix Timestamp in milliseconds.
+ Timestamp int64 `json:"timestamp"`
}
func createRequest(opts ClientOptions) (req request) {
- req.UserID = opts.UserID
+ req.UserID, _ = strconv.ParseInt(opts.UserID, 10, 64)
req.Email = opts.Email
req.Timestamp = time.Now().UnixMilli()
diff --git a/rtol_inquiry.go b/rtol_inquiry.go
new file mode 100644
index 0000000..33ab55d
--- /dev/null
+++ b/rtol_inquiry.go
@@ -0,0 +1,46 @@
+package duitku
+
+import (
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+)
+
+// RtolInquiry contains request to initiate transfer from merchant to
+// customer's bank account using [Online Transfer].
+//
+// The signature formula is SHA256(email + timestamp + bankCode +
+// bankAccount + amountTransfer + purpose + apiKey).
+//
+// [Online Transfer]: https://docs.duitku.com/disbursement/en/#online-transfer-inquiry-request
+type RtolInquiry struct {
+ // Destination Bank Code.
+ BankCode string `json:"bankCode"`
+
+ // Destination account number.
+ BankAccount string `json:"bankAccount"`
+
+ // Description of transfer purpose.
+ Purpose string `json:"purpose"`
+
+ // Customer name provided by merchant.
+ SenderName string `json:"senderName"`
+
+ request
+
+ // Customer ID provided by merchant.
+ SenderID int64 `json:"senderID"`
+
+ // Disbursement transfer amount.
+ Amount int64 `json:"amountTransfer"`
+}
+
+func (inq *RtolInquiry) sign(apiKey string) {
+ var (
+ plain = fmt.Sprintf(`%s%d%s%s%d%s%s`, inq.Email,
+ inq.Timestamp, inq.BankCode, inq.BankAccount,
+ inq.Amount, inq.Purpose, apiKey)
+ plainHash [sha256.Size]byte = sha256.Sum256([]byte(plain))
+ )
+ inq.Signature = hex.EncodeToString(plainHash[:])
+}
diff --git a/rtol_inquiry_response.go b/rtol_inquiry_response.go
new file mode 100644
index 0000000..1edc7a0
--- /dev/null
+++ b/rtol_inquiry_response.go
@@ -0,0 +1,30 @@
+package duitku
+
+import "github.com/shuLhan/share/lib/math/big"
+
+// RtolInquiryResponse contains response from inquiry for Online Transfer.
+type RtolInquiryResponse struct {
+ response
+
+ // Email sent when inquiry process.
+ Email string `json:"email"`
+
+ // Destination Bank Code.
+ BankCode string `json:"bankCode"`
+
+ // Destination account number.
+ BankAccount string `json:"bankAccount"`
+
+ // Disbursement transfer amount.
+ Amount *big.Rat `json:"amountTransfer"`
+
+ // Bank Account owner.
+ AccountName string `json:"accountName"`
+
+ // 9 Digit Customer reference number that will be printed when the
+ // transaction is successful.
+ CustRefNumber string `json:"custRefNumber"`
+
+ // Disbursement ID from duitku. Please save it for checking purpose.
+ DisburseID int64 `json:"disburseId"`
+}
diff --git a/testdata/disbursement_rtol_inquiry_test.txt b/testdata/disbursement_rtol_inquiry_test.txt
new file mode 100644
index 0000000..b72eab9
--- /dev/null
+++ b/testdata/disbursement_rtol_inquiry_test.txt
@@ -0,0 +1,16 @@
+Test disbursement transfer online inquiry.
+
+Ref: https://docs.duitku.com/disbursement/en/#transfer-online
+
+>>> request.json
+{
+ "userId": 3551,
+ "email": "test@chakratechnology.com",
+ "amountTransfer": 10000,
+ "bankAccount": "8760673566",
+ "bankCode": "002",
+ "purpose": "Test Transfer Online Inquiry with duitku.",
+ "timestamp": 1506486841000,
+ "senderId": 123456789,
+ "senderName": "John Doe"
+}