aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2022-11-07 11:55:20 +0700
committerShulhan <ms@kilabit.info>2022-11-07 11:59:50 +0700
commiteb3c3fc6b20b77c26a571395f584efdce8cfb4f7 (patch)
tree0eb6fc3a9435a93f8bf6a33fa9c8cf4b7ed93b02
parentab25f641e3343151839cf2624d19ef235a070c9d (diff)
downloadduitku-eb3c3fc6b20b77c26a571395f584efdce8cfb4f7.tar.xz
all: implement client API for Clearing Inquiry
The ClearingInquiry method is used to initiate the transfer for Clearing using LLG, RTGS, H2H, or BI-FAST.
-rw-r--r--clearing_inquiry.go41
-rw-r--r--clearing_inquiry_response.go11
-rw-r--r--client.go47
-rw-r--r--client_options.go10
-rw-r--r--client_test.go35
-rw-r--r--duitku.go28
-rw-r--r--testdata/disbursement/clearing_inquiry_test.txt28
7 files changed, 199 insertions, 1 deletions
diff --git a/clearing_inquiry.go b/clearing_inquiry.go
new file mode 100644
index 0000000..6bc7112
--- /dev/null
+++ b/clearing_inquiry.go
@@ -0,0 +1,41 @@
+package duitku
+
+import (
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+ "time"
+)
+
+// ClearingInquiry contains request to initiate transfer from merchant to
+// customer's bank account using [Clearing type].
+//
+// For Signature it use the following formula:
+//
+// SHA256(email + timestamp + bankCode + type + bankAccount + amountTransfer
+// + purpose + apiKey).
+//
+// [Clearing type]: // https://docs.duitku.com/disbursement/en/#clearing-inquiry-request
+type ClearingInquiry struct {
+ // 9 digits customer reference number.
+ CustRefNumber string `json:"custRefNumber"`
+
+ // Type of clearing: LLG, RTGS, H2H, or BIFAST.
+ Type string `json:"type"`
+
+ RtolInquiry
+}
+
+func (inq *ClearingInquiry) sign(opts ClientOptions) {
+ inq.UserID = opts.userID
+ inq.Email = opts.Email
+ inq.Timestamp = time.Now().UnixMilli()
+
+ var (
+ plain = fmt.Sprintf(`%s%d%s%s%s%d%s%s`, inq.Email,
+ inq.Timestamp, inq.BankCode, inq.Type,
+ inq.BankAccount, inq.Amount, inq.Purpose, opts.ApiKey)
+ plainHash [sha256.Size]byte = sha256.Sum256([]byte(plain))
+ )
+ inq.Signature = hex.EncodeToString(plainHash[:])
+}
diff --git a/clearing_inquiry_response.go b/clearing_inquiry_response.go
new file mode 100644
index 0000000..0fa6d98
--- /dev/null
+++ b/clearing_inquiry_response.go
@@ -0,0 +1,11 @@
+package duitku
+
+// ClearingInquiryResponse contains response from calling [Clearing Inquiry
+// request].
+//
+// [Clearing Inquiry request]: https://docs.duitku.com/disbursement/en/#clearing-inquiry-request
+type ClearingInquiryResponse struct {
+ RtolInquiryResponse
+
+ Type string `json:"type"`
+}
diff --git a/client.go b/client.go
index 789f8fb..47cc88d 100644
--- a/client.go
+++ b/client.go
@@ -26,6 +26,13 @@ const (
PathDisbursementTransfer = `/disbursement/transfer`
PathDisbursementTransferSandbox = `/disbursement/transfersandbox` // Used for testing.
+
+ // Paths for Clearing.
+ PathDisbursementInquiryClearing = `/disbursement/inquiryclearing`
+ PathDisbursementInquiryClearingSandbox = `/disbursement/inquiryclearingsandbox` // Used for testing.
+
+ PathDisbursementTransferClearing = `/disbursement/transferclearing`
+ PathDisbursementTransferClearingSandbox = `/disbursement/transferclearingsandbox` // Used for testing.
)
type Client struct {
@@ -85,6 +92,46 @@ func (cl *Client) CheckBalance() (bal *Balance, err error) {
return bal, nil
}
+// ClearingInquiry initiate the transfer for Clearing using LLG, RTGS, H2H, or
+// BI-FAST.
+func (cl *Client) ClearingInquiry(req ClearingInquiry) (res *ClearingInquiryResponse, err error) {
+ var (
+ logp = `ClearingInquiry`
+ path = PathDisbursementInquiryClearing
+
+ httpRes *http.Response
+ resBody []byte
+ )
+
+ req.sign(cl.opts)
+
+ // Since the path is different in test environment, we check the host
+ // here to set it.
+ if cl.opts.host != hostLive {
+ path = PathDisbursementInquiryClearingSandbox
+ }
+
+ httpRes, resBody, err = cl.PostJSON(path, nil, req)
+ if err != nil {
+ return nil, fmt.Errorf(`%s: %w`, logp, err)
+ }
+ if httpRes.StatusCode >= 500 {
+ return nil, fmt.Errorf(`%s: %s`, logp, httpRes.Status)
+ }
+
+ fmt.Printf(`%s: resBody: %s\n`, logp, resBody)
+
+ 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
+}
+
// tListBank fetch list of banks for disbursement.
func (cl *Client) ListBank() (banks []Bank, err error) {
var (
diff --git a/client_options.go b/client_options.go
index 5ed7d2c..4915b9c 100644
--- a/client_options.go
+++ b/client_options.go
@@ -6,6 +6,7 @@ package duitku
import (
"fmt"
"net/url"
+ "strconv"
)
// ClientOptions configuration for HTTP client.
@@ -17,6 +18,9 @@ type ClientOptions struct {
// The hostname extracted from ServerUrl.
host string
+
+ // The UserID converted to int64.
+ userID int64
}
// validate each field values.
@@ -34,11 +38,17 @@ func (opts *ClientOptions) validate() (err error) {
if len(opts.UserID) == 0 {
return fmt.Errorf(`invalid or empty UserID: %s`, opts.UserID)
}
+ opts.userID, err = strconv.ParseInt(opts.UserID, 10, 64)
+ if err != nil {
+ 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 2e2fd8f..bcb5c3d 100644
--- a/client_test.go
+++ b/client_test.go
@@ -44,7 +44,40 @@ func TestClient_CheckBalance(t *testing.T) {
test.Assert(t, `CheckBalance`, string(exp), string(got))
}
-func TestClient_RtolInquiry_live(t *testing.T) {
+func TestClient_ClearingInquiry_sandbox(t *testing.T) {
+ t.Skip(`This test require external call to server`)
+
+ var (
+ inquiryReq ClearingInquiry
+ err error
+ tdata *test.Data
+ inquiryRes *ClearingInquiryResponse
+ )
+
+ tdata, err = test.LoadData(`testdata/disbursement/clearing_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.ClearingInquiry(inquiryReq)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // We cannot compare the response, because for each call to server
+ // it will return different DisburseID.
+
+ t.Logf(`inquiryRes: %+v`, inquiryRes)
+
+ test.Assert(t, `AccountName`, `Test Account`, inquiryRes.AccountName)
+}
+
+func TestClient_RtolInquiry_sandbox(t *testing.T) {
t.Skip(`This test require external call to server`)
var (
diff --git a/duitku.go b/duitku.go
index 9a16fa6..7403609 100644
--- a/duitku.go
+++ b/duitku.go
@@ -12,3 +12,31 @@ const (
hostLive = `passport.duitku.com`
)
+
+const (
+ // LLG (Lalu Lintas Giro) is interbank transfer that cover more than
+ // 130 bank in Indonesia.
+ // The maximal amount transfer is IDR 500.000.000.
+ // Transfer process follows the BI (Bank Indonesia) schedule, which is
+ // 8.00-15.00 on business days.
+ ClearingTypeLLG = `LLG`
+
+ // RTGS (Real Time Gross Settlement) is interbank transfer that cover
+ // more than 130 bank in Indonesia.
+ // The minimal amount transfer is IDR 100.000.000.
+ // Transfer process follows the BI (Bank Indonesia) schedule, which is
+ // 8.00-15.00 on business days.
+ ClearingTypeRTGS = `RTGS`
+
+ // H2H (Bank Host to Host) Duitku Host to Host connection to bank, to
+ // ensure direct connection and better reliability.
+ // Currently only support 4 Major banks in Indonesia (BNI, BRI,
+ // Mandiri, Permata).
+ // Transfer schedule follows the schedule of each bank.
+ ClearingTypeH2H = `H2H`
+
+ // BIFAST (Bank Indonesia Fast Payment) National retail payments that
+ // can facilitate retail payments in real-time, safe, efficient, more
+ // affordable service fees and available at any time (24/7).
+ ClearingTypeBIFAST = `BIFAST`
+)
diff --git a/testdata/disbursement/clearing_inquiry_test.txt b/testdata/disbursement/clearing_inquiry_test.txt
new file mode 100644
index 0000000..63581e7
--- /dev/null
+++ b/testdata/disbursement/clearing_inquiry_test.txt
@@ -0,0 +1,28 @@
+>>> request.json
+{
+ "userId": 3551,
+ "email": "test@chakratechnology.com",
+ "amountTransfer": 10000,
+ "bankCode": "014",
+ "bankAccount": "8760673466",
+ "purpose": "Test Clearing Inquiry with duitku.",
+ "timestamp": 1506486841000,
+ "custRefNumber": "12345789",
+ "senderId": 123456789,
+ "senderName": "John Doe",
+ "type": "LLG"
+}
+
+<<< response.json
+{
+ "email": "test@chakratechnology.com",
+ "bankCode": "014",
+ "bankAccount": "8760673466",
+ "amountTransfer": 10000,
+ "accountName": "Test Account",
+ "custRefNumber": "12345789",
+ "disburseId": 121012,
+ "type": "LLG",
+ "responseCode": "00",
+ "responseDesc": "Approved or completed successfully"
+}