diff options
| -rw-r--r-- | client.go | 59 | ||||
| -rw-r--r-- | client_test.go | 39 | ||||
| -rw-r--r-- | rtol_transfer.go | 92 | ||||
| -rw-r--r-- | rtol_transfer_response.go | 15 | ||||
| -rw-r--r-- | testdata/disbursement_rtol_transfer_test.txt | 16 |
5 files changed, 219 insertions, 2 deletions
@@ -15,13 +15,17 @@ import ( libhttp "github.com/shuLhan/share/lib/http" ) +// List of known and implemented HTTP API paths. const ( PathDisbursementListBank = `/disbursement/listBank` PathDisbursementCheckBalance = `/disbursement/checkBalance` - // Path for transfer online. + // Paths for transfer online. PathDisbursementInquiry = `/disbursement/inquiry` - PathDisbursementInquirySandbox = `/disbursement/inquirysandbox` // Used when server URL is sandbox (testing). + PathDisbursementInquirySandbox = `/disbursement/inquirysandbox` // Used for testing. + + PathDisbursementTransfer = `/disbursement/transfer` + PathDisbursementTransferSandbox = `/disbursement/transfersandbox` // Used for testing. ) type Client struct { @@ -182,3 +186,54 @@ func (cl *Client) RtolInquiry(req RtolInquiry) (res *RtolInquiryResponse, err er return res, nil } + +// RtolTransfer do the actual transfer to customer's bank account using the +// request and response from call to RtolInquiry. +// +// Transfer will be limited from 25 to 50 Million per transaction depending on +// the beneficiary bank account. +// +// Ref: https://docs.duitku.com/disbursement/en/#online-transfer-transfer-request +func (cl *Client) RtolTransfer(inquiryReq RtolInquiry, inquiryRes RtolInquiryResponse) (res *RtolTransferResponse, err error) { + var ( + logp = `RtolTransfer` + path = PathDisbursementTransfer + + req *rtolTransfer + 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 = PathDisbursementTransferSandbox + } + + req = newRtolTransfer(&inquiryReq, &inquiryRes) + + err = req.sign(cl.opts) + if err != nil { + return nil, fmt.Errorf(`%s: %w`, logp, err) + } + + 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) + } + + fmt.Printf(`%s: %s`, 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 +} diff --git a/client_test.go b/client_test.go index 09db153..e1fdc4d 100644 --- a/client_test.go +++ b/client_test.go @@ -77,6 +77,45 @@ func TestClient_RtolInquiry_live(t *testing.T) { test.Assert(t, `AccountName`, `Test Account`, inquiryRes.AccountName) } +func TestClient_RtolTransfer_sandbox(t *testing.T) { + t.Skip(`This test require external call to server`) + + var ( + err error + tdata *test.Data + inquiryReq *RtolInquiry + inquiryRes *RtolInquiryResponse + transferRes *RtolTransferResponse + ) + + tdata, err = test.LoadData(`testdata/disbursement_rtol_transfer_test.txt`) + if err != nil { + t.Fatal(err) + } + + err = json.Unmarshal(tdata.Input[`request_inquiry.json`], &inquiryReq) + if err != nil { + t.Fatal(err) + } + + inquiryRes, err = testClient.RtolInquiry(*inquiryReq) + if err != nil { + t.Fatal(err) + } + + transferRes, err = testClient.RtolTransfer(*inquiryReq, *inquiryRes) + 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(`RtolTransfer response: %+v`, transferRes) + + test.Assert(t, `AccountName`, `Test Account`, inquiryRes.AccountName) +} + func TestClient_DisbursementListBank(t *testing.T) { var ( tdata *test.Data diff --git a/rtol_transfer.go b/rtol_transfer.go new file mode 100644 index 0000000..db6eabc --- /dev/null +++ b/rtol_transfer.go @@ -0,0 +1,92 @@ +package duitku + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "fmt" + "strconv" + "time" +) + +// rtolTransfer containts request to transfer amount from merchant to +// customer's bank account, using the previous data obtained from the inquiry +// process. +// +// The formula to generate Signature is SHA256(email + timestamp + bankCode + +// bankAccount + accountName + custRefNumber + amountTransfer + purpose + +// disburseId + secretKey). +// +// Ref: https://docs.duitku.com/disbursement/en/#online-transfer-transfer-request +type rtolTransfer struct { + // Bank Account owner, obtained after getting a response from the + // inquiry process. + AccountName string `json:"accountName"` + + // Destination Bank Code sent when inquiry process. + BankCode string `json:"bankCode"` + + // Destination account number sent when inquiry procces. + BankAccount string `json:"bankAccount"` + + // Customer reference number, obtained after getting a response from + // the inquiry process. + CustRefNumber string `json:"custRefNumber"` + + // Description of transfer purpose. + Purpose string `json:"purpose"` + + request + + // Disbursement transfer amount. + Amount int64 `json:"amountTransfer"` + + // Disbursement Id provided by Duitku, obtained after getting a + // response from the inquiry process. + DisburseID int64 `json:"disburseId"` +} + +// newRtolTransfer create new rtolTransfer request from request and response +// of RtolInquiry. +func newRtolTransfer(inquiryReq *RtolInquiry, inquiryRes *RtolInquiryResponse) (req *rtolTransfer) { + req = &rtolTransfer{ + Amount: inquiryReq.Amount, + Purpose: inquiryReq.Purpose, + + AccountName: inquiryRes.AccountName, + BankCode: inquiryRes.BankCode, + BankAccount: inquiryRes.BankAccount, + CustRefNumber: inquiryRes.CustRefNumber, + DisburseID: inquiryRes.DisburseID, + } + return req +} + +func (req *rtolTransfer) sign(opts ClientOptions) (err error) { + var ( + logp = `sign` + now = time.Now() + + bb bytes.Buffer + plainHash [sha256.Size]byte + ) + + req.UserID, err = strconv.ParseInt(opts.UserID, 10, 64) + if err != nil { + return fmt.Errorf(`%s: %s`, logp, err) + } + + req.Email = opts.Email + req.Timestamp = now.UnixMilli() + + fmt.Fprintf(&bb, `%s%d%s%s%s%s%d%s%d%s`, req.Email, req.Timestamp, + req.BankCode, req.BankAccount, req.AccountName, + req.CustRefNumber, req.Amount, req.Purpose, + req.DisburseID, opts.ApiKey) + + plainHash = sha256.Sum256(bb.Bytes()) + + req.Signature = hex.EncodeToString(plainHash[:]) + + return nil +} diff --git a/rtol_transfer_response.go b/rtol_transfer_response.go new file mode 100644 index 0000000..3ce6eab --- /dev/null +++ b/rtol_transfer_response.go @@ -0,0 +1,15 @@ +package duitku + +import "github.com/shuLhan/share/lib/math/big" + +// RtolTransferResponse contains response from online transfer. +type RtolTransferResponse struct { + Email string `json:"email"` + BankCode string `json:"bankCode"` + BankAccount string `json:"bankAccount"` + AccountName string `json:"accountName"` + CustRefNumber string `json:"custRefNumber"` + Amount *big.Rat `json:"amountTransfer"` + + response +} diff --git a/testdata/disbursement_rtol_transfer_test.txt b/testdata/disbursement_rtol_transfer_test.txt new file mode 100644 index 0000000..f9a0d82 --- /dev/null +++ b/testdata/disbursement_rtol_transfer_test.txt @@ -0,0 +1,16 @@ +Test disbursement online transfer. + +Ref: https://docs.duitku.com/disbursement/en/#transfer-online + +>>> request_inquiry.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" +} |
