aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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"
+}