├── .gitignore ├── LICENSE ├── README.md ├── bank.go ├── coreapi ├── client.go ├── client_test.go ├── clientsubscription.go ├── clientsubscription_test.go ├── clienttokenization.go ├── clienttokenization_test.go ├── clienttransaction.go ├── clienttransaction_test.go ├── paymenttype.go ├── request.go └── response.go ├── error.go ├── error_test.go ├── example ├── README.md ├── mockup.go └── simple │ ├── coreapi-card-3ds │ ├── main.go │ └── views │ │ └── index.html │ ├── coreapi │ └── sample.go │ ├── iris │ └── sample.go │ ├── snap │ └── sample.go │ ├── subscriptions │ └── sample.go │ ├── tokenization │ └── sample.go │ └── transaction │ └── sample.go ├── go.mod ├── go.sum ├── httpclient.go ├── iris ├── client.go ├── client_test.go ├── request.go └── response.go ├── log.go ├── maintaining.md ├── midtrans.go ├── request.go └── snap ├── client.go ├── client_test.go ├── paymenttype.go ├── request.go └── response.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Goland 9 | .idea 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Midtrans 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Midtrans Go Library 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/midtrans/midtrans-go)](https://goreportcard.com/report/github.com/midtrans/midtrans-go) 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | 5 | Midtrans :heart: Go ! 6 | 7 | Go is a very modern, terse, and combine aspect of dynamic and static typing that in a way very well suited for web development, among other things. 8 | Its small memory footprint is also an advantage of itself. This module will help you use Midtrans product's REST APIs in Go. 9 | 10 | ## 1. Installation 11 | ### 1.1 Using Go Module 12 | Run this command on your project to initialize Go mod (if you haven't): 13 | ```go 14 | go mod init 15 | ``` 16 | then reference midtrans-go in your project file with `import`: 17 | ```go 18 | import ( 19 | "github.com/midtrans/midtrans-go" 20 | "github.com/midtrans/midtrans-go/coreapi" 21 | "github.com/midtrans/midtrans-go/snap" 22 | "github.com/midtrans/midtrans-go/iris" 23 | ) 24 | ``` 25 | 26 | ### 1.2 Using go get 27 | Also, the alternative way you can use `go get` the package into your project 28 | ```go 29 | go get -u github.com/midtrans/midtrans-go 30 | ``` 31 | ## 2. Usage 32 | There is a type named `Client` (`coreapi.Client`, `snap.Client`, `iris.Client`) that should be instantiated through 33 | function `New` which holds any possible setting to the library. Any activity (charge, approve, etc) is done in the client level. 34 | 35 | ### 2.1 Choose Product/Method 36 | We have [3 different products](https://beta-docs.midtrans.com/) that you can use: 37 | - [Snap](#22A-snap) - Customizable payment popup will appear on **your web/app** (no redirection). [doc ref](https://snap-docs.midtrans.com/) 38 | - [Snap Redirect](#22B-snap-redirect) - Customer need to be redirected to payment url **hosted by midtrans**. [doc ref](https://snap-docs.midtrans.com/) 39 | - [Core API (VT-Direct)](#22C-core-api-vt-direct) - Basic backend implementation, you can customize the frontend embedded on **your web/app** as you like (no redirection). [doc ref](https://api-docs.midtrans.com/) 40 | - [Iris Disbursement](#22D-iris-api) - Iris is Midtrans’ cash management solution that allows you to disburse payments to any bank accounts in Indonesia securely and easily. [doc ref](https://iris-docs.midtrans.com/) 41 | 42 | To learn more and understand each of the product's quick overview you can visit https://docs.midtrans.com. 43 | 44 | 45 | ### 2.2 Client Initialization and Configuration 46 | Get your client key and server key from [Midtrans Dashboard](https://dashboard.midtrans.com) 47 | 48 | Create API client object, You can also check the [project's implementation](example/simple) for more examples. Please proceed there for more detail on how to run the example. 49 | 50 | #### 2.2.1 Using global config 51 | Set a config with globally, (except for iris api) 52 | 53 | ```go 54 | midtrans.ServerKey = "YOUR-SERVER-KEY" 55 | midtrans.Environment = midtrans.Sandbox 56 | ``` 57 | 58 | #### 2.2.2 Using Client 59 | ```go 60 | //Initiate client for Midtrans CoreAPI 61 | var c = coreapi.Client 62 | c.New("YOUR-SERVER-KEY", midtrans.Sandbox) 63 | 64 | //Initiate client for Midtrans Snap 65 | var s = snap.Client 66 | s.New("YOUR-SERVER-KEY", midtrans.Sandbox) 67 | 68 | //Initiate client for Iris disbursement 69 | var i = iris.Client 70 | i.New("IRIS-API-KEY", midtrans.Sandbox) 71 | ``` 72 | ### 2.3 Snap 73 | Snap is Midtrans existing tool to help merchant charge customers using a mobile-friendly, in-page, 74 | no-redirect checkout facilities. [Using snap is simple](https://docs.midtrans.com/en/snap/overview). 75 | 76 | Available methods for Snap 77 | ```go 78 | // CreateTransaction : Do `/transactions` API request to SNAP API to get Snap token and redirect url with `snap.Request` 79 | func CreateTransaction(req *snap.Request) (*Response, *midtrans.Error) 80 | 81 | // CreateTransactionToken : Do `/transactions` API request to SNAP API to get Snap token with `snap.Request` 82 | func CreateTransactionToken(req *snap.Request) (string, *midtrans.Error) 83 | 84 | // CreateTransactionUrl : Do `/transactions` API request to SNAP API to get Snap redirect url with `snap.Request` 85 | func CreateTransactionUrl(req *snap.Request) (string, *midtrans.Error) 86 | 87 | // CreateTransactionWithMap : Do `/transactions` API request to SNAP API to get Snap token and redirect url with Map request 88 | func CreateTransactionWithMap(req *snap.RequestParamWithMap) (ResponseWithMap, *midtrans.Error) 89 | 90 | // CreateTransactionTokenWithMap : Do `/transactions` API request to SNAP API to get Snap token with Map request 91 | func CreateTransactionTokenWithMap(req *snap.RequestParamWithMap) (string, *midtrans.Error) 92 | 93 | // CreateTransactionUrlWithMap : Do `/transactions` API request to SNAP API to get Snap redirect url with Map request 94 | func CreateTransactionUrlWithMap(req *snap.RequestParamWithMap) (string, *midtrans.Error) 95 | ``` 96 | Snap usage example, create transaction with minimum Snap parameters (choose **one** of alternatives below): 97 | #### 2.3.1 Using global Config & static function 98 | Sample usage if you prefer Midtrans global configuration & using static function. Useful if you only use 1 merchant account API key, and keep the code short. 99 | ```go 100 | // 1. Set you ServerKey with globally 101 | midtrans.ServerKey = "YOUR-SERVER-KEY" 102 | midtrans.Environment = midtrans.Sandbox 103 | 104 | // 2. Initiate Snap request 105 | req := & snap.RequestParam{ 106 | TransactionDetails: midtrans.TransactionDetails{ 107 | OrderID: "YOUR-ORDER-ID-12345", 108 | GrossAmt: 100000, 109 | }, 110 | CreditCard: &snap.CreditCardDetails{ 111 | Secure: true, 112 | }, 113 | } 114 | 115 | // 3. Request create Snap transaction to Midtrans 116 | snapResp, _ := CreateTransaction(req) 117 | fmt.Println("Response :", snapResp) 118 | ``` 119 | #### 2.3.2 Using Client 120 | Sample usage if you prefer to use client instance & config. Useful if you plan to use multiple merchant account API keys, want to have multiple client instances, or prefer the code to be object-oriented. 121 | 122 | ```go 123 | // 1. Initiate Snap client 124 | var s = snap.Client 125 | s.New("YOUR-SERVER-KEY", midtrans.Sandbox) 126 | 127 | // 2. Initiate Snap request 128 | req := & snap.RequestParam{ 129 | TransactionDetails: midtrans.TransactionDetails{ 130 | OrderID: "YOUR-ORDER-ID-12345", 131 | GrossAmt: 100000, 132 | }, 133 | CreditCard: &snap.CreditCardDetails{ 134 | Secure: true, 135 | }, 136 | } 137 | 138 | // 3. Request create Snap transaction to Midtrans 139 | snapResp, _ := s.CreateTransaction(req) 140 | fmt.Println("Response :", snapResp) 141 | ``` 142 | 143 | On the frontend side (on the HTML payment page), you will [need to include snap.js library and implement the payment page](https://docs.midtrans.com/en/snap/integration-guide?id=_2-displaying-snap-payment-page-on-frontend). 144 | 145 | Sample HTML payment page implementation: 146 | ```html 147 | 148 | 149 | 150 |
JSON result will appear here after payment:
151 | 152 | 153 | 154 | 173 | 174 | 175 | ``` 176 | 177 | You may want to override those `onSuccess`, `onPending` and `onError` functions to implement the behaviour that you want on each respective event. 178 | 179 | Then implement Backend Notification Handler, [Refer to this section](README.md#26-handle-http-notification) 180 | 181 | Alternativelly, more complete Snap parameter: 182 | 183 | ```go 184 | func GenerateSnapReq() *snap.Request { 185 | // Initiate Customer address 186 | custAddress := &midtrans.CustomerAddress{ 187 | FName: "John", 188 | LName: "Doe", 189 | Phone: "081234567890", 190 | Address: "Baker Street 97th", 191 | City: "Jakarta", 192 | Postcode: "16000", 193 | CountryCode: "IDN", 194 | } 195 | 196 | // Initiate Snap Request 197 | snapReq := &snap.Request{ 198 | TransactionDetails: midtrans.TransactionDetails{ 199 | OrderID: "YOUR-UNIQUE-ORDER-ID-1234", 200 | GrossAmt: 200000, 201 | }, 202 | CreditCard: &snap.CreditCardDetails{ 203 | Secure: true, 204 | }, 205 | CustomerDetail: &midtrans.CustomerDetails{ 206 | FName: "John", 207 | LName: "Doe", 208 | Email: "john@doe.com", 209 | Phone: "081234567890", 210 | BillAddr: custAddress, 211 | ShipAddr: custAddress, 212 | }, 213 | Items: &[]midtrans.ItemDetails{ 214 | midtrans.ItemDetails{ 215 | ID: "ITEM1", 216 | Price: 200000, 217 | Qty: 1, 218 | Name: "Someitem", 219 | }, 220 | }, 221 | } 222 | 223 | return snapReq 224 | } 225 | ``` 226 | 227 | >**INFO:** 228 | > When using client, you can set config options like `SetIdempotencyKey`, `SetContext`, `SetPaymentOverrideNotif`, etc 229 | > from Options object on the client, [check the usage detail on how to configure options here](README.md#3-advance-usage) 230 | 231 | #### Alternative, perform Core API Charge with Map type 232 | Snap client have `...WithMap` function, which is useful if you want to send custom JSON payload that the type/struct is not defined in this module. Refer to file `sample.go` in folder [Snap API simple sample](example/simple/snap/sample.go). 233 | 234 | ### 2.4 CoreApi 235 | Available methods for `CoreApi` 236 | ```go 237 | // ChargeTransaction : Do `/charge` API request to Midtrans Core API return `coreapi.Response` with `coreapi.ChargeReq` 238 | func ChargeTransaction(req *ChargeReq) (*Response, *midtrans.Error) 239 | 240 | // ChargeTransactionWithMap : Do `/charge` API request to Midtrans Core API return RAW MAP with Map as 241 | func ChargeTransactionWithMap(req *ChargeReqWithMap) (ResponseWithMap, *midtrans.Error) 242 | 243 | // CardToken : Do `/token` API request to Midtrans Core API return `coreapi.Response`, 244 | func CardToken(cardNumber string, expMonth int, expYear int, cvv string) (*CardTokenResponse, *midtrans.Error) 245 | 246 | // RegisterCard : Do `/card/register` API request to Midtrans Core API return `coreapi.Response`, 247 | func RegisterCard(cardNumber string, expMonth int, expYear int, cvv string) (*CardRegisterResponse, *midtrans.Error) 248 | 249 | // CardPointInquiry : Do `/point_inquiry/{tokenId}` API request to Midtrans Core API return `coreapi.Response`, 250 | func CardPointInquiry(cardToken string) (*CardTokenResponse, *midtrans.Error) 251 | 252 | // GetBIN : Do `/v1/bins/{bin}` API request to Midtrans Core API return `coreapi.BinResponse`, 253 | func GetBIN(binNumber string) (*BinResponse, *midtrans.Error) 254 | 255 | // CheckTransaction : Do `/{orderId}/status` API request to Midtrans Core API return `coreapi.Response`, 256 | func CheckTransaction(param string) (*Response, *midtrans.Error) 257 | 258 | // ApproveTransaction : Do `/{orderId}/approve` API request to Midtrans Core API return `coreapi.Response`, 259 | func ApproveTransaction(param string) (*Response, *midtrans.Error) 260 | 261 | // DenyTransaction : Do `/{orderId}/deny` API request to Midtrans Core API return `coreapi.Response`, 262 | func DenyTransaction(param string) (*Response, *midtrans.Error) 263 | 264 | // CancelTransaction : Do `/{orderId}/cancel` API request to Midtrans Core API return `coreapi.Response`, 265 | func CancelTransaction(param string) (*Response, *midtrans.Error) 266 | 267 | // ExpireTransaction : Do `/{orderId}/expire` API request to Midtrans Core API return `coreapi.Response`, 268 | func ExpireTransaction(param string) (*Response, *midtrans.Error) 269 | 270 | // RefundTransaction : Do `/{orderId}/refund` API request to Midtrans Core API return `coreapi.Response`, 271 | // with `coreapi.RefundReq` as body parameter, will be converted to JSON, 272 | func RefundTransaction(param string, req *RefundReq) (*Response, *midtrans.Error) 273 | 274 | // DirectRefundTransaction : Do `/{orderId}/refund/online/direct` API request to Midtrans Core API return `coreapi.Response`, 275 | // with `coreapi.RefundReq` as body parameter, will be converted to JSON, 276 | func DirectRefundTransaction(param string, req *RefundReq) (*Response, *midtrans.Error) 277 | 278 | // CaptureTransaction : Do `/{orderId}/capture` API request to Midtrans Core API return `coreapi.Response`, 279 | // with `coreapi.CaptureReq` as body parameter, will be converted to JSON, 280 | func CaptureTransaction(req *CaptureReq) (*Response, *midtrans.Error) 281 | 282 | // GetStatusB2B : Do `/{orderId}/status/b2b` API request to Midtrans Core API return `coreapi.Response`, 283 | func GetStatusB2B(param string) (*Response, *midtrans.Error) 284 | ``` 285 | #### 2.4.1 Using global Config & static function 286 | Sample usage if you prefer Midtrans global configuration & using static function. Useful if you only use 1 merchant account API key, and keep the code short. 287 | 288 | ```go 289 | // 1. Set you ServerKey with globally 290 | midtrans.ServerKey = "YOUR-SERVER-KEY" 291 | midtrans.Environment = midtrans.Sandbox 292 | 293 | // 2. Initiate charge request 294 | chargeReq := &coreapi.ChargeReq{ 295 | PaymentType: coreapi.PaymentTypeCreditCard, 296 | TransactionDetails: midtrans.TransactionDetails{ 297 | OrderID: "12345", 298 | GrossAmt: 200000, 299 | }, 300 | CreditCard: &coreapi.CreditCardDetails{ 301 | TokenID: "YOUR-CC-TOKEN", 302 | Authentication: true, 303 | }, 304 | Items: &[]midtrans.ItemDetails{ 305 | { 306 | ID: "ITEM1", 307 | Price: 200000, 308 | Qty: 1, 309 | Name: "Someitem", 310 | }, 311 | }, 312 | } 313 | 314 | // 3. Request to Midtrans using global config 315 | coreApiRes, _ := coreapi.ChargeTransaction(chargeReq) 316 | fmt.Println("Response :", coreApiRes) 317 | ``` 318 | #### 2.4.2 Using Client 319 | Sample usage if you prefer to use client instance & config. Useful if you plan to use multiple merchant account API keys, want to have multiple client instances, or prefer the code to be object-oriented. 320 | 321 | ```go 322 | // 1. Initiate coreapi client 323 | c := coreapi.Client{} 324 | c.New("YOUR-SERVER-KEY", midtrans.Sandbox) 325 | 326 | // 2. Initiate charge request 327 | chargeReq := &coreapi.ChargeReq{ 328 | PaymentType: midtrans.SourceCreditCard, 329 | TransactionDetails: midtrans.TransactionDetails{ 330 | OrderID: "12345", 331 | GrossAmt: 200000, 332 | }, 333 | CreditCard: &coreapi.CreditCardDetails{ 334 | TokenID: "YOUR-CC-TOKEN", 335 | Authentication: true, 336 | }, 337 | Items: &[]midtrans.ItemDetail{ 338 | coreapi.ItemDetail{ 339 | ID: "ITEM1", 340 | Price: 200000, 341 | Qty: 1, 342 | Name: "Someitem", 343 | }, 344 | }, 345 | } 346 | 347 | // 3. Request to Midtrans 348 | coreApiRes, _ := c.ChargeTransaction(chargeReq) 349 | fmt.Println("Response :", coreApiRes) 350 | ``` 351 | >**INFO:** 352 | > When using client, you can set config options like `SetIdempotencyKey`, `SetContext`, `SetPaymentOverrideNotif`, etc 353 | > from Options object on the client, [check the usage detail on how to configure options here](README.md#3-advance-usage) 354 | 355 | #### Alternative, perform Core API Charge with Map type 356 | CoreApi client have `ChargeTransactionWithMap` function, which is useful if you want to send custom JSON payload that the type/struct is not defined in this module. Refer to file `sample.go` in folder [Core API simple sample](example/simple/coreapi/sample.go). 357 | 358 | ### 2.5 Iris Client 359 | Iris is Midtrans cash management solution that allows you to disburse payments to any supported bank accounts securely and easily. Iris connects to the banks’ hosts to enable seamless transfer using integrated APIs. 360 | Available methods for `Iris` 361 | ```go 362 | // CreateBeneficiaries : to perform create a new beneficiary information for quick access on the payout page in Iris Portal. 363 | func (c Client) CreateBeneficiaries(req Beneficiaries) (*BeneficiariesResponse, *midtrans.Error) 364 | 365 | // UpdateBeneficiaries : to update an existing beneficiary identified by its alias_name. 366 | func (c Client) UpdateBeneficiaries(aliasName string, req Beneficiaries) (*BeneficiariesResponse, *midtrans.Error) 367 | 368 | // GetBeneficiaries : This method to fetch list of all beneficiaries saved in Iris Portal. 369 | func (c Client) GetBeneficiaries() ([]Beneficiaries, *midtrans.Error) 370 | 371 | // CreatePayout : This method for Creator to create a payout. It can be used for single payout and also multiple payouts. 372 | func (c Client) CreatePayout(req CreatePayoutReq) (*CreatePayoutResponse, *midtrans.Error) 373 | 374 | // ApprovePayout : this method for Apporver to approve multiple payout request. 375 | func (c Client) ApprovePayout(req ApprovePayoutReq) (*ApprovePayoutResponse, *midtrans.Error) 376 | 377 | // RejectPayout : This method for Apporver to reject multiple payout request. 378 | func (c Client) RejectPayout(req RejectPayoutReq) (*RejectPayoutResponse, *midtrans.Error) 379 | 380 | // GetPayoutDetails : Get details of a single payout. 381 | func (c Client) GetPayoutDetails(referenceNo string) (*PayoutDetailResponse, *midtrans.Error) 382 | 383 | // GetTransactionHistory : Returns all the payout details for specific dates 384 | func (c Client) GetTransactionHistory(fromDate string, toDate string) ([]TransactionHistoryResponse, *midtrans.Error) 385 | 386 | // GetTopUpChannels : Provide top up information channel for Aggregator Partner 387 | func (c Client) GetTopUpChannels() ([]TopUpAccountResponse, *midtrans.Error) 388 | 389 | // GetBalance : For Aggregator Partner, you need to top up to Iris’ bank account. Every partner have their own balance in Iris’ 390 | // bank account. Use this API is to get current balance information. 391 | func (c Client) GetBalance() (*BalanceResponse, *midtrans.Error) 392 | 393 | // GetListBankAccount : Show list of registered bank accounts for facilitator partner 394 | func (c Client) GetListBankAccount() ([]BankAccountResponse, *midtrans.Error) 395 | 396 | // GetFacilitatorBalance : For Facilitator Partner, use this API is to get current balance information of your registered bank account. 397 | func (c Client) GetFacilitatorBalance(accountId string) (*BalanceResponse, *midtrans.Error) 398 | 399 | // GetBeneficiaryBanks : Show list of supported banks in IRIS. 400 | func (c Client) GetBeneficiaryBanks() (*ListBeneficiaryBankResponse, *midtrans.Error) 401 | 402 | // ValidateBankAccount : Check if an account is valid, if valid return account information. 403 | func (c Client) ValidateBankAccount(bankName string, accountNo string) (*BankAccountDetailResponse, *midtrans.Error) 404 | ``` 405 | 406 | >Note: `IrisApiKey` will be used in `Iris.Client`'s the API Key can be found in Iris Dashboard. The API Key is different to Midtrans' payment gateway account's API key. 407 | ```go 408 | var i iris.Client 409 | i.New("YOUR-IRIS-API-KEY", midtrans.Sandbox) 410 | 411 | res, _ := i.GetBeneficiaryBanks() 412 | fmt.Println("Response: ", res) 413 | ``` 414 | 415 | ### 2.6 Handle HTTP Notification 416 | Create separated web endpoint (notification url) to receive HTTP POST notification callback/webhook. 417 | HTTP notification will be sent whenever transaction status is changed. 418 | Example also available in `sample.go` in folder [example/simple/coreapi](example/simple/coreapi/sample.go) 419 | 420 | ```go 421 | func notification(w http.ResponseWriter, r *http.Request) { 422 | // 1. Initialize empty map 423 | var notificationPayload map[string]interface{} 424 | 425 | // 2. Parse JSON request body and use it to set json to payload 426 | err := json.NewDecoder(r.Body).Decode(¬ificationPayload) 427 | if err != nil { 428 | // do something on error when decode 429 | return 430 | } 431 | // 3. Get order-id from payload 432 | orderId, exists := notificationPayload["order_id"].(string) 433 | if !exists { 434 | // do something when key `order_id` not found 435 | return 436 | } 437 | 438 | // 4. Check transaction to Midtrans with param orderId 439 | transactionStatusResp, e := c.CheckTransaction(orderId) 440 | if e != nil { 441 | http.Error(w, e.GetMessage(), http.StatusInternalServerError) 442 | return 443 | } else { 444 | if transactionStatusResp != nil { 445 | // 5. Do set transaction status based on response from check transaction status 446 | if transactionStatusResp.TransactionStatus == "capture" { 447 | if transactionStatusResp.FraudStatus == "challenge" { 448 | // TODO set transaction status on your database to 'challenge' 449 | // e.g: 'Payment status challenged. Please take action on your Merchant Administration Portal 450 | } else if transactionStatusResp.FraudStatus == "accept" { 451 | // TODO set transaction status on your database to 'success' 452 | } 453 | } else if transactionStatusResp.TransactionStatus == "settlement" { 454 | // TODO set transaction status on your databaase to 'success' 455 | } else if transactionStatusResp.TransactionStatus == "deny" { 456 | // TODO you can ignore 'deny', because most of the time it allows payment retries 457 | // and later can become success 458 | } else if transactionStatusResp.TransactionStatus == "cancel" || transactionStatusResp.TransactionStatus == "expire" { 459 | // TODO set transaction status on your databaase to 'failure' 460 | } else if transactionStatusResp.TransactionStatus == "pending" { 461 | // TODO set transaction status on your databaase to 'pending' / waiting payment 462 | } 463 | } 464 | } 465 | w.Header().Set("Content-Type", "application/json") 466 | w.Write([]byte("ok")) 467 | } 468 | ``` 469 | ### 2.7 Transaction Action 470 | Other functions related to actions that can be performed to transaction(s). Also available as examples [here](example/simple/transaction/sample.go) 471 | #### Get Status 472 | ```go 473 | // get status of transaction that already recorded on midtrans (already `charge`-ed) 474 | res, _ := c.CheckTransaction("YOUR_ORDER_ID OR TRANSACTION_ID") 475 | if res != nil { 476 | // do something to `res` object 477 | } 478 | ``` 479 | #### Get Status B2B 480 | ```go 481 | // get transaction status of VA b2b transaction 482 | res, _ := c.GetStatusB2B("YOUR_ORDER_ID OR TRANSACTION_ID") 483 | if res != nil { 484 | // do something to `res` object 485 | } 486 | ``` 487 | #### Approve Transaction 488 | ```go 489 | // approve a credit card transaction with `challenge` fraud status 490 | res, _ := c.ApproveTransaction("YOUR_ORDER_ID OR TRANSACTION_ID") 491 | if res != nil { 492 | // do something to `res` object 493 | } 494 | ``` 495 | #### Deny Transaction 496 | ```go 497 | // deny a credit card transaction with `challenge` fraud status 498 | res, _ := c.DenyTransaction("YOUR_ORDER_ID OR TRANSACTION_ID") 499 | if res != nil { 500 | // do something to `res` object 501 | } 502 | ``` 503 | #### Cancel Transaction 504 | ```go 505 | // cancel a credit card transaction or pending transaction 506 | res, _ := c.CancelTransaction("YOUR_ORDER_ID OR TRANSACTION_ID") 507 | if res != nil { 508 | // do something to `res` object 509 | } 510 | ``` 511 | #### Capture Transaction 512 | ```go 513 | // Capture an authorized transaction for card payment 514 | refundRequest := &coreapi.CaptureReq{ 515 | TransactionID: "TRANSACTION-ID", 516 | GrossAmt: 10000, 517 | } 518 | res, _ := c.CaptureTransaction(refundRequest) 519 | if res != nil { 520 | // do something to `res` object 521 | } 522 | ``` 523 | #### Expire Transaction 524 | ```go 525 | // expire a pending transaction 526 | res, _ := c.ExpireTransaction("YOUR_ORDER_ID OR TRANSACTION_ID") 527 | if res != nil { 528 | // do something to `res` object 529 | } 530 | ``` 531 | #### Refund Transaction 532 | ```go 533 | refundRequest := &coreapi.RefundReq{ 534 | Amount: 5000, 535 | Reason: "Item out of stock", 536 | } 537 | 538 | res, _ := c.RefundTransaction("YOUR_ORDER_ID OR TRANSACTION_ID", refundRequest) 539 | if res != nil { 540 | // do something to `res` object 541 | } 542 | ``` 543 | #### Refund Transaction with Direct Refund 544 | ```go 545 | refundRequest := &coreapi.RefundReq{ 546 | RefundKey: "order1-ref1", 547 | Amount: 5000, 548 | Reason: "Item out of stock", 549 | } 550 | 551 | res, _ := c.DirectRefundTransaction("YOUR_ORDER_ID OR TRANSACTION_ID", refundRequest) 552 | if res != nil { 553 | // do something to `res` object 554 | } 555 | ``` 556 | ## 3. Advance Usage 557 | ### 3.1 Override Notification Url 558 | Merchant can opt to change or add custom notification urls on every transaction. It can be achieved by adding additional HTTP headers into charge request. 559 | For Midtrans Payment, there are two headers we provide: 560 | 561 | 1. `X-Append-Notification`: to add new notification url(s) alongside the settings on dashboard 562 | 2. `X-Override-Notification`: to use new notification url(s) disregarding the settings on dashboard 563 | Both header can only receive up to maximum of **3 urls**. 564 | 565 | > **Note:** When both `SetPaymentAppendNotif` and `SetPaymentOverrideNotif` are used together then only `OverrideNotif` will be used. 566 | 567 | #### 3.1.1 Set Override/Append notification globally 568 | ```go 569 | // Set override or append for globally 570 | midtrans.SetPaymentAppendNotification("YOUR-APPEND-NOTIFICATION-ENDPOINT") 571 | midtrans.SetPaymentOverrideNotification("YOUR-OVERRID-NOTIFICATION-ENDPOINT") 572 | ``` 573 | #### 3.1.2 Set Override/Append notification via client options 574 | ```go 575 | // 1. Initiate Gateway 576 | var c = coreapi.Client 577 | c.New("YOUR-SERVER-KEY", midtrans.Sandbox) 578 | 579 | // 2. Set Payment Override or Append via gateway options for specific request 580 | c.Options.SetPaymentAppendNotification("YOUR-APPEND-NOTIFICATION-ENDPOINT") 581 | c.Options.SetPaymentOverrideNotification("YOUR-APPEND-NOTIFICATION-ENDPOINT") 582 | 583 | // 3. Then request to Midtrans API 584 | res, _ := c.ChargeRequest("YOUR-REQUEST") 585 | ``` 586 | Please see our documentation for [the details](https://api-docs.midtrans.com/#override-notification-url) about the feature 587 | 588 | ### 3.2 Request using go Context 589 | With Gateway options object you can set Go Context for each request by the net/http machinery, and is available with `SetContext()` method. 590 | ```go 591 | c.Options.SetContext(context.Background()) 592 | ``` 593 | 594 | ### 3.3 Log Configuration 595 | By default in `Sandbox` the log level will use `LogDebug` level, that outputs informational messages for debugging. In `Production` this module will only logs the error messages (`LogError` level), that outputs error message to `os.stderr`. 596 | You have option to change the default log level configuration with global variable `midtrans.DefaultLoggerLevel`: 597 | ```go 598 | midtrans.DefaultLoggerLevel = &midtrans.LoggerImplementation{LogLevel: midtrans.LogDebug} 599 | 600 | // Details Log Level 601 | // NoLogging : sets a logger to not show the messages 602 | // LogError : sets a logger to show error messages only. 603 | // LogInfo : sets a logger to show information messages 604 | // LogDebug : sets a logger to show informational messages for debugging 605 | ``` 606 | 607 | ### 3.4 Override HTTP Client timeout 608 | By default, timeout value for HTTP Client 80 seconds. But you can override the HTTP client default config from global variable `midtrans.DefaultGoHttpClient`: 609 | ```go 610 | t := 300 * time.Millisecond 611 | midtrans.DefaultGoHttpClient = &http.Client{ 612 | Timeout: t, 613 | } 614 | ``` 615 | 616 | ## 4. Handling Error 617 | When using function that result in Midtrans API call e.g: c.ChargeTransaction(...) or s.CreateTransaction(...) there's a chance it may throw error (Midtrans [Error object](/error.go)), the error object will contains below properties that can be used as information to your error handling logic: 618 | ```go 619 | _, err = c.chargeTransaction(param) 620 | if err != nil { 621 | msg := err.Error() // general message error 622 | stsCode := err.GetStatusCode() // HTTP status code e.g: 400, 401, etc. 623 | rawApiRes := err.GetRawApiResponse() // raw Go HTTP response object 624 | rawErr := err.Unwrap() // raw Go err object 625 | } 626 | ``` 627 | midtrans.error complies with [Go standard error](https://go.dev/blog/go1.13-errors). which support `Error, Unwrap, Is, As`. 628 | ```go 629 | // sample using errors.As 630 | _, err := c.chargeTransaction(param) 631 | var Err *midtrans.Error 632 | if errors.As(err, &Err) { 633 | fmt.Println(Err.Message) 634 | fmt.Println(Err.StatusCode) 635 | } 636 | 637 | // sample using unwrap 638 | _, err := c.chargeTransaction(param) 639 | if err != nil { 640 | log.Print(errors.Unwrap(err)) 641 | fmt.Print(err) 642 | } 643 | ``` 644 | 645 | ## 5. Examples 646 | Examples are available on [/examples](example) folder 647 | There are: 648 | - [Core Api examples](example/simple/coreapi/sample.go) 649 | - [Snap examples](example/simple/snap/sample.go) 650 | - [Iris examples](example/simple/iris/sample.go) 651 | - [Readme Example](example/README.md) 652 | 653 | Integration test are available 654 | - [CoreApi Sample Functional Test](coreapi/client_test.go) 655 | - [Snap Sample Functional Test](snap/client_test.go) 656 | - [Iris Sample Functional Test](iris/client_test.go) 657 | 658 | 659 | ## Get help 660 | 661 | * [Midtrans Docs](https://docs.midtrans.com) 662 | * [Midtrans Dashboard ](https://dashboard.midtrans.com/) 663 | * [SNAP documentation](http://snap-docs.midtrans.com) 664 | * [Core API documentation](http://api-docs.midtrans.com) 665 | * Can't find answer you looking for? email to [support@midtrans.com](mailto:support@midtrans.com) 666 | -------------------------------------------------------------------------------- /bank.go: -------------------------------------------------------------------------------- 1 | package midtrans 2 | 3 | // Bank value 4 | type Bank string 5 | 6 | const ( 7 | //BankBni : bni 8 | BankBni Bank = "bni" 9 | 10 | //BankMandiri : mandiri 11 | BankMandiri Bank = "mandiri" 12 | 13 | //BankCimb : cimb 14 | BankCimb Bank = "cimb" 15 | 16 | //BankBca : bca 17 | BankBca Bank = "bca" 18 | 19 | //BankBri : bri 20 | BankBri Bank = "bri" 21 | 22 | //BankMaybank : maybank 23 | BankMaybank Bank = "maybank" 24 | 25 | //BankPermata : permata 26 | BankPermata Bank = "permata" 27 | 28 | //BankMega : mega 29 | BankMega Bank = "mega" 30 | ) 31 | -------------------------------------------------------------------------------- /coreapi/client.go: -------------------------------------------------------------------------------- 1 | package coreapi 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "strconv" 9 | 10 | "github.com/midtrans/midtrans-go" 11 | ) 12 | 13 | // Client : CoreAPI Client struct 14 | type Client struct { 15 | ServerKey string 16 | ClientKey string 17 | Env midtrans.EnvironmentType 18 | HttpClient midtrans.HttpClient 19 | Options *midtrans.ConfigOptions 20 | } 21 | 22 | // New : this function will always be called when the CoreApi is initiated 23 | func (c *Client) New(serverKey string, env midtrans.EnvironmentType) { 24 | c.Env = env 25 | c.ServerKey = serverKey 26 | c.Options = &midtrans.ConfigOptions{} 27 | c.HttpClient = midtrans.GetHttpClient(env) 28 | } 29 | 30 | // getDefaultClient : internal function to get default Client 31 | func getDefaultClient() Client { 32 | return Client{ 33 | ServerKey: midtrans.ServerKey, 34 | ClientKey: midtrans.ClientKey, 35 | Env: midtrans.Environment, 36 | HttpClient: midtrans.GetHttpClient(midtrans.Environment), 37 | Options: &midtrans.ConfigOptions{ 38 | PaymentOverrideNotification: midtrans.PaymentOverrideNotification, 39 | PaymentAppendNotification: midtrans.PaymentAppendNotification, 40 | }, 41 | } 42 | } 43 | 44 | // ChargeTransactionWithMap : Do `/charge` API request to Midtrans Core API return RAW MAP with Map as 45 | // body parameter, will be converted to JSON, more detail refer to: https://api-docs.midtrans.com 46 | func (c Client) ChargeTransactionWithMap(req *ChargeReqWithMap) (ResponseWithMap, *midtrans.Error) { 47 | resp := ResponseWithMap{} 48 | jsonReq, _ := json.Marshal(req) 49 | err := c.HttpClient.Call( 50 | http.MethodPost, 51 | fmt.Sprintf("%s/v2/charge", c.Env.BaseUrl()), 52 | &c.ServerKey, 53 | c.Options, 54 | bytes.NewBuffer(jsonReq), 55 | &resp) 56 | if err != nil { 57 | return resp, err 58 | } 59 | return resp, nil 60 | } 61 | 62 | // ChargeTransactionWithMap : Do `/charge` API request to Midtrans Core API return RAW MAP with Map as 63 | // body parameter, will be converted to JSON, more detail refer to: https://api-docs.midtrans.com 64 | func ChargeTransactionWithMap(req *ChargeReqWithMap) (ResponseWithMap, *midtrans.Error) { 65 | return getDefaultClient().ChargeTransactionWithMap(req) 66 | } 67 | 68 | // ChargeTransaction : Do `/charge` API request to Midtrans Core API return `coreapi.ChargeResponse` with `coreapi.ChargeReq` 69 | // as body parameter, will be converted to JSON, more detail refer to: https://api-docs.midtrans.com 70 | func (c Client) ChargeTransaction(req *ChargeReq) (*ChargeResponse, *midtrans.Error) { 71 | resp := &ChargeResponse{} 72 | jsonReq, _ := json.Marshal(req) 73 | err := c.HttpClient.Call(http.MethodPost, 74 | fmt.Sprintf("%s/v2/charge", c.Env.BaseUrl()), 75 | &c.ServerKey, 76 | c.Options, 77 | bytes.NewBuffer(jsonReq), 78 | resp, 79 | ) 80 | 81 | if err != nil { 82 | return resp, err 83 | } 84 | return resp, nil 85 | } 86 | 87 | // ChargeTransaction : Do `/charge` API request to Midtrans Core API return `coreapi.ChargeResponse` with `coreapi.ChargeReq` 88 | // as body parameter, will be converted to JSON, more detail refer to: https://api-docs.midtrans.com 89 | func ChargeTransaction(req *ChargeReq) (*ChargeResponse, *midtrans.Error) { 90 | return getDefaultClient().ChargeTransaction(req) 91 | } 92 | 93 | // CardToken : Do `/token` API request to Midtrans Core API return `coreapi.CardTokenResponse`, 94 | // more detail refer to: https://api-docs.midtrans.com/#get-token 95 | func (c Client) CardToken(cardNumber string, expMonth int, expYear int, cvv string, clientKey string) (*CardTokenResponse, *midtrans.Error) { 96 | resp := &CardTokenResponse{} 97 | URL := c.Env.BaseUrl() + 98 | "/v2/token?client_key=" + clientKey + 99 | "&card_number=" + cardNumber + 100 | "&card_exp_month=" + strconv.Itoa(expMonth) + 101 | "&card_exp_year=" + strconv.Itoa(expYear) + 102 | "&card_cvv=" + cvv 103 | err := c.HttpClient.Call(http.MethodGet, URL, nil, c.Options, nil, resp) 104 | 105 | if err != nil { 106 | return resp, err 107 | } 108 | return resp, nil 109 | } 110 | 111 | // CardToken : Do `/token` API request to Midtrans Core API return `coreapi.CardTokenResponse`, 112 | // more detail refer to: https://api-docs.midtrans.com/#get-token 113 | func CardToken(cardNumber string, expMonth int, expYear int, cvv string) (*CardTokenResponse, *midtrans.Error) { 114 | c := getDefaultClient() 115 | return c.CardToken(cardNumber, expMonth, expYear, cvv, c.ClientKey) 116 | } 117 | 118 | // RegisterCard : Do `/card/register` API request to Midtrans Core API return `coreapi.CardRegisterResponse`, 119 | // more detail refer to: https://api-docs.midtrans.com/#register-card 120 | func (c Client) RegisterCard(cardNumber string, expMonth int, expYear int, clientKey string) (*CardRegisterResponse, *midtrans.Error) { 121 | resp := &CardRegisterResponse{} 122 | URL := c.Env.BaseUrl() + 123 | "/v2/card/register?card_number=" + cardNumber + 124 | "&card_exp_month=" + strconv.Itoa(expMonth) + 125 | "&card_exp_year=" + strconv.Itoa(expYear) + 126 | "&client_key=" + clientKey 127 | 128 | err := c.HttpClient.Call(http.MethodGet, URL, nil, c.Options, nil, resp) 129 | 130 | if err != nil { 131 | return resp, err 132 | } 133 | return resp, nil 134 | } 135 | 136 | // RegisterCard : Do `/card/register` API request to Midtrans Core API return `coreapi.CardRegisterResponse`, 137 | // more detail refer to: https://api-docs.midtrans.com/#register-card 138 | func RegisterCard(cardNumber string, expMonth int, expYear int) (*CardRegisterResponse, *midtrans.Error) { 139 | c := getDefaultClient() 140 | return c.RegisterCard(cardNumber, expMonth, expYear, c.ClientKey) 141 | } 142 | 143 | // CardPointInquiry : Do `/point_inquiry/{tokenId}` API request to Midtrans Core API return `coreapi.CardTokenResponse`, 144 | // more detail refer to: https://api-docs.midtrans.com/#point-inquiry 145 | func (c Client) CardPointInquiry(cardToken string) (*PointInquiryResponse, *midtrans.Error) { 146 | resp := &PointInquiryResponse{} 147 | err := c.HttpClient.Call( 148 | http.MethodGet, 149 | fmt.Sprintf("%s/v2/point_inquiry/%s", c.Env.BaseUrl(), cardToken), 150 | &c.ServerKey, 151 | c.Options, 152 | nil, 153 | resp, 154 | ) 155 | 156 | if err != nil { 157 | return resp, err 158 | } 159 | return resp, nil 160 | } 161 | 162 | // CardPointInquiry : Do `/point_inquiry/{tokenId}` API request to Midtrans Core API return `coreapi.CardTokenResponse`, 163 | // more detail refer to: https://api-docs.midtrans.com/#point-inquiry 164 | func CardPointInquiry(cardToken string) (*PointInquiryResponse, *midtrans.Error) { 165 | return getDefaultClient().CardPointInquiry(cardToken) 166 | } 167 | 168 | // GetBIN : Do `v1/bins/{bin}` API request to Midtrans Core API return `coreapi.BinResponse`, 169 | // more detail refer to: https://api-docs.midtrans.com/#bin-api 170 | func (c Client) GetBIN(binNumber string) (*BinResponse, *midtrans.Error) { 171 | resp := &BinResponse{} 172 | err := c.HttpClient.Call( 173 | http.MethodGet, 174 | fmt.Sprintf("%s/v1/bins/%s", c.Env.BaseUrl(), binNumber), 175 | &c.ClientKey, 176 | c.Options, 177 | nil, 178 | resp, 179 | ) 180 | 181 | if err != nil { 182 | return resp, err 183 | } 184 | return resp, nil 185 | } 186 | 187 | // GetBIN : Do `/v1/bins/{bin}` API request to Midtrans Core API return `coreapi.BinResponse`, 188 | // more detail refer to: https://api-docs.midtrans.com/#bin-api 189 | func GetBIN(binNumber string) (*BinResponse, *midtrans.Error) { 190 | return getDefaultClient().GetBIN(binNumber) 191 | } 192 | -------------------------------------------------------------------------------- /coreapi/client_test.go: -------------------------------------------------------------------------------- 1 | package coreapi 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/midtrans/midtrans-go" 6 | assert "github.com/stretchr/testify/require" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | const sandboxClientKey = "SB-Mid-client-yUgKb__vX_zH2TMN" 12 | const sandboxServerKey = "SB-Mid-server-TvgWB_Y9s81-rbMBH7zZ8BHW" 13 | const sampleCardNumber = "4811111111111114" 14 | const bniCardNumber = "4105058689481467" 15 | const bcaBinNumber = "45563300" 16 | 17 | func timestamp() string { 18 | return time.Now().UTC().Format("2006010215040105") 19 | } 20 | 21 | func getCardToken(cardNumber string) string { 22 | year := time.Now().Year() + 1 23 | midtrans.ClientKey = sandboxClientKey 24 | res, _ := CardToken(cardNumber, 12, year, "123") 25 | return res.TokenID 26 | } 27 | 28 | func createPayload(orderId string, paymentType CoreapiPaymentType, cardToken string) *ChargeReq { 29 | if paymentType == PaymentTypeCreditCard { 30 | return &ChargeReq{ 31 | PaymentType: paymentType, 32 | TransactionDetails: midtrans.TransactionDetails{ 33 | OrderID: orderId, 34 | GrossAmt: 10000, 35 | }, 36 | CreditCard: &CreditCardDetails{ 37 | TokenID: cardToken, 38 | }, 39 | } 40 | } 41 | return &ChargeReq{ 42 | PaymentType: paymentType, 43 | TransactionDetails: midtrans.TransactionDetails{ 44 | OrderID: orderId, 45 | GrossAmt: 10000, 46 | }, 47 | } 48 | } 49 | 50 | func TestRegisterCard(t *testing.T) { 51 | year := time.Now().Year() + 1 52 | midtrans.ClientKey = sandboxClientKey 53 | resp1, _ := RegisterCard(sampleCardNumber, 12, year) 54 | assert.Equal(t, resp1.StatusCode, "200") 55 | assert.Equal(t, resp1.MaskCard, "48111111-1114") 56 | 57 | c := Client{} 58 | c.New(sandboxServerKey, midtrans.Sandbox) 59 | resp2, _ := c.RegisterCard(bniCardNumber, 12, year, sandboxClientKey) 60 | assert.Equal(t, resp2.StatusCode, "200") 61 | assert.Equal(t, resp2.MaskCard, "41050586-1467") 62 | } 63 | 64 | func TestCardToken(t *testing.T) { 65 | year := time.Now().Year() + 1 66 | midtrans.ClientKey = sandboxClientKey 67 | resp1, _ := CardToken(sampleCardNumber, 12, year, "123") 68 | assert.Equal(t, resp1.StatusCode, "200") 69 | 70 | c := Client{} 71 | c.New(sandboxServerKey, midtrans.Sandbox) 72 | resp2, _ := c.CardToken(bniCardNumber, 12, year, "123", sandboxClientKey) 73 | assert.Equal(t, resp2.StatusCode, "200") 74 | } 75 | 76 | func TestChargeTransactionWithMap(t *testing.T) { 77 | req1 := &ChargeReqWithMap{ 78 | "payment_type": "gopay", 79 | "transaction_details": map[string]interface{}{ 80 | "order_id": "MID-GO-UNIT_TEST-3" + timestamp(), 81 | "gross_amount": 10000, 82 | }, 83 | } 84 | 85 | midtrans.ServerKey = sandboxServerKey 86 | resp, _ := ChargeTransactionWithMap(req1) 87 | assert.Equal(t, resp["status_code"], "201") 88 | assert.Equal(t, resp["payment_type"], "gopay") 89 | 90 | req2 := &ChargeReqWithMap{ 91 | "payment_type": PaymentTypeBankTransfer, 92 | "transaction_details": map[string]interface{}{ 93 | "order_id": "MID-GO-UNIT_TEST-4" + timestamp(), 94 | "gross_amount": 10000, 95 | }, 96 | } 97 | 98 | c := Client{} 99 | c.New(sandboxServerKey, midtrans.Sandbox) 100 | resp2, _ := c.ChargeTransactionWithMap(req2) 101 | assert.Equal(t, resp2["status_code"], "201") 102 | assert.Equal(t, resp2["payment_type"], "bank_transfer") 103 | } 104 | 105 | func TestChargeTransaction(t *testing.T) { 106 | midtrans.ServerKey = sandboxServerKey 107 | resp1, _ := ChargeTransaction(createPayload("MID-GO-UNIT_TEST-1"+timestamp(), PaymentTypeGopay, "")) 108 | assert.Equal(t, resp1.StatusCode, "201") 109 | assert.Equal(t, resp1.PaymentType, "gopay") 110 | 111 | c := Client{} 112 | c.New(sandboxServerKey, midtrans.Sandbox) 113 | resp2, _ := c.ChargeTransaction(createPayload("MID-GO-UNIT_TEST-2"+timestamp(), PaymentTypeGopay, "")) 114 | assert.Equal(t, resp2.StatusCode, "201") 115 | assert.Equal(t, resp2.PaymentType, "gopay") 116 | } 117 | 118 | func TestChargeTransactionWithIdempotencyKey(t *testing.T) { 119 | req := &ChargeReq{ 120 | PaymentType: PaymentTypeGopay, 121 | TransactionDetails: midtrans.TransactionDetails{ 122 | OrderID: "MID-GO-UNIT_TEST-" + timestamp(), 123 | GrossAmt: 10000, 124 | }, 125 | } 126 | 127 | c := Client{} 128 | c.New(sandboxServerKey, midtrans.Sandbox) 129 | c.Options.SetPaymentIdempotencyKey(timestamp()) 130 | 131 | resp1, _ := c.ChargeTransaction(req) 132 | resp2, _ := c.ChargeTransaction(req) 133 | 134 | assert.Equal(t, resp2, resp1) 135 | } 136 | 137 | func TestCardPointInquiry(t *testing.T) { 138 | midtrans.ServerKey = sandboxServerKey 139 | resp, _ := CardPointInquiry(getCardToken(bniCardNumber)) 140 | assert.Equal(t, resp.StatusCode, "200") 141 | } 142 | 143 | // Failure test case 144 | func TestRegisterCardFailure(t *testing.T) { 145 | midtrans.ClientKey = sandboxClientKey 146 | resp1, _ := RegisterCard(sampleCardNumber, 12, 2020) 147 | 148 | assert.Equal(t, resp1.StatusCode, "400") 149 | assert.Equal(t, resp1.StatusMessage, "One or more parameters in the payload is invalid.") 150 | 151 | c := Client{} 152 | c.New(sandboxServerKey, midtrans.Sandbox) 153 | resp2, _ := c.RegisterCard(bniCardNumber, 12, 2020, sandboxClientKey) 154 | assert.Equal(t, resp2.StatusCode, "400") 155 | assert.Equal(t, resp2.StatusMessage, "One or more parameters in the payload is invalid.") 156 | } 157 | 158 | func TestCardTokenFailure(t *testing.T) { 159 | midtrans.ClientKey = sandboxClientKey 160 | res, _ := CardToken(sampleCardNumber, 12, 2020, "123") 161 | 162 | assert.Equal(t, res.StatusCode, "400") 163 | assert.Equal(t, res.StatusMessage, "One or more parameters in the payload is invalid.") 164 | 165 | c := Client{} 166 | c.New(sandboxServerKey, midtrans.Sandbox) 167 | resp2, _ := c.CardToken(bniCardNumber, 12, 2020, "123", sandboxClientKey) 168 | assert.Equal(t, resp2.StatusCode, "400") 169 | assert.Equal(t, resp2.StatusMessage, "One or more parameters in the payload is invalid.") 170 | } 171 | 172 | func TestChargeTransactionNilParam(t *testing.T) { 173 | midtrans.ServerKey = sandboxServerKey 174 | _, err := ChargeTransaction(nil) 175 | assert.Equal(t, err.GetStatusCode(), 500) 176 | assert.Contains(t, err.GetMessage(), "Midtrans API is returning API error.") 177 | } 178 | 179 | func TestChargeTransactionWithMapNilParam(t *testing.T) { 180 | midtrans.ServerKey = sandboxServerKey 181 | _, err := ChargeTransactionWithMap(nil) 182 | assert.Equal(t, err.GetStatusCode(), 500) 183 | assert.Contains(t, err.GetMessage(), "Midtrans API is returning API error.") 184 | } 185 | 186 | func TestChargeWrongServerKey(t *testing.T) { 187 | midtrans.ServerKey = "DUMMY" 188 | _, err := ChargeTransaction(&ChargeReq{}) 189 | assert.Equal(t, err.GetStatusCode(), 401) 190 | 191 | c := Client{} 192 | c.New("DUMMY", midtrans.Sandbox) 193 | c.ChargeTransaction(&ChargeReq{}) 194 | assert.Equal(t, err.GetStatusCode(), 401) 195 | } 196 | 197 | func TestChargeTransactionWithQRISIncludesQRString(t *testing.T) { 198 | midtrans.ServerKey = sandboxServerKey 199 | resp1, _ := ChargeTransaction(createPayload("MID-GO-UNIT_TEST-1"+timestamp(), PaymentTypeQris, "")) 200 | assert.Equal(t, resp1.StatusCode, "201") 201 | assert.Equal(t, resp1.PaymentType, "qris") 202 | assert.NotEmpty(t, resp1.QRString) 203 | 204 | c := Client{} 205 | c.New(sandboxServerKey, midtrans.Sandbox) 206 | resp2, _ := c.ChargeTransaction(createPayload("MID-GO-UNIT_TEST-2"+timestamp(), PaymentTypeQris, "")) 207 | assert.Equal(t, resp2.StatusCode, "201") 208 | assert.Equal(t, resp2.PaymentType, "qris") 209 | assert.NotEmpty(t, resp2.QRString) 210 | } 211 | 212 | func TestGetBIN(t *testing.T) { 213 | midtrans.ClientKey = sandboxClientKey 214 | resp, _ := GetBIN(bcaBinNumber) 215 | assert.Equal(t, resp.Data.BankCode, "BCA") 216 | assert.Equal(t, resp.Data.RegistrationRequired, false) 217 | } 218 | 219 | type mockHTTPClient struct { 220 | // Define a field to hold the dummy response 221 | dummyResponseJSON []byte 222 | } 223 | 224 | // Implement the GetBIN method of the Client interface for the mock client 225 | func (m *mockHTTPClient) GetBIN(binNumber string) (*BinResponse, error) { 226 | // Return the stored dummy response 227 | var binResponse BinResponse 228 | err := json.Unmarshal(m.dummyResponseJSON, &binResponse) 229 | if err != nil { 230 | return nil, err 231 | } 232 | return &binResponse, nil 233 | } 234 | 235 | func TestGetBINWithRegistrationRequiredIsNullFromResponse(t *testing.T) { 236 | dummyResponseJSON := []byte(`{ 237 | "data": { 238 | "registration_required": null, 239 | "country_name": "INDONESIA", 240 | "country_code": "ID", 241 | "channel": "online_offline", 242 | "brand": "VISA", 243 | "bin_type": "CREDIT", 244 | "bin_class": "GOLD", 245 | "bin": "45563300", 246 | "bank_code": "BCA", 247 | "bank": "BANK CENTRAL ASIA" 248 | } 249 | }`) 250 | 251 | // Create an instance of the mock HTTP client with the dummy response 252 | mockClient := &mockHTTPClient{ 253 | dummyResponseJSON: dummyResponseJSON, 254 | } 255 | 256 | // Call the GetBIN function with the mock client 257 | resp, err := mockClient.GetBIN(bcaBinNumber) 258 | 259 | // Check if there's no error 260 | assert.NoError(t, err) 261 | 262 | // Check if the response matches the expected values 263 | assert.Equal(t, resp.Data.BankCode, "BCA") 264 | assert.Equal(t, resp.Data.RegistrationRequired, false) 265 | } 266 | 267 | func TestGetBINWithRegistrationRequiredIsTrueFromResponse(t *testing.T) { 268 | dummyResponseJSON := []byte(`{ 269 | "data": { 270 | "registration_required": true, 271 | "country_name": "INDONESIA", 272 | "country_code": "ID", 273 | "channel": "online_offline", 274 | "brand": "VISA", 275 | "bin_type": "CREDIT", 276 | "bin_class": "GOLD", 277 | "bin": "45563300", 278 | "bank_code": "BCA", 279 | "bank": "BANK CENTRAL ASIA" 280 | } 281 | }`) 282 | mockClient := &mockHTTPClient{ 283 | dummyResponseJSON: dummyResponseJSON, 284 | } 285 | resp, err := mockClient.GetBIN(bcaBinNumber) 286 | assert.NoError(t, err) 287 | assert.Equal(t, resp.Data.BankCode, "BCA") 288 | assert.Equal(t, resp.Data.RegistrationRequired, true) 289 | } 290 | -------------------------------------------------------------------------------- /coreapi/clientsubscription.go: -------------------------------------------------------------------------------- 1 | package coreapi 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/midtrans/midtrans-go" 8 | "net/http" 9 | ) 10 | 11 | // CreateSubscription : Do `/v1/subscriptions` To create subscription that contains all details for creating transaction 12 | // more detail refer to: http://api-docs.midtrans.com/#recurring-api 13 | func (c Client) CreateSubscription(req *SubscriptionReq) (*CreateSubscriptionResponse, *midtrans.Error) { 14 | resp := &CreateSubscriptionResponse{} 15 | jsonReq, _ := json.Marshal(req) 16 | err := c.HttpClient.Call( 17 | http.MethodPost, 18 | fmt.Sprintf("%s/v1/subscriptions", c.Env.BaseUrl()), 19 | &c.ServerKey, 20 | c.Options, 21 | bytes.NewBuffer(jsonReq), 22 | resp) 23 | if err != nil { 24 | return resp, err 25 | } 26 | return resp, nil 27 | } 28 | 29 | // CreateSubscription : Do `/v1/subscriptions` To create subscription that contains all details for creating transaction 30 | // more detail refer to: http://api-docs.midtrans.com/#recurring-api 31 | func CreateSubscription(req *SubscriptionReq) (*CreateSubscriptionResponse, *midtrans.Error) { 32 | return getDefaultClient().CreateSubscription(req) 33 | } 34 | 35 | //GetSubscription : Do `/v1/subscriptions/{subscription_id}` To find subscription by id to see the subscription details 36 | // more detail refer to: http://api-docs.midtrans.com/#recurring-api 37 | func (c Client) GetSubscription(subscriptionId string) (*StatusSubscriptionResponse, *midtrans.Error) { 38 | resp := &StatusSubscriptionResponse{} 39 | err := c.HttpClient.Call( 40 | http.MethodGet, 41 | fmt.Sprintf("%s/v1/subscriptions/%s", c.Env.BaseUrl(), subscriptionId), 42 | &c.ServerKey, 43 | c.Options, 44 | nil, 45 | resp, 46 | ) 47 | 48 | if err != nil { 49 | return resp, err 50 | } 51 | return resp, nil 52 | } 53 | 54 | //GetSubscription : Do `/v1/subscriptions/{subscription_id}` To find subscription by id to see the subscription details 55 | // more detail refer to: http://api-docs.midtrans.com/#recurring-api 56 | func GetSubscription(subscriptionId string) (*StatusSubscriptionResponse, *midtrans.Error) { 57 | return getDefaultClient().GetSubscription(subscriptionId) 58 | } 59 | 60 | // DisableSubscription : Do `/v1/subscriptions/{subscription_id}/disable` To make the subscription inactive 61 | // (the subscription will not create transaction anymore) more detail refer to: http://api-docs.midtrans.com/#recurring-api 62 | func (c Client) DisableSubscription(subscriptionId string) (*DisableSubscriptionResponse, *midtrans.Error) { 63 | resp := &DisableSubscriptionResponse{} 64 | err := c.HttpClient.Call( 65 | http.MethodPost, 66 | fmt.Sprintf("%s/v1/subscriptions/%s/disable", c.Env.BaseUrl(), subscriptionId), 67 | &c.ServerKey, 68 | c.Options, 69 | nil, 70 | resp, 71 | ) 72 | 73 | if err != nil { 74 | return resp, err 75 | } 76 | return resp, nil 77 | } 78 | 79 | // DisableSubscription : Do `/v1/subscriptions/{subscription_id}/disable` To make the subscription inactive 80 | // (the subscription will not create transaction anymore) more detail refer to: http://api-docs.midtrans.com/#recurring-api 81 | func DisableSubscription(subscriptionId string) (*DisableSubscriptionResponse, *midtrans.Error) { 82 | return getDefaultClient().DisableSubscription(subscriptionId) 83 | } 84 | 85 | // EnableSubscription : Do `/v1/subscriptions/{subscription_id}/enable` To make the subscription active 86 | // (the subscription will create periodic transaction) more detail refer to: http://api-docs.midtrans.com/#recurring-api 87 | func (c Client) EnableSubscription(subscriptionId string) (*EnableSubscriptionResponse, *midtrans.Error) { 88 | resp := &EnableSubscriptionResponse{} 89 | err := c.HttpClient.Call( 90 | http.MethodPost, 91 | fmt.Sprintf("%s/v1/subscriptions/%s/enable", c.Env.BaseUrl(), subscriptionId), 92 | &c.ServerKey, 93 | c.Options, 94 | nil, 95 | resp, 96 | ) 97 | 98 | if err != nil { 99 | return resp, err 100 | } 101 | return resp, nil 102 | } 103 | 104 | // EnableSubscription : Do `/v1/subscriptions/{subscription_id}/enable` To make the subscription active 105 | // (the subscription will create periodic transaction) more detail refer to: http://api-docs.midtrans.com/#recurring-api 106 | func EnableSubscription(subscriptionId string) (*EnableSubscriptionResponse, *midtrans.Error) { 107 | return getDefaultClient().EnableSubscription(subscriptionId) 108 | } 109 | 110 | // UpdateSubscription : Do `/v1/subscriptions/{subscription_id}` To update existing subscription details 111 | // more detail refer to: http://api-docs.midtrans.com/#recurring-api 112 | func (c Client) UpdateSubscription(subscriptionId string, req *SubscriptionReq) (*UpdateSubscriptionResponse, *midtrans.Error) { 113 | resp := &UpdateSubscriptionResponse{} 114 | jsonReq, _ := json.Marshal(req) 115 | err := c.HttpClient.Call( 116 | http.MethodPatch, 117 | fmt.Sprintf("%s/v1/subscriptions/%s", c.Env.BaseUrl(), subscriptionId), 118 | &c.ServerKey, 119 | c.Options, 120 | bytes.NewBuffer(jsonReq), 121 | resp) 122 | if err != nil { 123 | return resp, err 124 | } 125 | return resp, nil 126 | } 127 | 128 | // UpdateSubscription : Do `/v1/subscriptions/{subscription_id}` To update existing subscription details 129 | // more detail refer to: http://api-docs.midtrans.com/#recurring-api 130 | func UpdateSubscription(subscriptionId string, req *SubscriptionReq) (*UpdateSubscriptionResponse, *midtrans.Error) { 131 | return getDefaultClient().UpdateSubscription(subscriptionId, req) 132 | } 133 | -------------------------------------------------------------------------------- /coreapi/clientsubscription_test.go: -------------------------------------------------------------------------------- 1 | package coreapi 2 | 3 | import ( 4 | "github.com/midtrans/midtrans-go" 5 | assert "github.com/stretchr/testify/require" 6 | "log" 7 | "testing" 8 | ) 9 | 10 | /* 11 | This is subscription API integration test section 12 | */ 13 | var subs Client 14 | 15 | var subscriptionId string 16 | var subscriptionName string 17 | 18 | func initiateMidtransSubs() { 19 | midtrans.ServerKey = sandboxServerKey 20 | midtrans.ClientKey = sandboxClientKey 21 | 22 | subs.New(sandboxServerKey, midtrans.Sandbox) 23 | } 24 | 25 | func TestCreateSubscription(t *testing.T) { 26 | initiateMidtransSubs() 27 | subscriptionName = "MidGoSubTest-" + timestamp() 28 | req := &SubscriptionReq{ 29 | Name: subscriptionName, 30 | Amount: 100000, 31 | Currency: "IDR", 32 | PaymentType: PaymentTypeCreditCard, 33 | Token: "DUMMY", 34 | Schedule: ScheduleDetails{ 35 | Interval: 1, 36 | IntervalUnit: "month", 37 | MaxInterval: 12, 38 | }, 39 | CustomerDetails: &midtrans.CustomerDetails{ 40 | FName: "MidtransGo", 41 | LName: "SubscriptionTest", 42 | Email: "mid-go@mainlesia.com", 43 | Phone: "081234567", 44 | }, 45 | } 46 | 47 | resp, err := subs.CreateSubscription(req) 48 | if err != nil { 49 | log.Println("Failure :") 50 | log.Fatalln(err) 51 | } else { 52 | log.Println("Success :") 53 | log.Println(resp) 54 | assert.Equal(t, resp.Status, "active") 55 | assert.NotEmpty(t, resp.ID) 56 | subscriptionId = resp.ID 57 | } 58 | 59 | } 60 | 61 | func TestGetSubscription(t *testing.T) { 62 | initiateMidtransSubs() 63 | resp, err := subs.GetSubscription(subscriptionId) 64 | if err != nil { 65 | log.Println("Failure :") 66 | log.Fatal(err) 67 | } else { 68 | log.Println("Success :") 69 | log.Println(resp) 70 | assert.Equal(t, resp.Status, "active") 71 | assert.Equal(t, resp.StatusMessage, "") 72 | } 73 | } 74 | 75 | func TestDisableSubscription(t *testing.T) { 76 | initiateMidtransSubs() 77 | resp, err := subs.DisableSubscription(subscriptionId) 78 | if err != nil { 79 | log.Println("Failure :") 80 | log.Fatal(err) 81 | } else { 82 | log.Println("Success :") 83 | log.Println(resp) 84 | assert.Equal(t, resp.StatusMessage, "Subscription is updated.") 85 | } 86 | } 87 | 88 | func TestEnableSubscription(t *testing.T) { 89 | initiateMidtransSubs() 90 | resp, err := subs.EnableSubscription(subscriptionId) 91 | if err != nil { 92 | log.Println("Failure :") 93 | log.Fatal(err) 94 | } else { 95 | log.Println("Success :") 96 | log.Println(resp) 97 | assert.Equal(t, resp.StatusMessage, "Subscription is updated.") 98 | } 99 | } 100 | 101 | func TestUpdateSubscription(t *testing.T) { 102 | initiateMidtransSubs() 103 | reqUpdate := &SubscriptionReq{ 104 | Name: subscriptionName, 105 | Amount: 50000, 106 | Currency: "IDR", 107 | PaymentType: PaymentTypeCreditCard, 108 | Token: "DUMMY", 109 | Schedule: ScheduleDetails{ 110 | Interval: 1, 111 | IntervalUnit: "month", 112 | MaxInterval: 12, 113 | }, 114 | CustomerDetails: &midtrans.CustomerDetails{ 115 | FName: "MidtransGo", 116 | LName: "SubscriptionTest", 117 | Email: "mid-go@mainlesia.com", 118 | Phone: "081234567", 119 | }, 120 | } 121 | resp, err := subs.UpdateSubscription(subscriptionId, reqUpdate) 122 | if err != nil { 123 | log.Println("Failure :") 124 | log.Fatal(err) 125 | } else { 126 | log.Println("Success :") 127 | log.Println(resp) 128 | assert.Equal(t, resp.StatusMessage, "Subscription is updated.") 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /coreapi/clienttokenization.go: -------------------------------------------------------------------------------- 1 | package coreapi 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/midtrans/midtrans-go" 8 | "net/http" 9 | ) 10 | 11 | // LinkPaymentAccount : Do `/v2/pay/account` to link customer account to be used for specific payment channels 12 | // more detail refer to: https://api-docs.midtrans.com/#create-pay-account 13 | func (c Client) LinkPaymentAccount(req *PaymentAccountReq) (*PaymentAccountResponse, *midtrans.Error) { 14 | resp := &PaymentAccountResponse{} 15 | jsonReq, _ := json.Marshal(req) 16 | err := c.HttpClient.Call( 17 | http.MethodPost, 18 | fmt.Sprintf("%s/v2/pay/account", c.Env.BaseUrl()), 19 | &c.ServerKey, 20 | c.Options, 21 | bytes.NewBuffer(jsonReq), 22 | resp) 23 | if err != nil { 24 | return resp, err 25 | } 26 | return resp, nil 27 | } 28 | 29 | // LinkPaymentAccount : Do `/v2/pay/account/{account_id}` to link customer account to be used for specific payment channels 30 | // more detail refer to: https://api-docs.midtrans.com/#get-pay-account 31 | func LinkPaymentAccount(req *PaymentAccountReq) (*PaymentAccountResponse, *midtrans.Error) { 32 | return getDefaultClient().LinkPaymentAccount(req) 33 | } 34 | 35 | // GetPaymentAccount : Do `/v2/pay/account/{account_id}t` to get customer payment account details 36 | // more detail refer to: https://api-docs.midtrans.com/#get-pay-account 37 | func (c Client) GetPaymentAccount(accountId string) (*PaymentAccountResponse, *midtrans.Error) { 38 | resp := &PaymentAccountResponse{} 39 | err := c.HttpClient.Call( 40 | http.MethodGet, 41 | fmt.Sprintf("%s/v2/pay/account/%s", c.Env.BaseUrl(), accountId), 42 | &c.ServerKey, 43 | c.Options, 44 | nil, 45 | resp) 46 | if err != nil { 47 | return resp, err 48 | } 49 | return resp, nil 50 | } 51 | 52 | // GetPaymentAccount : Do `/v2/pay/account/{account_id}` to get customer payment account details 53 | // more detail refer to: https://api-docs.midtrans.com/#get-pay-account 54 | func GetPaymentAccount(accountId string) (*PaymentAccountResponse, *midtrans.Error) { 55 | return getDefaultClient().GetPaymentAccount(accountId) 56 | } 57 | 58 | // UnlinkPaymentAccount : Do `/v2/pay/account/{account_id}/unbind` to unbind a linked customer account 59 | // more detail refer to: https://api-docs.midtrans.com/#unbind-pay-account 60 | func (c Client) UnlinkPaymentAccount(accountId string) (*PaymentAccountResponse, *midtrans.Error) { 61 | resp := &PaymentAccountResponse{} 62 | err := c.HttpClient.Call( 63 | http.MethodPost, 64 | fmt.Sprintf("%s/v2/pay/account/%s/unbind", c.Env.BaseUrl(), accountId), 65 | &c.ServerKey, 66 | c.Options, 67 | nil, 68 | resp) 69 | if err != nil { 70 | return resp, err 71 | } 72 | return resp, nil 73 | } 74 | 75 | // UnlinkPaymentAccount : Do `/v2/pay/account/{account_id}/unbind` to unbind a linked customer account 76 | // more detail refer to: https://api-docs.midtrans.com/#unbind-pay-account 77 | func UnlinkPaymentAccount(accountId string) (*PaymentAccountResponse, *midtrans.Error) { 78 | return getDefaultClient().UnlinkPaymentAccount(accountId) 79 | } 80 | -------------------------------------------------------------------------------- /coreapi/clienttokenization_test.go: -------------------------------------------------------------------------------- 1 | package coreapi 2 | 3 | import ( 4 | "github.com/midtrans/midtrans-go" 5 | assert "github.com/stretchr/testify/require" 6 | "log" 7 | "testing" 8 | ) 9 | 10 | /* 11 | This is Tokenization API integration test 12 | */ 13 | var coreapi Client 14 | var accountId string 15 | var phoneNotRegistered = "123450001" 16 | var phoneNumberBlocked = "123450002" 17 | 18 | func initiateMidtransTokenization() { 19 | midtrans.ServerKey = sandboxServerKey 20 | midtrans.ClientKey = sandboxClientKey 21 | 22 | subs.New(sandboxServerKey, midtrans.Sandbox) 23 | } 24 | 25 | func paymentAccount(phoneNumber string) *PaymentAccountReq { 26 | return &PaymentAccountReq{ 27 | PaymentType: PaymentTypeGopay, 28 | GopayPartner: &GopayPartnerDetails{ 29 | PhoneNumber: phoneNumber, 30 | CountryCode: "62", 31 | RedirectURL: "https://midtrans.com/", 32 | }, 33 | } 34 | } 35 | 36 | func TestLinkPaymentAccountUserNotFound(t *testing.T) { 37 | initiateMidtransTokenization() 38 | req := paymentAccount(phoneNotRegistered) 39 | 40 | resp, err := subs.LinkPaymentAccount(req) 41 | if err != nil { 42 | log.Println("Failure :") 43 | log.Fatalln(err) 44 | } else { 45 | log.Println("Success :") 46 | log.Println(resp) 47 | assert.Equal(t, "202", resp.StatusCode) 48 | assert.Equal(t, "User Not Found", resp.ChannelResponseMessage) 49 | } 50 | } 51 | 52 | func TestLinkPaymentAccountUserBlocked(t *testing.T) { 53 | initiateMidtransTokenization() 54 | req := paymentAccount(phoneNumberBlocked) 55 | 56 | resp, err := subs.LinkPaymentAccount(req) 57 | if err != nil { 58 | log.Println("Failure :") 59 | log.Fatalln(err) 60 | } else { 61 | log.Println("Success :") 62 | log.Println(resp) 63 | assert.Equal(t, "202", resp.StatusCode) 64 | assert.Equal(t, "Wallet is Blocked", resp.ChannelResponseMessage) 65 | } 66 | } 67 | 68 | func TestLinkPaymentAccount(t *testing.T) { 69 | initiateMidtransTokenization() 70 | req := paymentAccount("628123456789") 71 | 72 | resp, err := subs.LinkPaymentAccount(req) 73 | if err != nil { 74 | log.Println("Failure :") 75 | log.Fatalln(err) 76 | } else { 77 | log.Println("Success :") 78 | log.Println(resp) 79 | assert.Equal(t, "201", resp.StatusCode) 80 | assert.NotEmpty(t, resp.AccountId) 81 | assert.NotEmpty(t, resp.Actions) 82 | accountId = resp.AccountId 83 | } 84 | } 85 | 86 | func TestGetPaymentAccount(t *testing.T) { 87 | initiateMidtransTokenization() 88 | 89 | resp, err := subs.GetPaymentAccount(accountId) 90 | if err != nil { 91 | log.Println("Failure :") 92 | log.Fatalln(err) 93 | } else { 94 | log.Println("Success :") 95 | log.Println(resp) 96 | assert.Equal(t, "201", resp.StatusCode) 97 | assert.Equal(t, accountId, resp.AccountId) 98 | } 99 | } 100 | 101 | func TestUnlinkPaymentAccount(t *testing.T) { 102 | initiateMidtransTokenization() 103 | _, err := subs.UnlinkPaymentAccount(accountId) 104 | if err != nil { 105 | log.Println("Failure :", err) 106 | assert.Equal(t, 412, err.StatusCode) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /coreapi/clienttransaction.go: -------------------------------------------------------------------------------- 1 | package coreapi 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/midtrans/midtrans-go" 8 | "net/http" 9 | ) 10 | 11 | // CheckTransaction : Do `/{orderId}/status` API request to Midtrans Core API return `coreapi.TransactionStatusResponse`, 12 | // more detail refer to: https://api-docs.midtrans.com/#get-transaction-status 13 | func (c Client) CheckTransaction(param string) (*TransactionStatusResponse, *midtrans.Error) { 14 | resp := &TransactionStatusResponse{} 15 | err := c.HttpClient.Call( 16 | http.MethodGet, 17 | fmt.Sprintf("%s/v2/%s/status", c.Env.BaseUrl(), param), 18 | &c.ServerKey, 19 | nil, 20 | nil, 21 | resp, 22 | ) 23 | 24 | if err != nil { 25 | return resp, err 26 | } 27 | return resp, nil 28 | } 29 | 30 | // CheckTransaction : Do `/{orderId}/status` API request to Midtrans Core API return `coreapi.TransactionStatusResponse`, 31 | // more detail refer to: https://api-docs.midtrans.com/#get-transaction-status 32 | func CheckTransaction(param string) (*TransactionStatusResponse, *midtrans.Error) { 33 | return getDefaultClient().CheckTransaction(param) 34 | } 35 | 36 | // ApproveTransaction : Do `/{orderId}/approve` API request to Midtrans Core API return `coreapi.ApproveResponse`, 37 | // more detail refer to: https://api-docs.midtrans.com/#approve-transaction 38 | func (c Client) ApproveTransaction(param string) (*ApproveResponse, *midtrans.Error) { 39 | resp := &ApproveResponse{} 40 | err := c.HttpClient.Call( 41 | http.MethodPost, 42 | fmt.Sprintf("%s/v2/%s/approve", c.Env.BaseUrl(), param), 43 | &c.ServerKey, 44 | c.Options, 45 | nil, 46 | resp, 47 | ) 48 | 49 | if err != nil { 50 | return resp, err 51 | } 52 | return resp, nil 53 | } 54 | 55 | // ApproveTransaction : Do `/{orderId}/approve` API request to Midtrans Core API return `coreapi.ApproveResponse`, 56 | // more detail refer to: https://api-docs.midtrans.com/#approve-transaction 57 | func ApproveTransaction(param string) (*ApproveResponse, *midtrans.Error) { 58 | return getDefaultClient().ApproveTransaction(param) 59 | } 60 | 61 | // DenyTransaction : Do `/{orderId}/deny` API request to Midtrans Core API return `coreapi.DenyResponse`, 62 | // more detail refer to: https://api-docs.midtrans.com/#deny-transaction 63 | func (c Client) DenyTransaction(param string) (*DenyResponse, *midtrans.Error) { 64 | resp := &DenyResponse{} 65 | err := c.HttpClient.Call( 66 | http.MethodPost, 67 | fmt.Sprintf("%s/v2/%s/deny", c.Env.BaseUrl(), param), 68 | &c.ServerKey, 69 | c.Options, 70 | nil, 71 | resp, 72 | ) 73 | 74 | if err != nil { 75 | return resp, err 76 | } 77 | return resp, nil 78 | } 79 | 80 | // DenyTransaction : Do `/{orderId}/deny` API request to Midtrans Core API return `coreapi.DenyResponse`, 81 | // more detail refer to: https://api-docs.midtrans.com/#deny-transaction 82 | func DenyTransaction(param string) (*DenyResponse, *midtrans.Error) { 83 | return getDefaultClient().DenyTransaction(param) 84 | } 85 | 86 | // CancelTransaction : Do `/{orderId}/cancel` API request to Midtrans Core API return `coreapi.CancelResponse`, 87 | // more detail refer to: https://api-docs.midtrans.com/#cancel-transaction 88 | func (c Client) CancelTransaction(param string) (*CancelResponse, *midtrans.Error) { 89 | resp := &CancelResponse{} 90 | err := c.HttpClient.Call( 91 | http.MethodPost, 92 | fmt.Sprintf("%s/v2/%s/cancel", c.Env.BaseUrl(), param), 93 | &c.ServerKey, 94 | c.Options, 95 | nil, 96 | resp, 97 | ) 98 | 99 | if err != nil { 100 | return resp, err 101 | } 102 | return resp, nil 103 | } 104 | 105 | // CancelTransaction : Do `/{orderId}/cancel` API request to Midtrans Core API return `coreapi.CancelResponse`, 106 | // more detail refer to: https://api-docs.midtrans.com/#cancel-transaction 107 | func CancelTransaction(param string) (*CancelResponse, *midtrans.Error) { 108 | return getDefaultClient().CancelTransaction(param) 109 | } 110 | 111 | // ExpireTransaction : Do `/{orderId}/expire` API request to Midtrans Core API return `coreapi.ExpireResponse`, 112 | // more detail refer to: https://api-docs.midtrans.com/#expire-transaction 113 | func (c Client) ExpireTransaction(param string) (*ExpireResponse, *midtrans.Error) { 114 | resp := &ExpireResponse{} 115 | err := c.HttpClient.Call( 116 | http.MethodPost, 117 | fmt.Sprintf("%s/v2/%s/expire", c.Env.BaseUrl(), param), 118 | &c.ServerKey, 119 | c.Options, 120 | nil, 121 | resp, 122 | ) 123 | 124 | if err != nil { 125 | return resp, err 126 | } 127 | return resp, nil 128 | } 129 | 130 | // ExpireTransaction : Do `/{orderId}/expire` API request to Midtrans Core API return `coreapi.ExpireResponse`, 131 | // more detail refer to: https://api-docs.midtrans.com/#expire-transaction 132 | func ExpireTransaction(param string) (*ExpireResponse, *midtrans.Error) { 133 | return getDefaultClient().ExpireTransaction(param) 134 | } 135 | 136 | // RefundTransaction : Do `/{orderId}/refund` API request to Midtrans Core API return `coreapi.RefundResponse`, 137 | // with `coreapi.RefundReq` as body parameter, will be converted to JSON, 138 | // more detail refer to: https://api-docs.midtrans.com/#refund-transaction 139 | func (c Client) RefundTransaction(param string, req *RefundReq) (*RefundResponse, *midtrans.Error) { 140 | resp := &RefundResponse{} 141 | jsonReq, _ := json.Marshal(req) 142 | err := c.HttpClient.Call( 143 | http.MethodPost, 144 | fmt.Sprintf("%s/v2/%s/refund", c.Env.BaseUrl(), param), 145 | &c.ServerKey, 146 | c.Options, 147 | bytes.NewBuffer(jsonReq), 148 | resp, 149 | ) 150 | 151 | if err != nil { 152 | return resp, err 153 | } 154 | return resp, nil 155 | } 156 | 157 | // RefundTransaction : Do `/{orderId}/refund` API request to Midtrans Core API return `coreapi.RefundResponse`, 158 | // with `coreapi.RefundReq` as body parameter, will be converted to JSON, 159 | // more detail refer to: https://api-docs.midtrans.com/#refund-transaction 160 | func RefundTransaction(param string, req *RefundReq) (*RefundResponse, *midtrans.Error) { 161 | return getDefaultClient().RefundTransaction(param, req) 162 | } 163 | 164 | // DirectRefundTransaction : Do `/{orderId}/refund/online/direct` API request to Midtrans Core API return `coreapi.RefundResponse`, 165 | // with `coreapi.CaptureReq` as body parameter, will be converted to JSON, 166 | // more detail refer to: https://api-docs.midtrans.com/#direct-refund-transaction 167 | func (c Client) DirectRefundTransaction(param string, req *RefundReq) (*RefundResponse, *midtrans.Error) { 168 | resp := &RefundResponse{} 169 | jsonReq, _ := json.Marshal(req) 170 | err := c.HttpClient.Call( 171 | http.MethodPost, 172 | fmt.Sprintf("%s/v2/%s/refund/online/direct", c.Env.BaseUrl(), param), 173 | &c.ServerKey, 174 | c.Options, 175 | bytes.NewBuffer(jsonReq), 176 | resp, 177 | ) 178 | 179 | if err != nil { 180 | return resp, err 181 | } 182 | return resp, nil 183 | } 184 | 185 | // DirectRefundTransaction : Do `/{orderId}/refund/online/direct` API request to Midtrans Core API return `coreapi.RefundResponse`, 186 | // with `coreapi.RefundReq` as body parameter, will be converted to JSON, 187 | // more detail refer to: https://api-docs.midtrans.com/#direct-refund-transaction 188 | func DirectRefundTransaction(param string, req *RefundReq) (*RefundResponse, *midtrans.Error) { 189 | return getDefaultClient().DirectRefundTransaction(param, req) 190 | } 191 | 192 | // CaptureTransaction : Do `/{orderId}/capture` API request to Midtrans Core API return `coreapi.CaptureResponse`, 193 | // with `coreapi.CaptureReq` as body parameter, will be converted to JSON, 194 | // more detail refer to: https://api-docs.midtrans.com/#capture-transaction 195 | func (c Client) CaptureTransaction(req *CaptureReq) (*CaptureResponse, *midtrans.Error) { 196 | resp := &CaptureResponse{} 197 | jsonReq, _ := json.Marshal(req) 198 | err := c.HttpClient.Call( 199 | http.MethodPost, 200 | fmt.Sprintf("%s/v2/capture", c.Env.BaseUrl()), 201 | &c.ServerKey, 202 | c.Options, 203 | bytes.NewBuffer(jsonReq), 204 | resp, 205 | ) 206 | 207 | if err != nil { 208 | return resp, err 209 | } 210 | return resp, nil 211 | } 212 | 213 | // CaptureTransaction : Do `/{orderId}/capture` API request to Midtrans Core API return `coreapi.CaptureResponse`, 214 | // with `coreapi.CaptureReq` as body parameter, will be converted to JSON, 215 | // more detail refer to: https://api-docs.midtrans.com/#capture-transaction 216 | func CaptureTransaction(req *CaptureReq) (*CaptureResponse, *midtrans.Error) { 217 | return getDefaultClient().CaptureTransaction(req) 218 | } 219 | 220 | // GetStatusB2B : Do `/{orderId}/status/b2b` API request to Midtrans Core API return `coreapi.TransactionStatusB2bResponse`, 221 | // more detail refer to: https://api-docs.midtrans.com/#get-transaction-status-b2b 222 | func (c Client) GetStatusB2B(param string) (*TransactionStatusB2bResponse, *midtrans.Error) { 223 | resp := &TransactionStatusB2bResponse{} 224 | err := c.HttpClient.Call( 225 | http.MethodGet, 226 | fmt.Sprintf("%s/v2/%s/status/b2b", c.Env.BaseUrl(), param), 227 | &c.ServerKey, 228 | c.Options, 229 | nil, 230 | resp, 231 | ) 232 | 233 | if err != nil { 234 | return resp, err 235 | } 236 | return resp, nil 237 | } 238 | 239 | // GetStatusB2B : Do `/{orderId}/status/b2b` API request to Midtrans Core API return `coreapi.TransactionStatusB2bResponse`, 240 | // more detail refer to: https://api-docs.midtrans.com/#get-transaction-status-b2b 241 | func GetStatusB2B(param string) (*TransactionStatusB2bResponse, *midtrans.Error) { 242 | return getDefaultClient().GetStatusB2B(param) 243 | } 244 | -------------------------------------------------------------------------------- /coreapi/clienttransaction_test.go: -------------------------------------------------------------------------------- 1 | package coreapi 2 | 3 | import ( 4 | "github.com/midtrans/midtrans-go" 5 | assert "github.com/stretchr/testify/require" 6 | "testing" 7 | ) 8 | 9 | func TestCheckTransaction(t *testing.T) { 10 | midtrans.ServerKey = sandboxServerKey 11 | _, err := CheckTransaction("DUMMY") 12 | assert.Equal(t, err.GetStatusCode(), 404) 13 | 14 | c := Client{} 15 | c.New(sandboxServerKey, midtrans.Sandbox) 16 | _, err2 := c.CheckTransaction("DUMMY") 17 | assert.Equal(t, err2.StatusCode, 404) 18 | } 19 | 20 | func TestApproveTransaction(t *testing.T) { 21 | midtrans.ServerKey = sandboxServerKey 22 | _, err := ApproveTransaction("DUMMY") 23 | assert.Equal(t, err.GetStatusCode(), 404) 24 | 25 | c := Client{} 26 | c.New(sandboxServerKey, midtrans.Sandbox) 27 | _, err2 := c.ApproveTransaction("DUMMY") 28 | assert.Equal(t, err2.StatusCode, 404) 29 | } 30 | 31 | func TestDenyTransaction(t *testing.T) { 32 | midtrans.ServerKey = sandboxServerKey 33 | _, err := DenyTransaction("DUMMY") 34 | assert.Equal(t, err.GetStatusCode(), 404) 35 | 36 | c := Client{} 37 | c.New(sandboxServerKey, midtrans.Sandbox) 38 | _, err2 := c.DenyTransaction("DUMMY") 39 | assert.Equal(t, err2.StatusCode, 404) 40 | } 41 | 42 | func TestCancelTransaction(t *testing.T) { 43 | midtrans.ServerKey = sandboxServerKey 44 | _, err := CancelTransaction("DUMMY") 45 | assert.Equal(t, err.GetStatusCode(), 404) 46 | 47 | c := Client{} 48 | c.New(sandboxServerKey, midtrans.Sandbox) 49 | _, err2 := c.CancelTransaction("DUMMY") 50 | assert.Equal(t, err2.StatusCode, 404) 51 | } 52 | 53 | func TestExpireTransaction(t *testing.T) { 54 | midtrans.ServerKey = sandboxServerKey 55 | _, err := ExpireTransaction("DUMMY") 56 | assert.Equal(t, err.GetStatusCode(), 404) 57 | 58 | c := Client{} 59 | c.New(sandboxServerKey, midtrans.Sandbox) 60 | _, err2 := c.ExpireTransaction("DUMMY") 61 | assert.Equal(t, err2.StatusCode, 404) 62 | } 63 | 64 | func TestRefundTransaction(t *testing.T) { 65 | refundReq := &RefundReq{ 66 | Amount: 10000, 67 | Reason: "Out of stock", 68 | } 69 | midtrans.ServerKey = sandboxServerKey 70 | _, err1 := RefundTransaction("DUMMY", refundReq) 71 | assert.Equal(t, err1.StatusCode, 404) 72 | 73 | c := Client{} 74 | c.New(sandboxServerKey, midtrans.Sandbox) 75 | _, err2 := c.RefundTransaction("DUMMY", refundReq) 76 | assert.Equal(t, err2.StatusCode, 404) 77 | } 78 | 79 | func TestDirectRefundTransaction(t *testing.T) { 80 | refundReq := &RefundReq{ 81 | RefundKey: "ORDER-ID-UNIQUE-ID", 82 | Amount: 10000, 83 | Reason: "Out of stock", 84 | } 85 | midtrans.ServerKey = sandboxServerKey 86 | _, err1 := DirectRefundTransaction("DUMMY", refundReq) 87 | assert.NotNil(t, err1) 88 | 89 | c := Client{} 90 | c.New(sandboxServerKey, midtrans.Sandbox) 91 | _, err2 := c.DirectRefundTransaction("DUMMY", refundReq) 92 | assert.NotNil(t, err2) 93 | } 94 | 95 | func TestCaptureTransaction(t *testing.T) { 96 | reqCapture := &CaptureReq{ 97 | TransactionID: "DUMMY", 98 | GrossAmt: 10000, 99 | } 100 | midtrans.ServerKey = sandboxServerKey 101 | _, err := CaptureTransaction(reqCapture) 102 | assert.Equal(t, err.GetStatusCode(), 404) 103 | 104 | c := Client{} 105 | c.New(sandboxServerKey, midtrans.Sandbox) 106 | _, err2 := c.CaptureTransaction(reqCapture) 107 | assert.Equal(t, err2.StatusCode, 404) 108 | } 109 | 110 | func TestGetStatusB2B(t *testing.T) { 111 | midtrans.ServerKey = sandboxServerKey 112 | _, err1 := GetStatusB2B("DUMMY") 113 | assert.Equal(t, err1.StatusCode, 404) 114 | 115 | c := Client{} 116 | c.New(sandboxServerKey, midtrans.Sandbox) 117 | _, err2 := GetStatusB2B("DUMMY") 118 | assert.Equal(t, err2.StatusCode, 404) 119 | } 120 | -------------------------------------------------------------------------------- /coreapi/paymenttype.go: -------------------------------------------------------------------------------- 1 | package coreapi 2 | 3 | type CoreapiPaymentType string 4 | type SubscriptionPaymentType = CoreapiPaymentType 5 | 6 | const ( 7 | // PaymentTypeBankTransfer : bank_transfer 8 | PaymentTypeBankTransfer CoreapiPaymentType = "bank_transfer" 9 | 10 | // PaymentTypeGopay : gopay 11 | PaymentTypeGopay CoreapiPaymentType = "gopay" 12 | 13 | // PaymentTypeShopeepay : shopeepay 14 | PaymentTypeShopeepay CoreapiPaymentType = "shopeepay" 15 | 16 | // PaymentTypeQris : qris 17 | PaymentTypeQris CoreapiPaymentType = "qris" 18 | 19 | // PaymentTypeCreditCard : credit_card 20 | PaymentTypeCreditCard CoreapiPaymentType = "credit_card" 21 | 22 | // PaymentTypeEChannel : echannel 23 | PaymentTypeEChannel CoreapiPaymentType = "echannel" 24 | 25 | // PaymentTypeBCAKlikpay : bca_klikpay 26 | PaymentTypeBCAKlikpay CoreapiPaymentType = "bca_klikpay" 27 | 28 | // PaymentTypeKlikBca : bca_klikbca 29 | PaymentTypeKlikBca CoreapiPaymentType = "bca_klikbca" 30 | 31 | // PaymentTypeBRIEpay : bri_epay 32 | PaymentTypeBRIEpay CoreapiPaymentType = "bri_epay" 33 | 34 | // PaymentTypeCimbClicks : cimb_clicks 35 | PaymentTypeCimbClicks CoreapiPaymentType = "cimb_clicks" 36 | 37 | // PaymentTypeDanamonOnline : danamon_online 38 | PaymentTypeDanamonOnline CoreapiPaymentType = "danamon_online" 39 | 40 | // PaymentTypeConvenienceStore : cstore 41 | PaymentTypeConvenienceStore CoreapiPaymentType = "cstore" 42 | 43 | // PaymentTypeAkulaku : akulaku 44 | PaymentTypeAkulaku CoreapiPaymentType = "akulaku" 45 | 46 | // PaymentTypeMandiriClickpay : mandiri_clickpay 47 | PaymentTypeMandiriClickpay CoreapiPaymentType = "mandiri_clickpay" 48 | ) 49 | -------------------------------------------------------------------------------- /coreapi/request.go: -------------------------------------------------------------------------------- 1 | package coreapi 2 | 3 | import ( 4 | "github.com/midtrans/midtrans-go" 5 | ) 6 | 7 | // ChargeReqWithMap : Represent Charge request with map payload 8 | type ChargeReqWithMap map[string]interface{} 9 | 10 | // ChargeReq : Represent Charge request payload 11 | type ChargeReq struct { 12 | PaymentType CoreapiPaymentType `json:"payment_type"` 13 | TransactionDetails midtrans.TransactionDetails `json:"transaction_details"` 14 | 15 | Items *[]midtrans.ItemDetails `json:"item_details,omitempty"` 16 | CustomerDetails *midtrans.CustomerDetails `json:"customer_details,omitempty"` 17 | 18 | CreditCard *CreditCardDetails `json:"credit_card,omitempty"` 19 | BankTransfer *BankTransferDetails `json:"bank_transfer,omitempty"` 20 | EChannel *EChannelDetail `json:"echannel,omitempty"` 21 | Gopay *GopayDetails `json:"gopay,omitempty"` 22 | ShopeePay *ShopeePayDetails `json:"shopeepay,omitempty"` 23 | Qris *QrisDetails `json:"qris,omitempty"` 24 | BCAKlikPay *BCAKlikPayDetails `json:"bca_klikpay,omitempty"` 25 | BCAKlikBCA *BcaKlikBCADetails `json:"bca_klikbca,omitempty"` 26 | MandiriClickPay *MandiriClickPayDetails `json:"mandiri_clickpay,omitempty"` 27 | CIMBClicks *CIMBClicksDetails `json:"cimb_clicks,omitempty"` 28 | 29 | ConvStore *ConvStoreDetails `json:"cstore,omitempty"` 30 | 31 | CustomExpiry *CustomExpiry `json:"custom_expiry,omitempty"` 32 | CustomField1 *string `json:"custom_field1,omitempty"` 33 | CustomField2 *string `json:"custom_field2,omitempty"` 34 | CustomField3 *string `json:"custom_field3,omitempty"` 35 | Metadata interface{} `json:"metadata,omitempty"` 36 | } 37 | 38 | // CreditCardDetails : Represent credit card detail for PaymentTypeCreditCard payment type 39 | type CreditCardDetails struct { 40 | // TokenID represents customer credit card information 41 | TokenID string `json:"token_id"` 42 | 43 | // Authentication Flag to enable the 3D secure authentication. Default value is false. 44 | Authentication bool `json:"authentication,omitempty"` 45 | 46 | // Bank Acquiring bank. Valid values: `midtrans.BankBca` `midtrans.BankMandiri`, `midtrans.BankBni`, 47 | //`midtrans.BankCimb`, `midtrans.BankMaybank`, and `midtrans.BankBri` 48 | Bank string `json:"bank,omitempty"` 49 | 50 | // InstallmentTerm for installment tenor 51 | InstallmentTerm int8 `json:"installment_term,omitempty"` 52 | 53 | // Type Used on preauthorization feature. Valid value: authorize 54 | Type string `json:"type,omitempty"` 55 | 56 | // Bins List of credit card's BIN (Bank Identification Number) that is allowed for transaction 57 | Bins []string `json:"bins,omitempty"` 58 | 59 | // SaveTokenID Used on 'one click' or 'two clicks' feature. Enabling it will return a `Response.SavedCardTokenID` on the response 60 | // and notification body that can be used for the next transaction 61 | SaveTokenID bool `json:"save_token_id,omitempty"` 62 | 63 | // PointRedeemAmount For Mandiri Point, you can only do Full Redemption.(use -1 for Full Redemption) 64 | PointRedeemAmount int64 `json:"point_redeem_amount,omitempty"` 65 | 66 | // Determines how the transaction status is updated to the merchant frontend. Possible values are js_event (default) and form 67 | CallbackType string `json:"callback_type,omitempty"` 68 | } 69 | 70 | // BankTransferDetails : Represent bank_transfer detail 71 | type BankTransferDetails struct { 72 | Bank midtrans.Bank `json:"bank"` 73 | VaNumber string `json:"va_number,omitempty"` 74 | Permata *PermataBankTransferDetail `json:"permata,omitempty"` 75 | FreeText *BCABankTransferDetailFreeText `json:"free_text,omitempty"` 76 | Bca *BcaBankTransferDetail `json:"bca,omitempty"` 77 | } 78 | 79 | // PermataBankTransferDetail : Represent Recipient for bank transfer Permata 80 | type PermataBankTransferDetail struct { 81 | RecipientName string `json:"recipient_name,omitempty"` 82 | } 83 | 84 | // BCABankTransferDetailFreeText : Represent BCA bank_transfer detail free_text 85 | type BCABankTransferDetailFreeText struct { 86 | Inquiry []BCABankTransferLangDetail `json:"inquiry,omitempty"` 87 | Payment []BCABankTransferLangDetail `json:"payment,omitempty"` 88 | } 89 | 90 | // BCABankTransferLangDetail : Represent BCA bank_transfer lang detail 91 | type BCABankTransferLangDetail struct { 92 | LangID string `json:"id,omitempty"` 93 | LangEN string `json:"en,omitempty"` 94 | } 95 | 96 | // BcaBankTransferDetail : BCA sub company code directed for this transactions 97 | // NOTE: Please contact Midtrans Sales Team. 98 | type BcaBankTransferDetail struct { 99 | SubCompanyCode string `json:"sub_company_code,omitempty"` 100 | } 101 | 102 | // EChannelDetail : Represent Mandiri Bill bank transfer detail 103 | type EChannelDetail struct { 104 | BillInfo1 string `json:"bill_info1"` 105 | BillInfo2 string `json:"bill_info2"` 106 | BillInfo3 string `json:"bill_info3,omitempty"` 107 | BillInfo4 string `json:"bill_info4,omitempty"` 108 | BillInfo5 string `json:"bill_info5,omitempty"` 109 | BillInfo6 string `json:"bill_info6,omitempty"` 110 | BillInfo7 string `json:"bill_info7,omitempty"` 111 | BillInfo8 string `json:"bill_info8,omitempty"` 112 | BillKey string `json:"bill_key,omitempty"` 113 | } 114 | 115 | // BCAKlikPayDetails : Represent Internet Banking for BCA KlikPay 116 | type BCAKlikPayDetails struct { 117 | Desc string `json:"description"` 118 | MiscFee int64 `json:"misc_fee,omitempty"` 119 | } 120 | 121 | // BcaKlikBCADetails : Represent Internet Banking BCA KlikBCA detail 122 | type BcaKlikBCADetails struct { 123 | Desc string `json:"description"` 124 | UserID string `json:"user_id"` 125 | } 126 | 127 | // MandiriClickPayDetails : Represent Mandiri ClickPay detail 128 | type MandiriClickPayDetails struct { 129 | // TokenID token id from Get card token Step 130 | TokenID string `json:"token_id"` 131 | Input1 string `json:"input1"` 132 | Input2 string `json:"input2"` 133 | 134 | // Input3 5-digits random number you gave to the customer 135 | Input3 string `json:"input3"` 136 | 137 | // Token Number generated by customer's physical token 138 | Token string `json:"token"` 139 | } 140 | 141 | // CIMBClicksDetails : Represent CIMB Clicks detail 142 | type CIMBClicksDetails struct { 143 | Desc string `json:"description"` 144 | } 145 | 146 | // QrisDetails QRIS is a QR payment standard in Indonesia that is developed by Bank Indonesia (BI). 147 | // Users could scan and pay the QR from any payment providers registered as the issuer 148 | type QrisDetails struct { 149 | Acquirer string `json:"acquirer,omitempty"` 150 | } 151 | 152 | // ConvStoreDetails : Represent cstore detail 153 | type ConvStoreDetails struct { 154 | Store string `json:"store"` 155 | Message string `json:"message,omitempty"` 156 | 157 | AlfamartFreeText1 string `json:"alfamart_free_text_1,omitempty"` 158 | AlfamartFreeText2 string `json:"alfamart_free_text_2,omitempty"` 159 | AlfamartFreeText3 string `json:"alfamart_free_text_3,omitempty"` 160 | } 161 | 162 | // GopayDetails : Represent gopay detail 163 | type GopayDetails struct { 164 | EnableCallback bool `json:"enable_callback,omitempty"` // To determine appending callback url in the deeplink. Default value: false 165 | CallbackUrl string `json:"callback_url,omitempty"` // To determine where GO-JEK apps will redirect after successful payment. Can be HTTP or deeplink url. Default value: callback_url in dashboard settings 166 | AccountID string `json:"account_id,omitempty"` // Required for GoPay tokenization. Linked customer account ID from create pay account API. 167 | PaymentOptionToken string `json:"payment_option_token,omitempty"` // Required for GoPay tokenization. Token to specify the payment option made by the customer from get pay account API metadata. 168 | PreAuth bool `json:"pre_auth,omitempty"` // To make payment mode into reservation of customer balance only. Once, customer balance is reserved, a subsequent capture call is expected to be initiated by merchants. 169 | Recurring bool `json:"recurring,omitempty"` 170 | } 171 | 172 | // ShopeePayDetails : Represent shopeepay detail 173 | type ShopeePayDetails struct { 174 | CallbackUrl string `json:"callback_url,omitempty"` 175 | } 176 | 177 | // CustomExpiry : Represent Core API custom_expiry 178 | type CustomExpiry struct { 179 | // OrderTime Time when the order is created in merchant website. Format: yyyy-MM-dd hh:mm:ss Z. 180 | // If attribute undefined, expiry time starts from transaction time 181 | OrderTime string `json:"order_time,omitempty"` 182 | 183 | // ExpiryDuration Time duration the payment will remain valid 184 | ExpiryDuration int `json:"expiry_duration,omitempty"` 185 | 186 | // Unit for expiry_duration. Valid values are: second, minute, hour, or day. 187 | // NOTE: If attribute undefined, default unit is minute 188 | Unit string `json:"unit,omitempty"` 189 | } 190 | 191 | // CaptureReq : Represent Capture request payload 192 | type CaptureReq struct { 193 | TransactionID string `json:"transaction_id"` 194 | GrossAmt float64 `json:"gross_amount"` 195 | } 196 | 197 | // RefundReq : Represent Refund request payload 198 | type RefundReq struct { 199 | RefundKey string `json:"refund_key"` 200 | Amount int64 `json:"amount"` 201 | Reason string `json:"reason"` 202 | } 203 | 204 | type SubscriptionReq struct { 205 | // Name Subscription's name that will be used to generate transaction's order id. 206 | // Note: Allowed symbols are dash(-), underscore(_), tilde (~), and dot (.) 207 | Name string `json:"name"` 208 | 209 | // Amount that will be used to make recurring charge. Note: Do not use decimal 210 | Amount int64 `json:"amount"` 211 | 212 | // Currency ISO-4217 representation for 3 digit alphabetic currency code. Note: Currently only support IDR 213 | Currency string `json:"currency"` 214 | 215 | // PaymentType Transaction payment method. Note: currently only support credit_card and gopay 216 | PaymentType SubscriptionPaymentType `json:"payment_type"` 217 | 218 | // Token Saved payment token. Note: For `credit_card` should use `saved_token_id` received in charge response. 219 | // For gopay should use payment_options. token received in get pay account response 220 | Token string `json:"token"` 221 | 222 | // Schedule Subscription schedule details 223 | Schedule ScheduleDetails `json:"schedule"` 224 | 225 | // Metadata of subscription from merchant, the size must be less than 1KB 226 | Metadata interface{} `json:"metadata,omitempty"` 227 | 228 | // CustomerDetails Customer details information 229 | CustomerDetails *midtrans.CustomerDetails `json:"customer_details,omitempty"` 230 | 231 | // Gopay subscription information, required if payment type is gopay 232 | Gopay *GopaySubscriptionDetails `json:"gopay,omitempty"` 233 | } 234 | 235 | type GopaySubscriptionDetails struct { 236 | AccountId string `json:"account_id"` // Gopay Account ID from Core API 237 | } 238 | 239 | //ScheduleDetails Create Subscription schedule object 240 | type ScheduleDetails struct { 241 | // Subscription's interval given by merchant 242 | Interval int `json:"interval"` 243 | 244 | // Interval temporal unit Note: currently only support day, week, and month 245 | IntervalUnit string `json:"interval_unit"` 246 | 247 | // MaxInterval Maximum interval of subscription. Subscription will end after maximum interval is reached 248 | MaxInterval int `json:"max_interval"` 249 | 250 | // StartTime Timestamp of subscription, format: yyyy-MM-dd HH:mm:ss Z. The value must be after the current time. 251 | // If specified, first payment will happen on start_time. If start_time is not specified, the default value for 252 | // start_time will be current time and first payment will happen on one interval after current time. 253 | StartTime string `json:"start_time,omitempty"` 254 | } 255 | 256 | type PaymentAccountReq struct { 257 | PaymentType CoreapiPaymentType `json:"payment_type"` // Payment channel where the account register to 258 | GopayPartner *GopayPartnerDetails `json:"gopay_partner"` // GoPay linking specific parameters 259 | } 260 | 261 | type GopayPartnerDetails struct { 262 | PhoneNumber string `json:"phone_number"` // Phone number linked to the customer account 263 | CountryCode string `json:"country_code"` // Country code associated to the phone number 264 | RedirectURL string `json:"redirect_url,omitempty"` // URL where user will be redirected to after finishing the confirmation on Gojek app 265 | } 266 | -------------------------------------------------------------------------------- /coreapi/response.go: -------------------------------------------------------------------------------- 1 | package coreapi 2 | 3 | import "github.com/midtrans/midtrans-go" 4 | 5 | type ResponseWithMap map[string]interface{} 6 | 7 | // VANumber : bank virtual account number 8 | type VANumber struct { 9 | Bank string `json:"bank"` 10 | VANumber string `json:"va_number"` 11 | } 12 | 13 | // Action represents response action 14 | type Action struct { 15 | Name string `json:"name"` 16 | Method string `json:"method"` 17 | URL string `json:"url"` 18 | Fields []string `json:"fields"` 19 | } 20 | 21 | type PaymentAmount struct { 22 | PaidAt string `json:"paid_at"` 23 | Amount string `json:"amount"` 24 | } 25 | 26 | // ChargeResponse : CoreAPI charge response struct when calling Midtrans API 27 | type ChargeResponse struct { 28 | TransactionID string `json:"transaction_id"` 29 | OrderID string `json:"order_id"` 30 | GrossAmount string `json:"gross_amount"` 31 | PaymentType string `json:"payment_type"` 32 | TransactionTime string `json:"transaction_time"` 33 | TransactionStatus string `json:"transaction_status"` 34 | FraudStatus string `json:"fraud_status"` 35 | MaskedCard string `json:"masked_card"` 36 | StatusCode string `json:"status_code"` 37 | Bank string `json:"bank"` 38 | StatusMessage string `json:"status_message"` 39 | ApprovalCode string `json:"approval_code"` 40 | ChannelResponseCode string `json:"channel_response_code"` 41 | ChannelResponseMessage string `json:"channel_response_message"` 42 | Currency string `json:"currency"` 43 | CardType string `json:"card_type"` 44 | RedirectURL string `json:"redirect_url"` 45 | ID string `json:"id"` 46 | ValidationMessages []string `json:"validation_messages"` 47 | InstallmentTerm string `json:"installment_term"` 48 | Eci string `json:"eci"` 49 | SavedTokenID string `json:"saved_token_id"` 50 | SavedTokenIDExpiredAt string `json:"saved_token_id_expired_at"` 51 | PointRedeemAmount int `json:"point_redeem_amount"` 52 | PointRedeemQuantity int `json:"point_redeem_quantity"` 53 | PointBalanceAmount string `json:"point_balance_amount"` 54 | PermataVaNumber string `json:"permata_va_number"` 55 | VaNumbers []VANumber `json:"va_numbers"` 56 | BillKey string `json:"bill_key"` 57 | BillerCode string `json:"biller_code"` 58 | Acquirer string `json:"acquirer"` 59 | Actions []Action `json:"actions"` 60 | PaymentCode string `json:"payment_code"` 61 | Store string `json:"store"` 62 | QRString string `json:"qr_string"` 63 | OnUs bool `json:"on_us"` 64 | ThreeDsVersion string `json:"three_ds_version"` 65 | ExpiryTime string `json:"expiry_time"` 66 | } 67 | 68 | // ApproveResponse : Approve response type when calling Midtrans approve transaction API 69 | type ApproveResponse = ChargeResponse 70 | 71 | // DenyResponse : Deny response type when calling Midtrans deny transaction API 72 | type DenyResponse = ChargeResponse 73 | 74 | // CancelResponse : Cancel response type when calling Midtrans cancel transaction API 75 | type CancelResponse = ChargeResponse 76 | 77 | // ExpireResponse : Expire response type when calling Midtrans expire transaction API 78 | type ExpireResponse = ChargeResponse 79 | 80 | // CaptureResponse : Capture response type when calling Midtrans API capture for credit card transaction 81 | type CaptureResponse = ChargeResponse 82 | 83 | // TransactionStatusResponse : Status transaction response struct 84 | type TransactionStatusResponse struct { 85 | TransactionTime string `json:"transaction_time"` 86 | GrossAmount string `json:"gross_amount"` 87 | Currency string `json:"currency"` 88 | OrderID string `json:"order_id"` 89 | PaymentType string `json:"payment_type"` 90 | SignatureKey string `json:"signature_key"` 91 | StatusCode string `json:"status_code"` 92 | TransactionID string `json:"transaction_id"` 93 | TransactionStatus string `json:"transaction_status"` 94 | FraudStatus string `json:"fraud_status"` 95 | SettlementTime string `json:"settlement_time"` 96 | StatusMessage string `json:"status_message"` 97 | MerchantID string `json:"merchant_id"` 98 | PermataVaNumber string `json:"permata_va_number"` 99 | VaNumbers []VANumber `json:"va_numbers"` 100 | PaymentAmounts []PaymentAmount `json:"payment_amounts"` 101 | ID string `json:"id"` 102 | PaymentCode string `json:"payment_code"` 103 | Store string `json:"store"` 104 | MaskedCard string `json:"masked_card"` 105 | Bank string `json:"bank"` 106 | ApprovalCode string `json:"approval_code"` 107 | Eci string `json:"eci"` 108 | ChannelResponseCode string `json:"channel_response_code"` 109 | ChannelResponseMessage string `json:"channel_response_message"` 110 | CardType string `json:"card_type"` 111 | Refunds []RefundDetails `json:"refunds"` 112 | RefundAmount string `json:"refund_amount"` 113 | BillKey string `json:"bill_key"` 114 | BillerCode string `json:"biller_code"` 115 | TransactionType string `json:"transaction_type"` 116 | Issuer string `json:"issuer"` 117 | Acquirer string `json:"acquirer"` 118 | CustomField1 string `json:"custom_field1"` 119 | CustomField2 string `json:"custom_field2"` 120 | CustomField3 string `json:"custom_field3"` 121 | Metadata interface{} `json:"metadata"` 122 | PaymentOptionsType string `json:"payment_options_type"` 123 | InstallmentTerm int `json:"installment_term"` 124 | ThreeDsVersion string `json:"three_ds_version"` 125 | ExpiryTime string `json:"expiry_time"` 126 | } 127 | 128 | type TransactionStatusB2bResponse struct { 129 | StatusCode string `json:"status_code"` 130 | StatusMessage string `json:"status_message"` 131 | ID string `json:"id"` 132 | Transactions []TransactionStatusResponse `json:"transactions"` 133 | } 134 | 135 | // RefundDetails Details 136 | type RefundDetails struct { 137 | RefundChargebackID int `json:"refund_chargeback_id"` 138 | RefundChargebackUUID string `json:"refund_chargeback_uuid"` 139 | RefundAmount string `json:"refund_amount"` 140 | Reason string `json:"reason"` 141 | RefundKey string `json:"refund_key"` 142 | RefundMethod string `json:"refund_method"` 143 | BankConfirmedAt string `json:"bank_confirmed_at"` 144 | CreatedAt string `json:"created_at"` 145 | } 146 | 147 | // RefundResponse : Refund response struct when calling Midtrans refund and direct refund API 148 | type RefundResponse struct { 149 | StatusCode string `json:"status_code"` 150 | StatusMessage string `json:"status_message"` 151 | ID string `json:"id"` 152 | TransactionID string `json:"transaction_id"` 153 | OrderID string `json:"order_id"` 154 | GrossAmount string `json:"gross_amount"` 155 | Currency string `json:"currency"` 156 | MerchantID string `json:"merchant_id"` 157 | PaymentType string `json:"payment_type"` 158 | TransactionTime string `json:"transaction_time"` 159 | TransactionStatus string `json:"transaction_status"` 160 | SettlementTime string `json:"settlement_time"` 161 | FraudStatus string `json:"fraud_status"` 162 | RefundChargebackID int `json:"refund_chargeback_id"` 163 | RefundChargebackUUID string `json:"refund_chargeback_uuid"` 164 | RefundAmount string `json:"refund_amount"` 165 | RefundKey string `json:"refund_key"` 166 | } 167 | 168 | type CardTokenResponse struct { 169 | StatusCode string `json:"status_code"` 170 | StatusMessage string `json:"status_message"` 171 | ValidationMessage []string `json:"validation_messages"` 172 | Id string `json:"id"` 173 | TokenID string `json:"token_id"` 174 | Hash string `json:"hash"` 175 | RedirectURL string `json:"redirect_url"` 176 | Bank string `json:"bank"` 177 | } 178 | 179 | type CardRegisterResponse struct { 180 | StatusCode string `json:"status_code"` 181 | StatusMessage string `json:"status_message"` 182 | ValidationMessage []string `json:"validation_messages"` 183 | Id string `json:"id"` 184 | SavedTokenID string `json:"saved_token_id"` 185 | TransactionID string `json:"transaction_id"` 186 | MaskCard string `json:"masked_card"` 187 | } 188 | 189 | type BinResponse struct { 190 | Data struct { 191 | RegistrationRequired bool `json:"registration_required"` 192 | CountryName string `json:"country_name"` 193 | CountryCode string `json:"country_code"` 194 | Channel string `json:"channel"` 195 | Brand string `json:"brand"` 196 | BinType string `json:"bin_type"` 197 | BinClass string `json:"bin_class"` 198 | Bin string `json:"bin"` 199 | BankCode string `json:"bank_code"` 200 | Bank string `json:"bank"` 201 | } `json:"data"` 202 | } 203 | 204 | type CreateSubscriptionResponse struct { 205 | ID string `json:"id"` 206 | Name string `json:"name"` 207 | Amount string `json:"amount"` 208 | Currency string `json:"currency"` 209 | CreatedAt string `json:"created_at"` 210 | Schedule ScheduleResponse `json:"schedule"` 211 | Status string `json:"status"` 212 | Token string `json:"token"` 213 | PaymentType string `json:"payment_type"` 214 | Metadata interface{} `json:"metadata"` 215 | CustomerDetails midtrans.CustomerDetails `json:"customer_details"` 216 | TransactionId []string `json:"transaction_id"` 217 | 218 | StatusMessage string `json:"status_message"` 219 | ValidationMessage []string `json:"validation_message"` 220 | } 221 | 222 | type StatusSubscriptionResponse = CreateSubscriptionResponse 223 | 224 | type UpdateSubscriptionResponse struct { 225 | StatusMessage string `json:"status_message"` 226 | } 227 | 228 | type EnableSubscriptionResponse = UpdateSubscriptionResponse 229 | type DisableSubscriptionResponse = UpdateSubscriptionResponse 230 | 231 | // ScheduleResponse Subscription schedule response object 232 | type ScheduleResponse struct { 233 | Interval int `json:"interval"` 234 | IntervalUnit string `json:"interval_unit"` 235 | MaxInterval int `json:"max_interval"` 236 | CurrentInterval int `json:"current_interval"` 237 | StartTime string `json:"start_time"` 238 | PreviousExecutionAt string `json:"previous_execution_at"` 239 | NextExecutionAt string `json:"next_execution_at"` 240 | } 241 | 242 | type PaymentAccountResponse struct { 243 | StatusCode string `json:"status_code"` 244 | PaymentType string `json:"payment_type"` 245 | AccountId string `json:"account_id"` 246 | AccountStatus string `json:"account_status"` 247 | ChannelResponseCode string `json:"channel_response_code"` 248 | ChannelResponseMessage string `json:"channel_response_message"` 249 | Actions []Action `json:"actions"` 250 | Metadata PaymentAccountMetadataDetails `json:"metadata"` 251 | StatusMessage string `json:"status_message"` 252 | ID string `json:"id"` 253 | } 254 | 255 | type PaymentAccountMetadataDetails struct { 256 | PaymentOptions []PaymentOptionsDetails `json:"payment_options"` 257 | } 258 | 259 | type PaymentOptionsDetails struct { 260 | Name string `json:"name"` 261 | Active bool `json:"active"` 262 | Metadata interface{} `json:"metadata"` 263 | Balance BalanceDetails `json:"balance"` 264 | Token string `json:"token"` 265 | } 266 | 267 | type BalanceDetails struct { 268 | Value string `json:"value"` 269 | Currency string `json:"currency"` 270 | } 271 | 272 | type PointInquiryResponse struct { 273 | StatusCode string `json:"status_code"` 274 | StatusMessage string `json:"status_message"` 275 | PointBalance int `json:"point_balance"` 276 | TransactionTime string `json:"transaction_time"` 277 | PointBalanceAmount string `json:"point_balance_amount"` 278 | } 279 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package midtrans 2 | 3 | import "fmt" 4 | 5 | type Error struct { 6 | Message string 7 | StatusCode int 8 | RawError error 9 | RawApiResponse *ApiResponse 10 | } 11 | 12 | // Error returns error message. 13 | // To comply midtrans.Error with Go error interface. 14 | func (e *Error) Error() string { 15 | if e.RawError != nil { 16 | return fmt.Sprintf("%s: %s", e.Message, e.RawError.Error()) 17 | } 18 | return e.Message 19 | } 20 | 21 | // Unwrap method that returns its contained error 22 | // if there is RawError supplied during error creation, return RawError. Else, will return nil 23 | func (e *Error) Unwrap() error { 24 | return e.RawError 25 | } 26 | 27 | // GetMessage this get general message error when call api 28 | func (e *Error) GetMessage() string { 29 | return e.Message 30 | } 31 | 32 | // GetStatusCode this get api response status code coming from midtrans backend 33 | func (e *Error) GetStatusCode() int { 34 | return e.StatusCode 35 | } 36 | 37 | // GetRawApiResponse this get api raw response from midtrans backend 38 | func (e *Error) GetRawApiResponse() *ApiResponse { 39 | return e.RawApiResponse 40 | } 41 | 42 | // GetRawError GetRawApiResponse this get api raw response from midtrans backend 43 | func (e *Error) GetRawError() error { 44 | return e.RawError 45 | } 46 | -------------------------------------------------------------------------------- /error_test.go: -------------------------------------------------------------------------------- 1 | package midtrans 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "net/http" 8 | "testing" 9 | 10 | assert "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestErrorStruct(t *testing.T) { 14 | err := &Error{ 15 | Message: "Error Test Message", 16 | StatusCode: 200, 17 | RawError: errors.New("TEST FROM GO ERROR"), 18 | RawApiResponse: nil, 19 | } 20 | var midError *Error 21 | assert.Error(t, err) 22 | assert.True(t, true, errors.Is(err, err)) 23 | assert.True(t, true, errors.As(err, &midError)) 24 | assert.Equal(t, `Error Test Message`, err.GetMessage()) 25 | assert.Equal(t, 200, err.GetStatusCode()) 26 | assert.Equal(t, "Error Test Message: TEST FROM GO ERROR", err.Error()) 27 | } 28 | 29 | func TestErrorResponse(t *testing.T) { 30 | serverKey := "dummy" 31 | c := GetHttpClient(Environment) 32 | jsonReq, _ := json.Marshal("{\"transaction_details\": {\"order_id\": \"TEST-1648108994111\", \"gross_amount\": 10000}}") 33 | err := c.Call(http.MethodPost, "https://app.midtrans.com/snap/v1/transactions", &serverKey, nil, bytes.NewBuffer(jsonReq), nil) 34 | 35 | var midError *Error 36 | assert.True(t, true, errors.Is(err, err)) 37 | assert.True(t, true, errors.As(err, &midError)) 38 | assert.Error(t, err) 39 | assert.Equal(t, 401, err.StatusCode) 40 | assert.Equal(t, "app.midtrans.com", err.RawApiResponse.Request.Host) 41 | } -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Sample App 2 | This is a very simple, very minimalist example to demonstrate integrating Midtrans with Go 3 | 4 | ## Run The app 5 | 1. Clone the repository, open terminal in this `/example/simple/coreapi-card-3ds` folder. 6 | 2. Run the web server using: `go run main.go` 7 | 3. The smple app will run at port 3000. Open `localhost:3000` from browser. 8 | 9 | ## Run command line app 10 | 1. Clone the repository, open terminal in this `/example/simple/coreapi` folder. 11 | 2. Run the app using: `go run main.go` 12 | -------------------------------------------------------------------------------- /example/mockup.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import ( 4 | "github.com/midtrans/midtrans-go" 5 | "github.com/midtrans/midtrans-go/coreapi" 6 | "github.com/midtrans/midtrans-go/snap" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | const SandboxServerKey1 = "SB-Mid-server-TvgWB_Y9s81-rbMBH7zZ8BHW" 12 | const SandboxServerKey2 = "SB-Mid-server-TOq1a2AVuiyhhOjvfs3U_KeO" 13 | 14 | const SandboxClientKey2 = "SB-Mid-client-nKsqvar5cn60u2Lv" 15 | 16 | 17 | const IrisCreatorKeySandbox = "IRIS-330198f0-e49d-493f-baae-585cfded355d" 18 | const IrisApproverKeySandbox = "IRIS-1595c12b-6814-4e5a-bbbb-9bc18193f47b" 19 | 20 | 21 | func SnapParamWithMap() *snap.RequestParamWithMap { 22 | req := &snap.RequestParamWithMap{ 23 | "transaction_details": map[string]interface{}{ 24 | "order_id": "MID-GO-TEST-" + Random(), 25 | "gross_amount": 10000, 26 | }, 27 | } 28 | return req 29 | 30 | } 31 | 32 | func SnapParam() *snap.Request { 33 | req := & snap.Request{ 34 | TransactionDetails: midtrans.TransactionDetails{ 35 | OrderID: "MID-GO-TEST-" + Random(), 36 | GrossAmt: 100000, 37 | }, 38 | } 39 | return req 40 | } 41 | 42 | func CoreParam() *coreapi.ChargeReqWithMap { 43 | req := &coreapi.ChargeReqWithMap{ 44 | "payment_type": "gopay", 45 | "transaction_details": map[string]interface{}{ 46 | "order_id": "MID-GO-TEST-" + Random(), 47 | "gross_amount": 10000, 48 | }, 49 | } 50 | return req 51 | } 52 | 53 | func Random() string { 54 | time.Sleep(500 * time.Millisecond) 55 | return strconv.FormatInt(time.Now().Unix(), 10) 56 | } -------------------------------------------------------------------------------- /example/simple/coreapi-card-3ds/main.go: -------------------------------------------------------------------------------- 1 | // This is just for very basic implementation reference, in production, you should validate the incoming requests and implement your backend more securely. 2 | 3 | package main 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "github.com/midtrans/midtrans-go" 9 | "github.com/midtrans/midtrans-go/coreapi" 10 | "html/template" 11 | "log" 12 | "net/http" 13 | "path" 14 | "strconv" 15 | "time" 16 | ) 17 | 18 | // Set Your server key 19 | // You can find it in Merchant Portal -> Settings -> Access keys 20 | const SERVER_KEY string = "SB-Mid-server-1isH_dlGSg6uy.I7NpeNK53i" 21 | const CLIENT_KEY string = "SB-Mid-client-yrY4WjUNOnhOyIIH" 22 | 23 | type CardTokenAndAuthRequest struct { 24 | TokenID string `json:"token_id"` 25 | Secure bool `json:"authenticate_3ds"` 26 | } 27 | 28 | type StatusTransactionRequest struct { 29 | TransactionID string `json:"transaction_id"` 30 | } 31 | 32 | func main() { 33 | mux := http.NewServeMux() 34 | mux.HandleFunc("/", HomeHandler) 35 | mux.HandleFunc("/charge_core_api_ajax", ChargeAjaxHandler) 36 | mux.HandleFunc("/check_transaction_status", StatusAjaxHandler) 37 | 38 | log.Println("Starting web on port 3000") 39 | err := http.ListenAndServe(":3000", mux) 40 | log.Fatal(err) 41 | } 42 | 43 | func HomeHandler(w http.ResponseWriter, r *http.Request) { 44 | 45 | fmt.Println(generateOrderIdSuffix) 46 | if r.URL.Path != "/" { 47 | http.NotFound(w, r) 48 | return 49 | } 50 | 51 | needCredential := false 52 | if len(SERVER_KEY) == 0 || len(CLIENT_KEY) == 0 { 53 | needCredential = true 54 | } 55 | 56 | templ, err := template.ParseFiles(path.Join("views", "index.html")) 57 | if err != nil { 58 | log.Println(err) 59 | http.Error(w, "template file is not found", http.StatusInternalServerError) 60 | return 61 | } 62 | 63 | data := map[string]interface{} { 64 | "clientKey": CLIENT_KEY, 65 | "needCredential": needCredential, 66 | } 67 | err = templ.Execute(w, data) 68 | if err != nil { 69 | log.Println(err) 70 | http.Error(w, "template file is not found", http.StatusInternalServerError) 71 | return 72 | } 73 | } 74 | 75 | func ChargeAjaxHandler(w http.ResponseWriter, r *http.Request) { 76 | var requestBody CardTokenAndAuthRequest 77 | err := json.NewDecoder(r.Body).Decode(&requestBody) 78 | if err != nil { 79 | http.Error(w, err.Error(), http.StatusBadRequest) 80 | return 81 | } 82 | 83 | var c = coreapi.Client{} 84 | c.New(SERVER_KEY, midtrans.Sandbox) 85 | 86 | chargeReq := &coreapi.ChargeReq{ 87 | PaymentType: coreapi.PaymentTypeCreditCard, 88 | TransactionDetails: midtrans.TransactionDetails{ 89 | OrderID: "MID-GO-TEST-" + generateOrderIdSuffix(), 90 | GrossAmt: 200000, 91 | }, 92 | CreditCard: &coreapi.CreditCardDetails{ 93 | TokenID: requestBody.TokenID, 94 | Authentication: requestBody.Secure, 95 | }, 96 | Items: &[]midtrans.ItemDetails{ 97 | midtrans.ItemDetails{ 98 | ID: "ITEM1", 99 | Price: 200000, 100 | Qty: 1, 101 | Name: "Someitem", 102 | }, 103 | }, 104 | } 105 | 106 | res, _ := c.ChargeTransaction(chargeReq) 107 | response, _ := json.Marshal(res) 108 | 109 | w.Header().Set("Content-Type", "application/json") 110 | w.Write(response) 111 | } 112 | 113 | func StatusAjaxHandler(w http.ResponseWriter, r *http.Request) { 114 | var requestBody StatusTransactionRequest 115 | err := json.NewDecoder(r.Body).Decode(&requestBody) 116 | if err != nil { 117 | http.Error(w, err.Error(), http.StatusBadRequest) 118 | return 119 | } 120 | 121 | var c = coreapi.Client{} 122 | c.New(SERVER_KEY, midtrans.Sandbox) 123 | res, _ := c.CheckTransaction(requestBody.TransactionID) 124 | response, _ := json.Marshal(res) 125 | 126 | w.Header().Set("Content-Type", "application/json") 127 | w.Write(response) 128 | } 129 | 130 | func generateOrderIdSuffix() string { 131 | return strconv.FormatInt(time.Now().Unix(), 10) 132 | } 133 | -------------------------------------------------------------------------------- /example/simple/coreapi-card-3ds/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Checkout 8 | 9 | 10 | 11 | 12 | {{if .needCredential}} 13 |

Please set your server key and client key from sandbox In file: main.go

14 | // Set Your server key 15 | // You can find it in Merchant Portal -> Settings -> Access keys 16 |
SERVER_KEY = "" 17 |
CLIENT_KEY = "" 18 |
19 | {{end}} 20 |

Checkout

21 |
22 |
23 | Checkout 24 | Field that may be presented to customer: 25 |

26 | 27 | 28 |

29 |

30 | 31 | 32 | / 33 | 34 |

35 |

36 | 37 | 38 |

39 | 40 | Fields that shouldn't be presented to the customer: 41 |

42 | 43 | 44 |

45 | 46 | 47 | 48 |
49 |
50 | 51 | 52 | Transaction Result: 53 |
 Awaiting transactions... 
54 | Transaction verified status result: 55 |
 Awaiting transactions... 
56 |
 57 |           Testing cards:
 58 | 
 59 |           For 3D Secure:
 60 |           Visa success              4811 1111 1111 1114
 61 |           Visa deny by bank         4711 1111 1111 1115
 62 |           Visa deny by FDS          4611 1111 1111 1116
 63 | 
 64 |           MasterCard success        5211 1111 1111 1117
 65 |           MasterCard deny by bank   5111 1111 1111 1118
 66 |           MasterCard deny by FDS    5411 1111 1111 1115
 67 | 
 68 |           Challenge by FDS          4511 1111 1111 1117 
 69 |       
70 |
71 | 72 |
73 | Check `main.go` file, func `ChargeAjaxHandler` for the backend implementation 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /example/simple/coreapi/sample.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "net/http/httptest" 9 | "strings" 10 | 11 | "github.com/midtrans/midtrans-go" 12 | "github.com/midtrans/midtrans-go/coreapi" 13 | "github.com/midtrans/midtrans-go/example" 14 | ) 15 | 16 | var c coreapi.Client 17 | 18 | func setupGlobalMidtransConfigApi() { 19 | midtrans.ServerKey = example.SandboxServerKey1 20 | // change value to `midtrans.Production`, if you want change the env to production 21 | midtrans.Environment = midtrans.Sandbox 22 | } 23 | 24 | func chargeWithMapGlobalConfig() { 25 | resp, err := coreapi.ChargeTransactionWithMap(example.CoreParam()) 26 | if err != nil { 27 | fmt.Println("Error coreapi api, with global config", err.GetMessage()) 28 | } 29 | fmt.Println("response coreapi api, with global config", resp) 30 | } 31 | 32 | func chargeTransactionWithMap() { 33 | // Optional: here is how if you want to set idempotency for this request 34 | c.Options.SetPaymentIdempotencyKey(example.Random()) 35 | // Optional: here is how if you want to set context for this request 36 | c.Options.SetContext(context.Background()) 37 | // Optional: here is how if you want to set payment override for this request 38 | c.Options.SetPaymentOverrideNotification("https://example.com") 39 | 40 | resp, err := c.ChargeTransactionWithMap(example.CoreParam()) 41 | if err != nil { 42 | fmt.Println("Error coreapi api", err.GetMessage()) 43 | } 44 | fmt.Println("response coreapi api", resp) 45 | } 46 | 47 | func getCardToken() string { 48 | midtrans.ClientKey = example.SandboxClientKey2 49 | resp, err := coreapi.CardToken("4105058689481467", 12, 2025, "123") 50 | if err != nil { 51 | fmt.Println("Error get card token", err.GetMessage()) 52 | } 53 | fmt.Println("response card token", resp) 54 | return resp.TokenID 55 | } 56 | 57 | func registerCard() { 58 | midtrans.ClientKey = example.SandboxClientKey2 59 | resp, err := coreapi.RegisterCard("4811111111111114", 12, 2025) 60 | if err != nil { 61 | fmt.Println("Error register card token", err.GetMessage()) 62 | } 63 | fmt.Println("response register card token", resp) 64 | } 65 | 66 | func cardPointInquiry() { 67 | midtrans.ServerKey = example.SandboxServerKey1 68 | resp, err := coreapi.CardPointInquiry(getCardToken()) 69 | if err != nil { 70 | fmt.Println("Error card point inquiry", err.GetMessage()) 71 | } 72 | fmt.Println("response card point inquiry", resp) 73 | } 74 | 75 | func getBin(bin string) { 76 | midtrans.ClientKey = example.SandboxClientKey2 77 | resp, err := coreapi.GetBIN(bin) 78 | if err != nil { 79 | fmt.Println("Error get bin", err.GetMessage()) 80 | } 81 | fmt.Println("response get bin", resp) 82 | } 83 | 84 | func requestCreditCard() { 85 | var c = coreapi.Client{} 86 | c.New(example.SandboxServerKey1, midtrans.Sandbox) 87 | 88 | chargeReq := &coreapi.ChargeReq{ 89 | PaymentType: coreapi.PaymentTypeCreditCard, 90 | TransactionDetails: midtrans.TransactionDetails{ 91 | OrderID: "12345", 92 | GrossAmt: 200000, 93 | }, 94 | CreditCard: &coreapi.CreditCardDetails{ 95 | TokenID: "YOUR-CC-TOKEN", 96 | Authentication: true, 97 | }, 98 | Items: &[]midtrans.ItemDetails{ 99 | { 100 | ID: "ITEM1", 101 | Price: 200000, 102 | Qty: 1, 103 | Name: "Someitem", 104 | }, 105 | }, 106 | } 107 | 108 | res, _ := c.ChargeTransaction(chargeReq) 109 | fmt.Println(res) 110 | } 111 | 112 | func main() { 113 | // 1. Setup with global config 114 | setupGlobalMidtransConfigApi() 115 | 116 | // Optional: here is how if you want to set append payment notification globally 117 | midtrans.SetPaymentAppendNotification("https://midtrans-java.herokuapp.com/notif/append1") 118 | // Optional: here is how if you want to set override payment notification globally 119 | midtrans.SetPaymentOverrideNotification("https://midtrans-java.herokuapp.com/notif/override") 120 | 121 | // 2. ChargeTransaction with global config 122 | chargeWithMapGlobalConfig() 123 | 124 | fmt.Println("################# REQUEST 2 FROM OBJECT ################") 125 | 126 | // 3. Using initialize object 127 | c.New(example.SandboxServerKey1, midtrans.Sandbox) 128 | 129 | // 4. ChargeTransaction from initial object 130 | chargeTransactionWithMap() 131 | 132 | // 5. Sample request card token 133 | getCardToken() 134 | 135 | // 6. Sample request card register 136 | registerCard() 137 | 138 | // 7. Sample request card point inquiry 139 | cardPointInquiry() 140 | 141 | // 8. Sample request BIN 142 | getBin("410505") 143 | 144 | // 9. Sample request charge with credit card 145 | requestCreditCard() 146 | 147 | w := httptest.NewRecorder() 148 | r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("{\n \"masked_card\": \"451111-1117\",\n \"bank\": \"bca\",\n \"eci\": \"06\",\n \"channel_response_code\": \"7\",\n \"channel_response_message\": \"Denied\",\n \"transaction_time\": \"2021-06-08 15:49:54\",\n \"gross_amount\": \"100000.00\",\n \"currency\": \"IDR\",\n \"payment_type\": \"credit_card\",\n \"signature_key\": \"76fe68ed1b7040c7c329356c1cd47819be3ccb8b056376ff3488bfa9af1db52a85ded0501b2dab1de56e5852982133a9ef7a47c54222abbe72288c2c4f591a71\",\n \"status_code\": \"202\",\n \"transaction_id\": \"36f3687e-05d4-4879-a428-fd6d1ffb786e\",\n \"transaction_status\": \"deny\",\n \"fraud_status\": \"challenge\",\n \"status_message\": \"Success, transaction is found\",\n \"merchant_id\": \"G812785002\",\n \"card_type\": \"credit\"\n}")) 149 | notification(w, r) 150 | } 151 | 152 | // notification : Midtrans-Go simple sample HTTP Notification handling 153 | func notification(w http.ResponseWriter, r *http.Request) { 154 | // 1. Initialize empty map 155 | var notificationPayload map[string]interface{} 156 | 157 | // 2. Parse JSON request body and use it to set json to payload 158 | err := json.NewDecoder(r.Body).Decode(¬ificationPayload) 159 | if err != nil { 160 | // do something on error when decode 161 | return 162 | } 163 | // 3. Get order-id from payload 164 | orderId, exists := notificationPayload["order_id"].(string) 165 | if !exists { 166 | // do something when key `order_id` not found 167 | return 168 | } 169 | 170 | // 4. Check transaction to Midtrans with param orderId 171 | transactionStatusResp, e := c.CheckTransaction(orderId) 172 | if e != nil { 173 | http.Error(w, e.GetMessage(), http.StatusInternalServerError) 174 | return 175 | } else { 176 | if transactionStatusResp != nil { 177 | // 5. Do set transaction status based on response from check transaction status 178 | if transactionStatusResp.TransactionStatus == "capture" { 179 | if transactionStatusResp.FraudStatus == "challenge" { 180 | // TODO set transaction status on your database to 'challenge' 181 | // e.g: 'Payment status challenged. Please take action on your Merchant Administration Portal 182 | } else if transactionStatusResp.FraudStatus == "accept" { 183 | // TODO set transaction status on your database to 'success' 184 | } 185 | } else if transactionStatusResp.TransactionStatus == "settlement" { 186 | // TODO set transaction status on your databaase to 'success' 187 | } else if transactionStatusResp.TransactionStatus == "deny" { 188 | // TODO you can ignore 'deny', because most of the time it allows payment retries 189 | // and later can become success 190 | } else if transactionStatusResp.TransactionStatus == "cancel" || transactionStatusResp.TransactionStatus == "expire" { 191 | // TODO set transaction status on your databaase to 'failure' 192 | } else if transactionStatusResp.TransactionStatus == "pending" { 193 | // TODO set transaction status on your databaase to 'pending' / waiting payment 194 | } 195 | } 196 | } 197 | w.Header().Set("Content-Type", "application/json") 198 | w.Write([]byte("ok")) 199 | } 200 | -------------------------------------------------------------------------------- /example/simple/iris/sample.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/midtrans/midtrans-go" 6 | "github.com/midtrans/midtrans-go/example" 7 | "github.com/midtrans/midtrans-go/iris" 8 | "math/rand" 9 | "strconv" 10 | "time" 11 | ) 12 | 13 | var irisCreator iris.Client 14 | var irisApprover iris.Client 15 | 16 | func setupIrisGateway() { 17 | irisCreator.New(example.IrisCreatorKeySandbox, midtrans.Sandbox) 18 | irisApprover.New(example.IrisApproverKeySandbox, midtrans.Sandbox) 19 | } 20 | 21 | func GetBalance() { 22 | res, err := irisCreator.GetBalance() 23 | if err != nil { 24 | fmt.Println("Error: ", err.GetMessage()) 25 | } 26 | fmt.Println("Response: ", res) 27 | } 28 | 29 | func CreateAndUpdateBeneficiaries() { 30 | newBeneficiaries := mockBeneficiaries() 31 | resp, _ := irisCreator.CreateBeneficiaries(newBeneficiaries) 32 | fmt.Println("Iris Create beneficiary response: ", resp) 33 | 34 | getListAndUpdateBeneficiaries(newBeneficiaries) 35 | } 36 | 37 | func getListAndUpdateBeneficiaries(beneficiaries iris.Beneficiaries) { 38 | beneficiariesList, _ := irisCreator.GetBeneficiaries() 39 | 40 | b := iris.Beneficiaries{} 41 | for _, account := range beneficiariesList { 42 | if account.AliasName == beneficiaries.AliasName { 43 | b = account 44 | break 45 | } 46 | } 47 | 48 | updateBeneficiaries := iris.Beneficiaries{ 49 | Name: b.Name, 50 | Account: b.Account, 51 | Bank: b.Bank, 52 | AliasName: b.AliasName + "edt", 53 | Email: b.Email, 54 | } 55 | 56 | resp, _ := irisCreator.UpdateBeneficiaries(b.AliasName, updateBeneficiaries) 57 | fmt.Println("Iris Update Beneficiary: ", resp) 58 | } 59 | 60 | func createPayout() []iris.CreatePayoutDetailResponse { 61 | p := iris.CreatePayoutDetailReq{ 62 | BeneficiaryName: "Tony Stark", 63 | BeneficiaryAccount: "1380011819286", 64 | BeneficiaryBank: "mandiri", 65 | BeneficiaryEmail: "tony.stark@mail.com", 66 | Amount: random(), 67 | Notes: "MidGoUnitTestApproved", 68 | } 69 | var payouts []iris.CreatePayoutDetailReq 70 | payouts = append(payouts, p) 71 | 72 | cp := iris.CreatePayoutReq{Payouts: payouts} 73 | 74 | payoutReps, err := irisCreator.CreatePayout(cp) 75 | fmt.Println(payoutReps, err) 76 | 77 | return payoutReps.Payouts 78 | } 79 | 80 | func GetPayoutDetails(refNo string) { 81 | payoutReps, _ := irisCreator.GetPayoutDetails(refNo) 82 | fmt.Println("Iris Payout details", payoutReps) 83 | } 84 | 85 | func CreateAndApprovePayout() { 86 | var payouts = createPayout() 87 | 88 | var refNos []string 89 | refNos = append(refNos, payouts[0].ReferenceNo) 90 | 91 | ap := iris.ApprovePayoutReq{ 92 | ReferenceNo: refNos, 93 | OTP: "335163", 94 | } 95 | 96 | approveResp, _ := irisApprover.ApprovePayout(ap) 97 | fmt.Println("Iris Approve payout resp: ", approveResp) 98 | } 99 | 100 | func CreateAndRejectPayout() { 101 | var payouts = createPayout() 102 | 103 | var refNos []string 104 | refNos = append(refNos, payouts[0].ReferenceNo) 105 | 106 | ap := iris.RejectPayoutReq{ 107 | ReferenceNo: refNos, 108 | RejectReason: "MidGoUnitTest", 109 | } 110 | 111 | approveResp, _ := irisApprover.RejectPayout(ap) 112 | fmt.Println("Iris reject payout resp: ", approveResp) 113 | } 114 | 115 | func PayoutHistory() { 116 | fromDate, toDate := generateDate() 117 | 118 | resp, _ := irisApprover.GetTransactionHistory(fromDate, toDate) 119 | fmt.Println("Iris Payout history: ", resp) 120 | } 121 | 122 | func GetTopUpChannels() { 123 | resp, _ := irisApprover.GetTopUpChannels() 124 | fmt.Println("Iris TopUp Channels resp: ", resp) 125 | } 126 | 127 | func GetListBeneficiaryBank() { 128 | resp, _ := irisApprover.GetBeneficiaryBanks() 129 | fmt.Println("Iris Beneficiary Banks Resp: ", resp) 130 | } 131 | 132 | func ValidateBankAccount() { 133 | resp, _ := irisApprover.ValidateBankAccount("danamon", "000001137298") 134 | fmt.Println("Validate Bank Account Resp: ", resp) 135 | } 136 | 137 | func main() { 138 | setupIrisGateway() 139 | 140 | GetBalance() 141 | 142 | CreateAndUpdateBeneficiaries() 143 | 144 | GetPayoutDetails("ssss") 145 | 146 | CreateAndApprovePayout() 147 | 148 | CreateAndRejectPayout() 149 | 150 | PayoutHistory() 151 | 152 | GetTopUpChannels() 153 | 154 | GetListBeneficiaryBank() 155 | 156 | ValidateBankAccount() 157 | } 158 | 159 | func random() string { 160 | rand.Seed(time.Now().UnixNano()) 161 | return strconv.Itoa(rand.Intn(2000-1000) + 100000) 162 | } 163 | 164 | func generateDate() (string, string) { 165 | t := time.Now() 166 | var fromDate = t.AddDate(0, -1, 0).Format("2006-01-02") 167 | var toDate = t.Format("2006-01-02") 168 | return fromDate, toDate 169 | } 170 | 171 | func mockBeneficiaries() iris.Beneficiaries { 172 | var random = random() 173 | return iris.Beneficiaries{ 174 | Name: "MidGoUnitTest" + random, 175 | Account: random, 176 | Bank: "bca", 177 | AliasName: "midgotest" + random, 178 | Email: "midgo" + random + "@mail.com", 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /example/simple/snap/sample.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/midtrans/midtrans-go" 7 | "github.com/midtrans/midtrans-go/example" 8 | "github.com/midtrans/midtrans-go/snap" 9 | ) 10 | 11 | var s snap.Client 12 | 13 | func setupGlobalMidtransConfig() { 14 | midtrans.ServerKey = example.SandboxServerKey1 15 | midtrans.Environment = midtrans.Sandbox 16 | 17 | // Optional : here is how if you want to set append payment notification globally 18 | midtrans.SetPaymentAppendNotification("https://example.com/append") 19 | // Optional : here is how if you want to set override payment notification globally 20 | midtrans.SetPaymentOverrideNotification("https://example.com/override") 21 | 22 | //// remove the comment bellow, in cases you need to change the default for Log Level 23 | // midtrans.DefaultLoggerLevel = &midtrans.LoggerImplementation{ 24 | // LogLevel: midtrans.LogInfo, 25 | // } 26 | } 27 | 28 | func initializeSnapClient() { 29 | s.New(example.SandboxServerKey1, midtrans.Sandbox) 30 | } 31 | 32 | func createTransactionWithGlobalConfig() { 33 | res, err := snap.CreateTransactionWithMap(example.SnapParamWithMap()) 34 | if err != nil { 35 | fmt.Println("Snap Request Error", err.GetMessage()) 36 | } 37 | fmt.Println("Snap response", res) 38 | } 39 | 40 | func createTransaction() { 41 | // Optional : here is how if you want to set append payment notification for this request 42 | s.Options.SetPaymentAppendNotification("https://example.com/append") 43 | 44 | // Optional : here is how if you want to set override payment notification for this request 45 | s.Options.SetPaymentOverrideNotification("https://example.com/override") 46 | // Send request to Midtrans Snap API 47 | 48 | resp, err := s.CreateTransaction(GenerateSnapReq()) 49 | if err != nil { 50 | fmt.Println("Error :", err.GetMessage()) 51 | } 52 | fmt.Println("Response : ", resp) 53 | } 54 | 55 | func createTokenTransactionWithGateway() { 56 | s.Options.SetPaymentOverrideNotification("https://example.com/url2") 57 | 58 | resp, err := s.CreateTransactionToken(GenerateSnapReq()) 59 | if err != nil { 60 | fmt.Println("Error :", err.GetMessage()) 61 | } 62 | fmt.Println("Response : ", resp) 63 | } 64 | 65 | func createUrlTransactionWithGateway() { 66 | s.Options.SetContext(context.Background()) 67 | 68 | resp, err := s.CreateTransactionUrl(GenerateSnapReq()) 69 | if err != nil { 70 | fmt.Println("Error :", err.GetMessage()) 71 | } 72 | fmt.Println("Response : ", resp) 73 | } 74 | 75 | func main() { 76 | fmt.Println("================ Request with global config ================") 77 | setupGlobalMidtransConfig() 78 | createTransactionWithGlobalConfig() 79 | 80 | fmt.Println("================ Request with Snap Client ================") 81 | initializeSnapClient() 82 | createTransaction() 83 | 84 | fmt.Println("================ Request Snap token ================") 85 | createTokenTransactionWithGateway() 86 | 87 | fmt.Println("================ Request Snap URL ================") 88 | createUrlTransactionWithGateway() 89 | } 90 | 91 | func GenerateSnapReq() *snap.Request { 92 | 93 | // Initiate Customer address 94 | custAddress := &midtrans.CustomerAddress{ 95 | FName: "John", 96 | LName: "Doe", 97 | Phone: "081234567890", 98 | Address: "Baker Street 97th", 99 | City: "Jakarta", 100 | Postcode: "16000", 101 | CountryCode: "IDN", 102 | } 103 | 104 | // Initiate Snap Request 105 | snapReq := &snap.Request{ 106 | TransactionDetails: midtrans.TransactionDetails{ 107 | OrderID: "MID-GO-ID-" + example.Random(), 108 | GrossAmt: 200000, 109 | }, 110 | CreditCard: &snap.CreditCardDetails{ 111 | Secure: true, 112 | }, 113 | CustomerDetail: &midtrans.CustomerDetails{ 114 | FName: "John", 115 | LName: "Doe", 116 | Email: "john@doe.com", 117 | Phone: "081234567890", 118 | BillAddr: custAddress, 119 | ShipAddr: custAddress, 120 | }, 121 | EnabledPayments: snap.AllSnapPaymentType, 122 | Items: &[]midtrans.ItemDetails{ 123 | { 124 | ID: "ITEM1", 125 | Price: 200000, 126 | Qty: 1, 127 | Name: "Someitem", 128 | }, 129 | }, 130 | } 131 | return snapReq 132 | } 133 | -------------------------------------------------------------------------------- /example/simple/subscriptions/sample.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/midtrans/midtrans-go" 5 | "github.com/midtrans/midtrans-go/coreapi" 6 | "github.com/midtrans/midtrans-go/example" 7 | "log" 8 | ) 9 | 10 | var c coreapi.Client 11 | var subscriptionName string 12 | var subscriptionId string 13 | 14 | func main() { 15 | c.New(example.SandboxServerKey1, midtrans.Sandbox) 16 | 17 | CreateSubscription() 18 | DisableSubscription() 19 | EnableSubscription() 20 | UpdateSubscription() 21 | GetSubscription() 22 | } 23 | 24 | func CreateSubscription() { 25 | subscriptionName = "MidGoSubTest-" + example.Random() 26 | req := &coreapi.SubscriptionReq{ 27 | Name: subscriptionName, 28 | Amount: 100000, 29 | Currency: "IDR", 30 | PaymentType: coreapi.PaymentTypeCreditCard, 31 | Token: "DUMMY", 32 | Schedule: coreapi.ScheduleDetails{ 33 | Interval: 1, 34 | IntervalUnit: "month", 35 | MaxInterval: 12, 36 | }, 37 | CustomerDetails: &midtrans.CustomerDetails{ 38 | FName: "MidtransGo", 39 | LName: "SubscriptionTest", 40 | Email: "mid-go@mainlesia.com", 41 | Phone: "081234567", 42 | }, 43 | } 44 | 45 | resp, err := c.CreateSubscription(req) 46 | if err != nil { 47 | log.Println("Failure :") 48 | log.Fatalln(err) 49 | } else { 50 | log.Println("Success :") 51 | log.Println(resp) 52 | subscriptionId = resp.ID 53 | } 54 | 55 | } 56 | 57 | func GetSubscription() { 58 | resp, err := c.GetSubscription(subscriptionId) 59 | if err != nil { 60 | log.Println("Failure :") 61 | log.Fatal(err) 62 | } else { 63 | log.Println("Success :") 64 | log.Println(resp) 65 | } 66 | } 67 | 68 | func DisableSubscription() { 69 | resp, err := c.DisableSubscription(subscriptionId) 70 | if err != nil { 71 | log.Println("Failure :") 72 | log.Fatal(err) 73 | } else { 74 | log.Println("Success :") 75 | log.Println(resp) 76 | } 77 | } 78 | 79 | func EnableSubscription() { 80 | resp, err := c.EnableSubscription(subscriptionId) 81 | if err != nil { 82 | log.Println("Failure :") 83 | log.Fatal(err) 84 | } else { 85 | log.Println("Success :") 86 | log.Println(resp) 87 | } 88 | } 89 | 90 | func UpdateSubscription() { 91 | reqUpdate := &coreapi.SubscriptionReq{ 92 | Name: subscriptionName, 93 | Amount: 50000, 94 | Currency: "IDR", 95 | PaymentType: coreapi.PaymentTypeCreditCard, 96 | Token: "DUMMY", 97 | Schedule: coreapi.ScheduleDetails{ 98 | Interval: 1, 99 | IntervalUnit: "month", 100 | MaxInterval: 12, 101 | }, 102 | CustomerDetails: &midtrans.CustomerDetails{ 103 | FName: "MidtransGo", 104 | LName: "SubscriptionTest", 105 | Email: "mid-go@mainlesia.com", 106 | Phone: "081234567", 107 | }, 108 | } 109 | resp, err := c.UpdateSubscription(subscriptionId, reqUpdate) 110 | if err != nil { 111 | log.Println("Failure :") 112 | log.Fatal(err) 113 | } else { 114 | log.Println("Success :") 115 | log.Println(resp) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /example/simple/tokenization/sample.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/midtrans/midtrans-go" 5 | "github.com/midtrans/midtrans-go/coreapi" 6 | "github.com/midtrans/midtrans-go/example" 7 | "log" 8 | ) 9 | 10 | var c coreapi.Client 11 | var accountId string 12 | var accountIdActive = "18631af2-542a-435c-848f-3bc63cbd221c" 13 | var paymentOptionToken string 14 | 15 | func paymentAccount(phoneNumber string) *coreapi.PaymentAccountReq { 16 | return &coreapi.PaymentAccountReq{ 17 | PaymentType: coreapi.PaymentTypeGopay, 18 | GopayPartner: &coreapi.GopayPartnerDetails{ 19 | PhoneNumber: phoneNumber, 20 | CountryCode: "62", 21 | RedirectURL: "https://midtrans.com/", 22 | }, 23 | } 24 | } 25 | 26 | func main() { 27 | c.New(example.SandboxServerKey1, midtrans.Sandbox) 28 | 29 | LinkPaymentAccount() 30 | GetPaymentAccount() 31 | ChargeRequest() 32 | UnlinkPaymentAccount() 33 | } 34 | 35 | func LinkPaymentAccount() { 36 | req := paymentAccount("62877812345678") 37 | resp, err := c.LinkPaymentAccount(req) 38 | if err != nil { 39 | log.Println("Failure :") 40 | log.Fatalln(err) 41 | } else { 42 | log.Println("Success :") 43 | log.Println(resp) 44 | accountId = resp.AccountId 45 | } 46 | } 47 | 48 | func UnlinkPaymentAccount() { 49 | resp, err := c.UnlinkPaymentAccount(accountId) 50 | if err != nil { 51 | log.Println("Failure :") 52 | log.Fatalln(err) 53 | } else { 54 | log.Println("Success :") 55 | log.Println(resp) 56 | } 57 | } 58 | 59 | func GetPaymentAccount() { 60 | resp, err := c.GetPaymentAccount(accountIdActive) 61 | if err != nil { 62 | log.Println("Failure :") 63 | log.Fatalln(err) 64 | } else { 65 | log.Println("Success :") 66 | log.Println(resp) 67 | paymentOptionToken = resp.Metadata.PaymentOptions[0].Token 68 | } 69 | } 70 | 71 | func ChargeRequest() { 72 | req := &coreapi.ChargeReq{ 73 | PaymentType: coreapi.PaymentTypeGopay, 74 | TransactionDetails: midtrans.TransactionDetails{ 75 | OrderID: "MidGoSample-Tokenization-" + example.Random(), 76 | GrossAmt: 1000, 77 | }, 78 | Gopay: &coreapi.GopayDetails{ 79 | EnableCallback: true, 80 | CallbackUrl: "https://midtrans.com", 81 | AccountID: accountIdActive, 82 | PaymentOptionToken: paymentOptionToken, 83 | }, 84 | } 85 | 86 | resp, err := c.ChargeTransaction(req) 87 | if err != nil { 88 | log.Println("Failure :") 89 | log.Fatalln(err) 90 | } else { 91 | log.Println("Success :") 92 | log.Println(resp) 93 | } 94 | 95 | } 96 | 97 | -------------------------------------------------------------------------------- /example/simple/transaction/sample.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/midtrans/midtrans-go" 6 | "github.com/midtrans/midtrans-go/coreapi" 7 | "github.com/midtrans/midtrans-go/example" 8 | ) 9 | 10 | var c coreapi.Client 11 | 12 | func initiateCoreApiClient() { 13 | c.New(example.SandboxServerKey1, midtrans.Sandbox) 14 | } 15 | 16 | func CheckTransaction() { 17 | res, err := c.CheckTransaction("YOUR-ORDER-ID_or_TRANSACTION-ID") 18 | if err != nil { 19 | // do something on error handle 20 | } 21 | fmt.Println("Response: ", res) 22 | } 23 | 24 | func CheckStatusB2B() { 25 | res, err := c.GetStatusB2B("YOUR-ORDER-ID_or_TRANSACTION-ID") 26 | if err != nil { 27 | // do something on error handle 28 | } 29 | fmt.Println("Response: ", res) 30 | } 31 | 32 | func ApproveTransaction() { 33 | res, err := c.ApproveTransaction("YOUR-ORDER-ID_or_TRANSACTION-ID") 34 | if err != nil { 35 | // do something on error handle 36 | } 37 | fmt.Println("Response: ", res) 38 | } 39 | 40 | func DenyTransaction() { 41 | res, err := c.DenyTransaction("YOUR-ORDER-ID_or_TRANSACTION-ID") 42 | if err != nil { 43 | // do something on error handle 44 | } 45 | fmt.Println("Response: ", res) 46 | } 47 | 48 | func CancelTransaction() { 49 | res, err := c.CancelTransaction("YOUR-ORDER-ID_or_TRANSACTION-ID") 50 | if err != nil { 51 | // do something on error handle 52 | } 53 | fmt.Println("Response: ", res) 54 | } 55 | 56 | func ExpireTransaction() { 57 | res, err := c.ExpireTransaction("YOUR-ORDER-ID_or_TRANSACTION-ID") 58 | if err != nil { 59 | // do something on error handle 60 | } 61 | fmt.Println("Response: ", res) 62 | } 63 | 64 | func CaptureTransaction() { 65 | refundRequest := &coreapi.CaptureReq{ 66 | TransactionID: "TRANSACTION-ID", 67 | GrossAmt: 10000, 68 | } 69 | res, err := c.CaptureTransaction(refundRequest) 70 | if err != nil { 71 | // do something on error handle 72 | } 73 | fmt.Println("Response: ", res) 74 | } 75 | 76 | func RefundTransaction() { 77 | refundRequest := &coreapi.RefundReq{ 78 | Amount: 5000, 79 | Reason: "Item out of stock", 80 | } 81 | 82 | res, err := c.RefundTransaction("YOUR_ORDER_ID_or_TRANSACTION_ID", refundRequest) 83 | if err != nil { 84 | // do something on error handle 85 | } 86 | fmt.Println("Response: ", res) 87 | } 88 | 89 | func DirectRefundTransaction() { 90 | refundRequest := &coreapi.RefundReq{ 91 | RefundKey: "order1-ref1", 92 | Amount: 5000, 93 | Reason: "Item out of stock", 94 | } 95 | 96 | // Optional: set payment idempotency key to prevent duplicate request 97 | c.Options.SetPaymentIdempotencyKey("UNIQUE-ID") 98 | 99 | res, err := c.DirectRefundTransaction("YOUR_ORDER_ID_or_TRANSACTION-ID", refundRequest) 100 | if err != nil { 101 | // do something on error handle 102 | } 103 | fmt.Println("Response: ", res) 104 | } 105 | 106 | func main() { 107 | initiateCoreApiClient() 108 | 109 | CheckTransaction() 110 | CheckStatusB2B() 111 | ApproveTransaction() 112 | DenyTransaction() 113 | CancelTransaction() 114 | ExpireTransaction() 115 | CaptureTransaction() 116 | 117 | RefundTransaction() 118 | DirectRefundTransaction() 119 | } 120 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/midtrans/midtrans-go 2 | 3 | go 1.13 4 | 5 | require github.com/stretchr/testify v1.7.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 6 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 7 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 10 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 11 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 12 | -------------------------------------------------------------------------------- /httpclient.go: -------------------------------------------------------------------------------- 1 | package midtrans 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "net/http" 10 | "strconv" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | type HttpClient interface { 16 | Call(method string, url string, apiKey *string, options *ConfigOptions, body io.Reader, result interface{}) *Error 17 | } 18 | 19 | // HttpClientImplementation : this is for midtrans HttpClient Implementation 20 | type HttpClientImplementation struct { 21 | HttpClient *http.Client 22 | Logger LoggerInterface 23 | } 24 | 25 | // Call the Midtrans API at specific `path` using the specified HTTP `method`. The result will be 26 | // given to `result` if there is no error. If any error occurred, the return of this function is the `midtrans.Error` 27 | // itself, otherwise nil. 28 | func (c *HttpClientImplementation) Call(method string, url string, apiKey *string, options *ConfigOptions, body io.Reader, result interface{}) *Error { 29 | // NewRequest is used by Call to generate an http.Request. 30 | req, err := http.NewRequest(method, url, body) 31 | 32 | if err != nil { 33 | c.Logger.Error("Cannot create Midtrans request: %v", err) 34 | return &Error{ 35 | Message: fmt.Sprintf("Error Request creation failed: %s", err.Error()), 36 | RawError: err, 37 | } 38 | } 39 | 40 | if options != nil { 41 | if options.Ctx != nil { 42 | req.WithContext(options.Ctx) 43 | } 44 | 45 | if options.IrisIdempotencyKey != nil { 46 | req.Header.Add("X-Idempotency-Key", *options.IrisIdempotencyKey) 47 | } 48 | 49 | if options.PaymentIdempotencyKey != nil { 50 | req.Header.Add("Idempotency-Key", *options.PaymentIdempotencyKey) 51 | } 52 | 53 | if options.PaymentOverrideNotification != nil { 54 | req.Header.Add("X-Override-Notification", *options.PaymentOverrideNotification) 55 | } 56 | 57 | if options.PaymentAppendNotification != nil { 58 | req.Header.Add("X-Append-Notification", *options.PaymentAppendNotification) 59 | } 60 | } 61 | req.Header.Add("Content-Type", "application/json") 62 | req.Header.Add("Accept", "application/json") 63 | req.Header.Add("User-Agent", "Midtrans-Go_"+libraryVersion) 64 | if apiKey != nil { 65 | key := *apiKey 66 | if key == "" { 67 | err := &Error{ 68 | Message: "The API Key (ServerKey/IrisApiKey) is invalid, as it is an empty string. Please double-check your API key. " + 69 | "You can check from the Midtrans Dashboard. " + 70 | "See https://docs.midtrans.com/en/midtrans-account/overview?id=retrieving-api-access-keys " + 71 | "for the details or please contact us via https://midtrans.com/contact-us. ", 72 | } 73 | c.Logger.Error("Authentication: ", err.GetMessage()) 74 | return err 75 | } else if strings.Contains(key, " ") { 76 | err := &Error{ 77 | Message: "The API Key (ServerKey/IrisApiKey) contains white-space. Please double-check your API key. " + 78 | "You can check the ServerKey from the Midtrans Dashboard. " + 79 | "See https://docs.midtrans.com/en/midtrans-account/overview?id=retrieving-api-access-keys " + 80 | "for the details or please contact us via https://midtrans.com/contact-us. ", 81 | } 82 | c.Logger.Error("Authentication: ", err.GetMessage()) 83 | return err 84 | } else { 85 | req.SetBasicAuth(key, "") 86 | } 87 | } 88 | 89 | c.Logger.Info("================ Request ================") 90 | c.Logger.Info("%v Request %v %v", req.Method, req.URL, req.Proto) 91 | logHttpHeaders(c.Logger, req.Header, true) 92 | return c.DoRequest(req, result) 93 | } 94 | 95 | // DoRequest : is used by Call to execute an API request using HTTP client and parse the response into `result`. 96 | func (c *HttpClientImplementation) DoRequest(req *http.Request, result interface{}) *Error { 97 | start := time.Now() 98 | res, err := c.HttpClient.Do(req) 99 | if err != nil { 100 | c.Logger.Error("Cannot send request: %v", err.Error()) 101 | var statusCode int 102 | 103 | if res != nil { 104 | statusCode = res.StatusCode 105 | } else if strings.Contains(err.Error(), "timeout") { 106 | statusCode = 408 107 | } else { 108 | statusCode = 0 109 | } 110 | 111 | return &Error{ 112 | Message: fmt.Sprintf("Error when request via HttpClient, Cannot send request with error: %s", err.Error()), 113 | StatusCode: statusCode, 114 | RawError: err, 115 | } 116 | } 117 | 118 | if res != nil { 119 | defer res.Body.Close() 120 | 121 | c.Logger.Info("================== END ==================") 122 | c.Logger.Info("Request completed in %v ", time.Since(start)) 123 | 124 | resBody, err := ioutil.ReadAll(res.Body) 125 | if err != nil { 126 | c.Logger.Error("Request failed: %v", err) 127 | return &Error{ 128 | Message: "Cannot read response body: " + err.Error(), 129 | StatusCode: res.StatusCode, 130 | RawError: err, 131 | } 132 | } 133 | 134 | rawResponse := newHTTPResponse(res, resBody) 135 | c.Logger.Debug("=============== Response ===============") 136 | // Loop through headers to perform log 137 | logHttpHeaders(c.Logger, rawResponse.Header, false) 138 | c.Logger.Debug("Response Body: %v", string(rawResponse.RawBody)) 139 | 140 | if result != nil { 141 | if err = json.Unmarshal(resBody, &result); err != nil { 142 | return &Error{ 143 | Message: fmt.Sprintf("Invalid body response, parse error during API request to Midtrans with message: %s", err.Error()), 144 | StatusCode: res.StatusCode, 145 | RawError: err, 146 | RawApiResponse: rawResponse, 147 | } 148 | } 149 | } 150 | 151 | // Check status_code from Midtrans response body 152 | if found, data := HasOwnProperty("status_code", resBody); found { 153 | statusCode, _ := strconv.Atoi(data["status_code"].(string)) 154 | if statusCode >= 401 && statusCode != 407 { 155 | errMessage := fmt.Sprintf("Midtrans API is returning API error. HTTP status code: %s API response: %s", strconv.Itoa(statusCode), string(resBody)) 156 | return &Error{ 157 | Message: errMessage, 158 | StatusCode: statusCode, 159 | RawError: errors.New(errMessage), 160 | RawApiResponse: rawResponse, 161 | } 162 | } 163 | } 164 | 165 | // Check StatusCode from Midtrans HTTP response api StatusCode 166 | if res.StatusCode >= 400 { 167 | errMessage := fmt.Sprintf("Midtrans API is returning API error. HTTP status code: %s API response: %s", strconv.Itoa(res.StatusCode), string(resBody)) 168 | return &Error{ 169 | Message: errMessage, 170 | StatusCode: res.StatusCode, 171 | RawError: errors.New(errMessage), 172 | RawApiResponse: rawResponse, 173 | } 174 | } 175 | } 176 | return nil 177 | } 178 | 179 | // ApiResponse : is a structs that may come from Midtrans API endpoints 180 | type ApiResponse struct { 181 | Status string // e.g. "200 OK" 182 | StatusCode int // e.g. 200 183 | Proto string // e.g. "HTTP/1.0" 184 | 185 | // response Header contain a map of all HTTP header keys to values. 186 | Header http.Header 187 | // response body 188 | RawBody []byte 189 | // request that was sent to obtain the response 190 | Request *http.Request 191 | } 192 | 193 | // newHTTPResponse : internal function to set HTTP Raw response return to ApiResponse 194 | func newHTTPResponse(res *http.Response, responseBody []byte) *ApiResponse { 195 | return &ApiResponse{ 196 | Status: res.Status, 197 | StatusCode: res.StatusCode, 198 | Proto: res.Proto, 199 | Header: res.Header, 200 | RawBody: responseBody, 201 | Request: res.Request, 202 | } 203 | } 204 | 205 | // logHttpHeaders : internal function to perform log from headers 206 | func logHttpHeaders(log LoggerInterface, header http.Header, isReq bool) { 207 | // Loop through headers to perform log 208 | for name, headers := range header { 209 | name = strings.ToLower(name) 210 | for _, h := range headers { 211 | if name == "authorization" { 212 | log.Debug("%v: %v", name, h) 213 | } else { 214 | if isReq { 215 | log.Info("%v: %v", name, h) 216 | } else { 217 | log.Debug("%v: %v", name, h) 218 | } 219 | } 220 | } 221 | } 222 | } 223 | 224 | //HasOwnProperty : Convert HTTP raw response body to map and check if the body has own field 225 | func HasOwnProperty(key string, body []byte) (bool, map[string]interface{}) { 226 | d := make(map[string]interface{}) 227 | _ = json.Unmarshal(body, &d) 228 | if _, found := d[key].(string); found { 229 | return found, d 230 | } else { 231 | return found, d 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /iris/client.go: -------------------------------------------------------------------------------- 1 | package iris 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/midtrans/midtrans-go" 8 | "net/http" 9 | ) 10 | 11 | // Client : Iris Client struct 12 | type Client struct { 13 | IrisApiKey *string 14 | Env midtrans.EnvironmentType 15 | HttpClient midtrans.HttpClient 16 | Options *midtrans.ConfigOptions 17 | } 18 | 19 | // New : this function will always be called when the Iris is initiated 20 | func (c *Client) New(irisApiKey string, env midtrans.EnvironmentType) { 21 | c.Env = env 22 | c.IrisApiKey = &irisApiKey 23 | c.Options = &midtrans.ConfigOptions{} 24 | c.HttpClient = &midtrans.HttpClientImplementation{ 25 | HttpClient: midtrans.DefaultGoHttpClient, 26 | Logger: midtrans.GetDefaultLogger(env), 27 | } 28 | } 29 | 30 | // CreateBeneficiaries : to perform create a new beneficiary information for quick access 31 | // on the payout page in Iris Portal. https://iris-docs.midtrans.com/#create-beneficiaries 32 | func (c Client) CreateBeneficiaries(req Beneficiaries) (*BeneficiariesResponse, *midtrans.Error) { 33 | resp := &BeneficiariesResponse{} 34 | jsonReq, _ := json.Marshal(req) 35 | err := c.HttpClient.Call( 36 | http.MethodPost, 37 | fmt.Sprintf("%s/api/v1/beneficiaries", c.Env.IrisURL()), 38 | c.IrisApiKey, 39 | c.Options, 40 | bytes.NewBuffer(jsonReq), 41 | resp, 42 | ) 43 | 44 | if err != nil { 45 | return resp, err 46 | } 47 | return resp, nil 48 | } 49 | 50 | // UpdateBeneficiaries : to update an existing beneficiary identified by its alias_name. 51 | // https://iris-docs.midtrans.com/#update-beneficiaries 52 | func (c Client) UpdateBeneficiaries(aliasName string, req Beneficiaries) (*BeneficiariesResponse, *midtrans.Error) { 53 | resp := &BeneficiariesResponse{} 54 | jsonReq, _ := json.Marshal(req) 55 | err := c.HttpClient.Call( 56 | http.MethodPatch, 57 | fmt.Sprintf("%s/api/v1/beneficiaries/%s", c.Env.IrisURL(), aliasName), 58 | c.IrisApiKey, 59 | c.Options, 60 | bytes.NewBuffer(jsonReq), 61 | resp, 62 | ) 63 | 64 | if err != nil { 65 | return resp, err 66 | } 67 | return resp, nil 68 | } 69 | 70 | // GetBeneficiaries : This method to fetch list of all beneficiaries saved in Iris Portal. 71 | // https://iris-docs.midtrans.com/#list-beneficiaries 72 | func (c Client) GetBeneficiaries() ([]Beneficiaries, *midtrans.Error) { 73 | var resp []Beneficiaries 74 | err := c.HttpClient.Call( 75 | http.MethodGet, 76 | fmt.Sprintf("%s/api/v1/beneficiaries", c.Env.IrisURL()), 77 | c.IrisApiKey, 78 | c.Options, 79 | nil, 80 | &resp, 81 | ) 82 | 83 | if err != nil { 84 | return resp, err 85 | } 86 | return resp, nil 87 | } 88 | 89 | // CreatePayout : This method for Creator to create a payout. 90 | // It can be used for single payout and also multiple payouts. https://iris-docs.midtrans.com/#create-payouts 91 | func (c Client) CreatePayout(req CreatePayoutReq) (*CreatePayoutResponse, *midtrans.Error) { 92 | resp := &CreatePayoutResponse{} 93 | jsonReq, _ := json.Marshal(req) 94 | err := c.HttpClient.Call( 95 | http.MethodPost, 96 | fmt.Sprintf("%s/api/v1/payouts", c.Env.IrisURL()), 97 | c.IrisApiKey, 98 | c.Options, 99 | bytes.NewBuffer(jsonReq), 100 | resp, 101 | ) 102 | 103 | if err != nil { 104 | return resp, err 105 | } 106 | return resp, nil 107 | } 108 | 109 | // ApprovePayout : this method for Apporver to approve multiple payout request. 110 | // https://iris-docs.midtrans.com/#approve-payouts 111 | func (c Client) ApprovePayout(req ApprovePayoutReq) (*ApprovePayoutResponse, *midtrans.Error) { 112 | resp := &ApprovePayoutResponse{} 113 | jsonReq, _ := json.Marshal(req) 114 | err := c.HttpClient.Call( 115 | http.MethodPost, 116 | fmt.Sprintf("%s/api/v1/payouts/approve", c.Env.IrisURL()), 117 | c.IrisApiKey, 118 | c.Options, 119 | bytes.NewBuffer(jsonReq), 120 | resp, 121 | ) 122 | 123 | if err != nil { 124 | return resp, err 125 | } 126 | return resp, nil 127 | } 128 | 129 | // RejectPayout : This method for Apporver to reject multiple payout request. 130 | // https://iris-docs.midtrans.com/#reject-payouts 131 | func (c Client) RejectPayout(req RejectPayoutReq) (*RejectPayoutResponse, *midtrans.Error) { 132 | resp := &RejectPayoutResponse{} 133 | jsonReq, _ := json.Marshal(req) 134 | err := c.HttpClient.Call( 135 | http.MethodPost, 136 | fmt.Sprintf("%s/api/v1/payouts/reject", c.Env.IrisURL()), 137 | c.IrisApiKey, 138 | c.Options, 139 | bytes.NewBuffer(jsonReq), 140 | resp) 141 | 142 | if err != nil { 143 | return resp, err 144 | } 145 | return resp, nil 146 | } 147 | 148 | // GetPayoutDetails : Get details of a single payout. https://iris-docs.midtrans.com/#get-payout-details 149 | func (c Client) GetPayoutDetails(referenceNo string) (*PayoutDetailResponse, *midtrans.Error) { 150 | resp := &PayoutDetailResponse{} 151 | if referenceNo == "" { 152 | return resp, &midtrans.Error{ 153 | Message: "you must specified referenceNo", 154 | } 155 | } 156 | err := c.HttpClient.Call( 157 | http.MethodGet, 158 | fmt.Sprintf("%s/api/v1/payouts/%s", c.Env.IrisURL(), referenceNo), 159 | c.IrisApiKey, 160 | c.Options, 161 | nil, 162 | resp, 163 | ) 164 | 165 | if err != nil { 166 | return resp, err 167 | } 168 | return resp, nil 169 | } 170 | 171 | // GetTransactionHistory : Returns all the payout details for specific dates (https://iris-docs.midtrans.com/#payout-history) 172 | func (c Client) GetTransactionHistory(fromDate string, toDate string) ([]TransactionHistoryResponse, *midtrans.Error) { 173 | var resp []TransactionHistoryResponse 174 | jsonReq, _ := json.Marshal(`{ "from_date": ` + fromDate + `, "to_date": ` + toDate + `}`) 175 | err := c.HttpClient.Call( 176 | http.MethodGet, 177 | fmt.Sprintf("%s/api/v1/statements", c.Env.IrisURL()), 178 | c.IrisApiKey, 179 | c.Options, 180 | bytes.NewBuffer(jsonReq), 181 | &resp, 182 | ) 183 | if err != nil { 184 | return resp, err 185 | } 186 | 187 | return resp, nil 188 | } 189 | 190 | // GetTopUpChannels : Provide top up information channel for Aggregator Partner 191 | // https://iris-docs.midtrans.com/#top-up-channel-information-aggregator 192 | func (c Client) GetTopUpChannels() ([]TopUpAccountResponse, *midtrans.Error) { 193 | var resp []TopUpAccountResponse 194 | err := c.HttpClient.Call( 195 | http.MethodGet, 196 | fmt.Sprintf("%s/api/v1/channels", c.Env.IrisURL()), 197 | c.IrisApiKey, 198 | c.Options, 199 | nil, 200 | &resp, 201 | ) 202 | if err != nil { 203 | return resp, err 204 | } 205 | 206 | return resp, nil 207 | } 208 | 209 | // GetBalance : For Aggregator Partner, you need to top up to Iris’ bank account. Every partner have their own balance in Iris’ 210 | // bank account. Use this API is to get current balance information. https://iris-docs.midtrans.com/#check-balance-aggregator 211 | func (c Client) GetBalance() (*BalanceResponse, *midtrans.Error) { 212 | resp := &BalanceResponse{} 213 | err := c.HttpClient.Call( 214 | http.MethodGet, 215 | fmt.Sprintf("%s/api/v1/balance", c.Env.IrisURL()), 216 | c.IrisApiKey, 217 | c.Options, 218 | nil, 219 | resp, 220 | ) 221 | 222 | if err != nil { 223 | return resp, err 224 | } 225 | return resp, nil 226 | } 227 | 228 | // GetListBankAccount : Show list of registered bank accounts for facilitator partner 229 | // https://iris-docs.midtrans.com/#bank-accounts-facilitator 230 | func (c Client) GetListBankAccount() ([]BankAccountResponse, *midtrans.Error) { 231 | var resp []BankAccountResponse 232 | err := c.HttpClient.Call( 233 | http.MethodGet, 234 | fmt.Sprintf("%s/api/v1/bank_accounts", c.Env.IrisURL()), 235 | c.IrisApiKey, 236 | c.Options, 237 | nil, 238 | &resp, 239 | ) 240 | if err != nil { 241 | return resp, err 242 | } 243 | 244 | return resp, nil 245 | } 246 | 247 | // GetFacilitatorBalance : For Facilitator Partner, use this API is to get current balance information of your registered bank account. 248 | // https://iris-docs.midtrans.com/#bank-accounts-facilitator 249 | func (c Client) GetFacilitatorBalance(accountId string) (*BalanceResponse, *midtrans.Error) { 250 | resp := &BalanceResponse{} 251 | err := c.HttpClient.Call( 252 | http.MethodGet, 253 | fmt.Sprintf("%s/api/v1/bank_accounts/%s/balance", c.Env.IrisURL(), accountId), 254 | c.IrisApiKey, 255 | c.Options, 256 | nil, 257 | resp, 258 | ) 259 | 260 | if err != nil { 261 | return resp, err 262 | } 263 | return resp, nil 264 | } 265 | 266 | // GetBeneficiaryBanks : Show list of supported banks in IRIS. https://iris-docs.midtrans.com/#list-banks 267 | func (c Client) GetBeneficiaryBanks() (*ListBeneficiaryBankResponse, *midtrans.Error) { 268 | resp := &ListBeneficiaryBankResponse{} 269 | err := c.HttpClient.Call( 270 | http.MethodGet, 271 | fmt.Sprintf("%s/api/v1/beneficiary_banks", c.Env.IrisURL()), 272 | c.IrisApiKey, 273 | c.Options, 274 | nil, 275 | &resp, 276 | ) 277 | if err != nil { 278 | return resp, err 279 | } 280 | return resp, nil 281 | } 282 | 283 | // ValidateBankAccount : Check if an account is valid, if valid return account information. (https://iris-docs.midtrans.com/#validate-bank-account) 284 | func (c Client) ValidateBankAccount(bankName string, accountNo string) (*BankAccountDetailResponse, *midtrans.Error) { 285 | resp := &BankAccountDetailResponse{} 286 | err := c.HttpClient.Call( 287 | http.MethodGet, 288 | fmt.Sprintf("%s/api/v1/account_validation?bank=%s&account=%s", c.Env.IrisURL(), bankName, accountNo), 289 | c.IrisApiKey, 290 | c.Options, 291 | nil, 292 | resp, 293 | ) 294 | 295 | if err != nil { 296 | return resp, err 297 | } 298 | return resp, nil 299 | } 300 | -------------------------------------------------------------------------------- /iris/client_test.go: -------------------------------------------------------------------------------- 1 | package iris 2 | 3 | import ( 4 | "fmt" 5 | "github.com/midtrans/midtrans-go" 6 | assert "github.com/stretchr/testify/require" 7 | "math/rand" 8 | "strconv" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | var irisCreatorKeySandbox = "IRIS-330198f0-e49d-493f-baae-585cfded355d" 14 | var irisApproverKeySandbox = "IRIS-1595c12b-6814-4e5a-bbbb-9bc18193f47b" 15 | 16 | func random() string { 17 | rand.Seed(time.Now().UnixNano()) 18 | return strconv.Itoa(rand.Intn(2000-1000) + 100000) 19 | } 20 | 21 | func generateDate() (string, string) { 22 | t := time.Now() 23 | var fromDate = t.AddDate(0, -1, 0).Format("2006-01-02") 24 | var toDate = t.Format("2006-01-02") 25 | return fromDate, toDate 26 | } 27 | 28 | func mockBeneficiaries() Beneficiaries { 29 | var random = random() 30 | return Beneficiaries{ 31 | Name: "MidGoUnitTest" + random, 32 | Account: random, 33 | Bank: "bca", 34 | AliasName: "midgotest" + random, 35 | Email: "midgo" + random + "@mail.com", 36 | } 37 | } 38 | 39 | func TestGetBalance(t *testing.T) { 40 | var iris = Client{} 41 | iris.New(irisCreatorKeySandbox, midtrans.Sandbox) 42 | resp2, err2 := iris.GetBalance() 43 | assert.Nil(t, err2) 44 | assert.NotNil(t, resp2) 45 | } 46 | 47 | func TestCreateAndUpdateBeneficiaries(t *testing.T) { 48 | iris := Client{} 49 | iris.New(irisCreatorKeySandbox, midtrans.Sandbox) 50 | 51 | newBeneficiaries := mockBeneficiaries() 52 | resp1, err1 := iris.CreateBeneficiaries(newBeneficiaries) 53 | assert.Nil(t, err1) 54 | assert.Equal(t, resp1.Status, "created") 55 | 56 | getListAndUpdateBeneficiaries(t, newBeneficiaries) 57 | } 58 | 59 | func getListAndUpdateBeneficiaries(t *testing.T, beneficiaries Beneficiaries) { 60 | iris := Client{} 61 | iris.New(irisCreatorKeySandbox, midtrans.Sandbox) 62 | beneficiariesList, _ := iris.GetBeneficiaries() 63 | 64 | b := Beneficiaries{} 65 | for _, account := range beneficiariesList { 66 | if account.AliasName == beneficiaries.AliasName { 67 | b = account 68 | break 69 | } 70 | } 71 | 72 | updateBeneficiaries := Beneficiaries{ 73 | Name: b.Name, 74 | Account: b.Account, 75 | Bank: b.Bank, 76 | AliasName: b.AliasName + "edt", 77 | Email: b.Email, 78 | } 79 | 80 | resp, _ := iris.UpdateBeneficiaries(b.AliasName, updateBeneficiaries) 81 | assert.Equal(t, resp.Status, "updated") 82 | } 83 | 84 | func createPayout() []CreatePayoutDetailResponse { 85 | p := CreatePayoutDetailReq{ 86 | BeneficiaryName: "Tony Stark", 87 | BeneficiaryAccount: "1380011819286", 88 | BeneficiaryBank: "mandiri", 89 | BeneficiaryEmail: "tony.stark@mail.com", 90 | Amount: random(), 91 | Notes: "MidGoUnitTestApproved", 92 | } 93 | var payouts []CreatePayoutDetailReq 94 | payouts = append(payouts, p) 95 | 96 | cp := CreatePayoutReq{Payouts: payouts} 97 | 98 | iris := Client{} 99 | iris.New(irisCreatorKeySandbox, midtrans.Sandbox) 100 | payoutReps, err := iris.CreatePayout(cp) 101 | fmt.Println(payoutReps, err) 102 | 103 | return payoutReps.Payouts 104 | } 105 | 106 | func getPayoutDetails(refNo string) string { 107 | iris := Client{} 108 | iris.New(irisCreatorKeySandbox, midtrans.Sandbox) 109 | payoutReps, err := iris.GetPayoutDetails(refNo) 110 | fmt.Println(payoutReps, err) 111 | return payoutReps.ReferenceNo 112 | } 113 | 114 | func TestCreateAndApprovePayout(t *testing.T) { 115 | var payouts = createPayout() 116 | assert.Equal(t, payouts[0].Status, "queued") 117 | 118 | var refNos []string 119 | refNos = append(refNos, payouts[0].ReferenceNo) 120 | 121 | ap := ApprovePayoutReq{ 122 | ReferenceNo: refNos, 123 | OTP: "335163", 124 | } 125 | iris := Client{} 126 | iris.New(irisApproverKeySandbox, midtrans.Sandbox) 127 | approveResp, err2 := iris.ApprovePayout(ap) 128 | assert.Nil(t, err2) 129 | assert.Equal(t, approveResp.Status, "ok") 130 | 131 | assert.Equal(t, getPayoutDetails(payouts[0].ReferenceNo), payouts[0].ReferenceNo) 132 | } 133 | 134 | func TestCreateAndRejectPayout(t *testing.T) { 135 | var payouts = createPayout() 136 | assert.Equal(t, payouts[0].Status, "queued") 137 | 138 | var refNos []string 139 | refNos = append(refNos, payouts[0].ReferenceNo) 140 | 141 | ap := RejectPayoutReq{ 142 | ReferenceNo: refNos, 143 | RejectReason: "MidGoUnitTest", 144 | } 145 | iris := Client{} 146 | iris.New(irisApproverKeySandbox, midtrans.Sandbox) 147 | approveResp, err2 := iris.RejectPayout(ap) 148 | assert.Nil(t, err2) 149 | assert.Equal(t, approveResp.Status, "ok") 150 | } 151 | 152 | func TestPayoutHistory(t *testing.T) { 153 | fromDate, toDate := generateDate() 154 | 155 | iris := Client{} 156 | iris.New(irisApproverKeySandbox, midtrans.Sandbox) 157 | resp, err := iris.GetTransactionHistory(fromDate, toDate) 158 | assert.Nil(t, err) 159 | assert.NotNil(t, resp) 160 | } 161 | 162 | func TestGetTopUpChannels(t *testing.T) { 163 | iris := Client{} 164 | iris.New(irisApproverKeySandbox, midtrans.Sandbox) 165 | resp, err := iris.GetTopUpChannels() 166 | assert.Nil(t, err) 167 | assert.NotNil(t, resp) 168 | } 169 | 170 | func TestGetListBeneficiaryBank(t *testing.T) { 171 | iris := Client{} 172 | iris.New(irisApproverKeySandbox, midtrans.Sandbox) 173 | resp, err := iris.GetBeneficiaryBanks() 174 | assert.Nil(t, err) 175 | assert.NotNil(t, resp) 176 | } 177 | 178 | func TestValidateBankAccount(t *testing.T) { 179 | iris := Client{} 180 | iris.New(irisApproverKeySandbox, midtrans.Sandbox) 181 | resp, err := iris.ValidateBankAccount("mandiri", "1111222233333") 182 | assert.Nil(t, err) 183 | assert.Equal(t, resp.AccountNo, "1111222233333") 184 | } 185 | 186 | func TestCreatePayoutFail(t *testing.T) { 187 | iris := Client{} 188 | iris.New(irisCreatorKeySandbox, midtrans.Sandbox) 189 | 190 | p1 := CreatePayoutDetailReq{ 191 | BeneficiaryAccount: "1380011819286", 192 | BeneficiaryBank: "mandiri", 193 | BeneficiaryEmail: "tony.stark@mail.com", 194 | Amount: random(), 195 | Notes: "MidGoUnitTest", 196 | } 197 | 198 | p2 := CreatePayoutDetailReq{ 199 | BeneficiaryAccount: "1380011819286", 200 | BeneficiaryBank: "mandiri", 201 | BeneficiaryEmail: "jon.snow@mail.com", 202 | Amount: random(), 203 | Notes: "MidGoUnitTest", 204 | } 205 | var payouts []CreatePayoutDetailReq 206 | payouts = append(payouts, p1) 207 | payouts = append(payouts, p2) 208 | 209 | 210 | cp := CreatePayoutReq{Payouts: payouts} 211 | payoutReps, err := iris.CreatePayout(cp) 212 | assert.NotNil(t, payoutReps) 213 | assert.NotNil(t, err) 214 | assert.Equal(t, "An error occurred when creating payouts", payoutReps.ErrorMessage) 215 | } 216 | -------------------------------------------------------------------------------- /iris/request.go: -------------------------------------------------------------------------------- 1 | package iris 2 | 3 | // Beneficiaries : Iris Beneficiaries request (create, update, list) 4 | // https://iris-docs.midtrans.com/#create-beneficiaries 5 | // https://iris-docs.midtrans.com/#update-beneficiaries 6 | // https://iris-docs.midtrans.com/#list-beneficiaries 7 | type Beneficiaries struct { 8 | Name string `json:"name"` 9 | Account string `json:"account"` 10 | Bank string `json:"bank"` 11 | AliasName string `json:"alias_name"` 12 | Email string `json:"email"` 13 | } 14 | 15 | // CreatePayoutReq : Represent Create Payout request payload Iris 16 | type CreatePayoutReq struct { 17 | Payouts []CreatePayoutDetailReq `json:"payouts"` 18 | } 19 | 20 | // CreatePayoutDetailReq : Represent Create Payout detail payload Iris 21 | type CreatePayoutDetailReq struct { 22 | BeneficiaryName string `json:"beneficiary_name"` 23 | BeneficiaryAccount string `json:"beneficiary_account"` 24 | BeneficiaryBank string `json:"beneficiary_bank"` 25 | BeneficiaryEmail string `json:"beneficiary_email"` 26 | Amount string `json:"amount"` 27 | Notes string `json:"notes"` 28 | } 29 | 30 | // ApprovePayoutReq : Represent Approve Payout payload Iris 31 | type ApprovePayoutReq struct { 32 | ReferenceNo []string `json:"reference_nos"` 33 | OTP string `json:"otp"` 34 | } 35 | 36 | // RejectPayoutReq : Represent Reject Payout payload Iris 37 | type RejectPayoutReq struct { 38 | ReferenceNo []string `json:"reference_nos"` 39 | RejectReason string `json:"reject_reason"` 40 | } 41 | -------------------------------------------------------------------------------- /iris/response.go: -------------------------------------------------------------------------------- 1 | package iris 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type ResponseWithMap map[string]interface{} 8 | 9 | // BeneficiariesResponse : Represent Beneficiaries response payload 10 | type BeneficiariesResponse struct { 11 | Status string `json:"status"` 12 | StatusCode string `json:"status_code"` 13 | Errors []string `json:"errors"` 14 | } 15 | 16 | // BeneficiaryBanksResponse : Show list of supported banks in IRIS. https://iris-docs.midtrans.com/#list-banks 17 | type BeneficiaryBanksResponse struct { 18 | BeneficiaryBanks []BeneficiaryBankResponse `json:"beneficiary_banks"` 19 | StatusCode string `json:"status_code"` 20 | } 21 | 22 | // BeneficiaryBankResponse : Represent Beneficiary bank response payload 23 | type BeneficiaryBankResponse struct { 24 | Code string `json:"code"` 25 | Name string `json:"name"` 26 | } 27 | 28 | // CreatePayoutResponse : Represent Create payout response payload 29 | type CreatePayoutResponse struct { 30 | Payouts []CreatePayoutDetailResponse `json:"payouts"` 31 | ErrorMessage string `json:"error_message"` 32 | Errors interface{} `json:"errors"` 33 | } 34 | 35 | // CreatePayoutDetailResponse : Represent Create payout detail response payload 36 | type CreatePayoutDetailResponse struct { 37 | Status string `json:"status"` 38 | ReferenceNo string `json:"reference_no"` 39 | } 40 | 41 | // ApprovePayoutResponse : Represent Approve payout response payload 42 | type ApprovePayoutResponse struct { 43 | Status string `json:"status"` 44 | ErrorMessage string `json:"error_message"` 45 | Errors []string `json:"errors"` 46 | } 47 | 48 | // RejectPayoutResponse : Represent Reject payout response payload 49 | type RejectPayoutResponse struct { 50 | Status string `json:"status"` 51 | ErrorMessage string `json:"error_message"` 52 | Errors []string `json:"errors"` 53 | } 54 | 55 | type TransactionHistoryResponse struct { 56 | Account string `json:"account"` 57 | Type string `json:"type"` 58 | Amount string `json:"amount"` 59 | Status string `json:"status"` 60 | CreatedAt time.Time `json:"created_at"` 61 | ReferenceNo string `json:"reference_no"` 62 | BeneficiaryName string `json:"beneficiary_name"` 63 | BeneficiaryAccount string `json:"beneficiary_account"` 64 | } 65 | 66 | // PayoutDetailResponse : Represent Payout detail response payload 67 | type PayoutDetailResponse struct { 68 | Amount string `json:"amount"` 69 | BeneficiaryName string `json:"beneficiary_name"` 70 | BeneficiaryAccount string `json:"beneficiary_account"` 71 | Bank string `json:"bank"` 72 | ReferenceNo string `json:"reference_no"` 73 | Notes string `json:"notes"` 74 | BeneficiaryEmail string `json:"beneficiary_email"` 75 | Status string `json:"status"` 76 | CreatedBy string `json:"created_by"` 77 | CreatedAt time.Time `json:"created_at"` 78 | UpdatedAt time.Time `json:"updated_at"` 79 | ErrorMessage string `json:"error_message"` 80 | Errors string `json:"errors"` 81 | } 82 | 83 | // BankAccountDetailResponse : Represent Bank account detail payload 84 | type BankAccountDetailResponse struct { 85 | AccountName string `json:"account_name"` 86 | AccountNo string `json:"account_no"` 87 | BankName string `json:"bank_name"` 88 | ErrorMessage string `json:"error_message"` 89 | Errors *BankAccountDetailErrorResponse `json:"errors"` 90 | } 91 | 92 | // BankAccountDetailErrorResponse : Represent Bank account detail error payload 93 | type BankAccountDetailErrorResponse struct { 94 | Account []string `json:"account"` 95 | Bank []string `json:"bank"` 96 | } 97 | 98 | // BalanceResponse : Represent balance detail response payload 99 | type BalanceResponse struct { 100 | Balance string `json:"balance"` 101 | } 102 | 103 | type TopUpAccountResponse struct { 104 | ID int `json:"id"` 105 | VirtualAccountType string `json:"virtual_account_type"` 106 | VirtualAccountNumber string `json:"virtual_account_number"` 107 | } 108 | 109 | type BankAccountResponse struct { 110 | BankAccountID string `json:"bank_account_id"` 111 | BankName string `json:"bank_name"` 112 | AccountName string `json:"account_name"` 113 | AccountNumber string `json:"account_number"` 114 | Status string `json:"status"` 115 | } 116 | 117 | type ListBeneficiaryBankResponse struct { 118 | BeneficiaryBanks []struct { 119 | Code string `json:"code"` 120 | Name string `json:"name"` 121 | RoutingCode []string `json:"routing_code"` 122 | } `json:"beneficiary_banks"` 123 | } 124 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package midtrans 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | // LogLevel is the logging level used by the Midtrans go library 9 | type LogLevel uint32 10 | 11 | const ( 12 | // NoLogging sets a logger to not show the messages 13 | NoLogging LogLevel = 0 14 | 15 | // LogError sets a logger to show error messages only. 16 | LogError LogLevel = 1 17 | 18 | // LogInfo sets a logger to show information messages 19 | LogInfo LogLevel = 2 20 | 21 | // LogDebug sets a logger to show informational messages for debugging 22 | LogDebug LogLevel = 3 23 | ) 24 | 25 | type LoggerInterface interface { 26 | // Error logs a warning message using Printf conventions. 27 | Error(format string, val ...interface{}) 28 | 29 | // Info logs an informational message using Printf conventions. 30 | Info(format string, val ...interface{}) 31 | 32 | // Debug logs debug message using Printf conventions. 33 | Debug(format string, val ...interface{}) 34 | } 35 | 36 | // LoggerImplementation is a logger interface implementation. 37 | // It prints some info, errors message and debug message for debugging to `os.Stderr` and `os.Stdout` 38 | type LoggerImplementation struct { 39 | LogLevel LogLevel 40 | } 41 | 42 | // Error : Logs a warning message using Printf conventions. 43 | func (l *LoggerImplementation) Error(format string, val ...interface{}) { 44 | if l.LogLevel >= LogError { 45 | fmt.Fprintf(os.Stderr, "ERROR - "+format+"\n", val...) 46 | } 47 | } 48 | 49 | // Info : Logs information message using Printf conventions. 50 | func (l *LoggerImplementation) Info(format string, val ...interface{}) { 51 | if l.LogLevel >= LogInfo { 52 | fmt.Fprintf(os.Stdout, "INFO - "+format+"\n", val...) 53 | } 54 | } 55 | 56 | // Debug : Log debug message using Printf conventions. 57 | func (l *LoggerImplementation) Debug(format string, val ...interface{}) { 58 | if l.LogLevel >= LogDebug { 59 | fmt.Fprintf(os.Stdout, "DEBUG - "+format+"\n", val...) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /maintaining.md: -------------------------------------------------------------------------------- 1 | > Warning: This note is for developer/maintainer of this package only 2 | 3 | ## Updating Package 4 | - Make your changes 5 | - Update `libraryVersion` value on `midtrans.go` file 6 | - To install dev dependencies with go module run `go mod tidy` on repo folder 7 | - To run test, run 8 | - `go test github.com/midtrans/midtrans-go/coreapi` 9 | - `go test github.com/midtrans/midtrans-go/snap` 10 | - `go test github.com/midtrans/midtrans-go/iris` 11 | 12 | ## Release new version 13 | - Commit and push changes to Github master branch 14 | - Create a [Github Release](https://github.com/Midtrans/midtrans-go/releases) with the target version 15 | - Github Release and Master Branch are automatically synced to [Go Packages](https://pkg.go.dev/github.com/midtrans/midtrans-go) -------------------------------------------------------------------------------- /midtrans.go: -------------------------------------------------------------------------------- 1 | package midtrans 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | // EnvironmentType is global config Environment for Midtrans api 11 | type EnvironmentType int8 12 | 13 | const ( 14 | _ EnvironmentType = iota 15 | 16 | //Sandbox : represent sandbox environment 17 | Sandbox 18 | 19 | //Production : represent production environment 20 | Production 21 | 22 | //libraryVersion : midtrans go library version 23 | libraryVersion = "v1.3.8" 24 | ) 25 | 26 | // ServerKey is config payment API key for global use 27 | var ServerKey string 28 | 29 | // ClientKey is config payment public API key for global use 30 | var ClientKey string 31 | 32 | // PaymentOverrideNotification opt to change or add custom notification urls globally on every transaction. 33 | var PaymentOverrideNotification *string 34 | 35 | // PaymentAppendNotification opt to change or set custom notification urls globally on every transaction. 36 | var PaymentAppendNotification *string 37 | 38 | // SetPaymentOverrideNotification opt to change or set custom notification urls globally on every transaction. 39 | // To use new notification url(s) disregarding the settings on Midtrans dashboard, only receive up to maximum of 3 urls. 40 | func SetPaymentOverrideNotification(val string) { 41 | PaymentOverrideNotification = &val 42 | } 43 | 44 | // SetPaymentAppendNotification opt to change or add custom notification urls globally on every transaction. 45 | // To use new notification url(s) disregarding the settings on Midtrans dashboard, only receive up to maximum of 3 urls. 46 | func SetPaymentAppendNotification(val string) { 47 | PaymentAppendNotification = &val 48 | } 49 | 50 | var ( 51 | //Environment default Environment for Midtrans API 52 | Environment = Sandbox 53 | 54 | //DefaultHttpTimeout default timeout for go HTTP HttpClient 55 | DefaultHttpTimeout = 80 * time.Second 56 | 57 | //DefaultGoHttpClient default Go HTTP Client for Midtrans HttpClient API 58 | DefaultGoHttpClient = &http.Client{Timeout: DefaultHttpTimeout} 59 | 60 | //DefaultLoggerLevel logging level that will be used for config globally by Midtrans logger 61 | DefaultLoggerLevel = &LoggerImplementation{LogLevel: LogError} 62 | 63 | //defaultHttpClientImplementation 64 | defaultHttpClientImplementation = &HttpClientImplementation{ 65 | HttpClient: DefaultGoHttpClient, 66 | Logger: GetDefaultLogger(Environment), 67 | } 68 | ) 69 | 70 | // GetDefaultLogger the default logger that the library will use to log errors, debug, and informational messages. 71 | func GetDefaultLogger(env EnvironmentType) LoggerInterface { 72 | if env == Sandbox { 73 | return &LoggerImplementation{LogLevel: LogDebug} 74 | } else { 75 | return DefaultLoggerLevel 76 | } 77 | } 78 | 79 | // GetHttpClient : get HttpClient implementation 80 | func GetHttpClient(Env EnvironmentType) *HttpClientImplementation { 81 | return &HttpClientImplementation{ 82 | HttpClient: DefaultGoHttpClient, 83 | Logger: GetDefaultLogger(Env), 84 | } 85 | } 86 | 87 | var typeString = map[EnvironmentType]string{ 88 | Sandbox: "https://api.sandbox.midtrans.com", 89 | Production: "https://api.midtrans.com", 90 | } 91 | 92 | // BaseUrl To get Midtrans Base URL 93 | func (e EnvironmentType) BaseUrl() string { 94 | for k, v := range typeString { 95 | if k == e { 96 | return v 97 | } 98 | } 99 | return "undefined" 100 | } 101 | 102 | // SnapURL : To get Snap environment API URL 103 | func (e EnvironmentType) SnapURL() string { 104 | return strings.Replace(e.BaseUrl(), "api.", "app.", 1) 105 | } 106 | 107 | // IrisURL : To get Iris environment API URL 108 | func (e EnvironmentType) IrisURL() string { 109 | return strings.Replace(e.BaseUrl(), "api.", "app.", 1) + "/iris" 110 | } 111 | 112 | // ConfigOptions : is used to configure some feature before request to Midtrans API 113 | // via `coreapi.Gateway` `snap.Gateway` and `iris.Gateway` 114 | type ConfigOptions struct { 115 | PaymentIdempotencyKey *string 116 | PaymentOverrideNotification *string 117 | PaymentAppendNotification *string 118 | IrisIdempotencyKey *string 119 | Ctx context.Context 120 | } 121 | 122 | // SetPaymentIdempotencyKey : options to change or add unique idempotency-key on header on Midtrans Payment API request with key maximum length is 36. 123 | // To safely handle retry request without performing the same operation twice. This is helpful for cases where merchant didn't 124 | // receive the response because of network issue or other unexpected error. 125 | func (o *ConfigOptions) SetPaymentIdempotencyKey(val string) { 126 | o.PaymentIdempotencyKey = &val 127 | } 128 | 129 | // SetIrisIdempotencyKey : options to change or add unique idempotency-key on header on Iris API request with key maximum length is 100. 130 | // To safely handle retry request without performing the same operation twice. This is helpful for cases where merchant didn't 131 | // receive the response because of network issue or other unexpected error. 132 | func (o *ConfigOptions) SetIrisIdempotencyKey(val string) { 133 | o.IrisIdempotencyKey = &val 134 | } 135 | 136 | // SetPaymentOverrideNotification : options to change or add custom notification urls on every transaction. 137 | // To use new notification url(s) disregarding the settings on Midtrans dashboard, only receive up to maximum of 3 urls 138 | func (o *ConfigOptions) SetPaymentOverrideNotification(val string) { 139 | o.PaymentOverrideNotification = &val 140 | } 141 | 142 | // SetPaymentAppendNotification : options to change or add custom notification urls on every transaction. 143 | // To use new notification url(s) disregarding the settings on Midtrans dashboard, only receive up to maximum of 3 urls 144 | func (o *ConfigOptions) SetPaymentAppendNotification(val string) { 145 | o.PaymentAppendNotification = &val 146 | } 147 | 148 | // SetContext : options to change or add Context for each API request 149 | func (o *ConfigOptions) SetContext(ctx context.Context) { 150 | o.Ctx = ctx 151 | } 152 | -------------------------------------------------------------------------------- /request.go: -------------------------------------------------------------------------------- 1 | package midtrans 2 | 3 | // TransactionDetails : Represent transaction details 4 | type TransactionDetails struct { 5 | OrderID string `json:"order_id"` 6 | GrossAmt int64 `json:"gross_amount"` 7 | } 8 | 9 | // ItemDetails : Represent the transaction details 10 | type ItemDetails struct { 11 | ID string `json:"id,omitempty"` 12 | Name string `json:"name"` 13 | Price int64 `json:"price"` 14 | Qty int32 `json:"quantity"` 15 | Brand string `json:"brand,omitempty"` 16 | Category string `json:"category,omitempty"` 17 | MerchantName string `json:"merchant_name,omitempty"` 18 | } 19 | 20 | // CustomerAddress : Represent the customer address 21 | type CustomerAddress struct { 22 | FName string `json:"first_name,omitempty"` 23 | LName string `json:"last_name,omitempty"` 24 | Phone string `json:"phone,omitempty"` 25 | Address string `json:"address,omitempty"` 26 | City string `json:"city,omitempty"` 27 | Postcode string `json:"postal_code,omitempty"` 28 | CountryCode string `json:"country_code,omitempty"` 29 | } 30 | 31 | // CustomerDetails : Represent the customer detail 32 | type CustomerDetails struct { 33 | // first name 34 | FName string `json:"first_name,omitempty"` 35 | 36 | // last name 37 | LName string `json:"last_name,omitempty"` 38 | 39 | Email string `json:"email,omitempty"` 40 | Phone string `json:"phone,omitempty"` 41 | BillAddr *CustomerAddress `json:"billing_address,omitempty"` 42 | ShipAddr *CustomerAddress `json:"customer_address,omitempty"` 43 | } 44 | -------------------------------------------------------------------------------- /snap/client.go: -------------------------------------------------------------------------------- 1 | package snap 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/midtrans/midtrans-go" 8 | "net/http" 9 | ) 10 | 11 | // Client : Snap Client struct 12 | type Client struct { 13 | ServerKey string 14 | Env midtrans.EnvironmentType 15 | HttpClient midtrans.HttpClient 16 | Options *midtrans.ConfigOptions 17 | } 18 | 19 | //New : this function will always be called when the Snap is initiated 20 | func (c *Client) New(serverKey string, env midtrans.EnvironmentType) { 21 | c.Env = env 22 | c.ServerKey = serverKey 23 | c.Options = &midtrans.ConfigOptions{} 24 | c.HttpClient = midtrans.GetHttpClient(env) 25 | } 26 | 27 | //getDefaultClient : this is internal function to get default Snap Client 28 | func getDefaultClient() Client { 29 | return Client{ 30 | ServerKey: midtrans.ServerKey, 31 | Env: midtrans.Environment, 32 | HttpClient: midtrans.GetHttpClient(midtrans.Environment), 33 | Options: &midtrans.ConfigOptions{ 34 | PaymentOverrideNotification: midtrans.PaymentOverrideNotification, 35 | PaymentAppendNotification: midtrans.PaymentAppendNotification, 36 | }, 37 | } 38 | } 39 | 40 | //CreateTransactionWithMap : Do `/transactions` API request to SNAP API return RAW JSON with Map as 41 | // body parameter, will be converted to JSON, more detail refer to: https://snap-docs.midtrans.com 42 | func (c Client) CreateTransactionWithMap(req *RequestParamWithMap) (ResponseWithMap, *midtrans.Error) { 43 | resp := ResponseWithMap{} 44 | jsonReq, _ := json.Marshal(req) 45 | err := c.HttpClient.Call( 46 | http.MethodPost, 47 | fmt.Sprintf("%s/snap/v1/transactions", c.Env.SnapURL()), 48 | &c.ServerKey, 49 | c.Options, 50 | bytes.NewBuffer(jsonReq), 51 | &resp, 52 | ) 53 | 54 | if err != nil { 55 | return resp, err 56 | } 57 | return resp, nil 58 | } 59 | 60 | // CreateTransactionWithMap : Do `/transactions` API request to SNAP API to get Snap token and redirect url with map as 61 | // body parameter, will be converted to JSON, more detail refer to: https://snap-docs.midtrans.com 62 | func CreateTransactionWithMap(req *RequestParamWithMap) (ResponseWithMap, *midtrans.Error) { 63 | return getDefaultClient().CreateTransactionWithMap(req) 64 | } 65 | 66 | // CreateTransactionTokenWithMap : Do `/transactions` API request to SNAP API to get Snap token with map as 67 | // body parameter, will be converted to JSON, more detail refer to: https://snap-docs.midtrans.com 68 | func (c Client) CreateTransactionTokenWithMap(req *RequestParamWithMap) (string, *midtrans.Error) { 69 | var snapToken string 70 | resp, err := c.CreateTransactionWithMap(req) 71 | 72 | if err != nil { 73 | return snapToken, err 74 | } 75 | 76 | if token, found := resp["token"]; !found { 77 | return snapToken, &midtrans.Error{ 78 | Message: "Token field notfound", 79 | StatusCode: 0, 80 | } 81 | } else { 82 | snapToken = token.(string) 83 | return snapToken, nil 84 | } 85 | } 86 | 87 | // CreateTransactionTokenWithMap : Do `/transactions` API request to SNAP API to get Snap token with map as 88 | // body parameter, will be converted to JSON, more detail refer to: https://snap-docs.midtrans.com 89 | func CreateTransactionTokenWithMap(req *RequestParamWithMap) (string, *midtrans.Error) { 90 | return getDefaultClient().CreateTransactionTokenWithMap(req) 91 | } 92 | 93 | // CreateTransactionUrlWithMap : Do `/transactions` API request to SNAP API to get Snap redirect url with map as 94 | // body parameter, will be converted to JSON, more detail refer to: https://snap-docs.midtrans.com 95 | func (c Client) CreateTransactionUrlWithMap(req *RequestParamWithMap) (string, *midtrans.Error) { 96 | var redirectUrl string 97 | resp, err := c.CreateTransactionWithMap(req) 98 | 99 | if err != nil { 100 | return redirectUrl, err 101 | } 102 | 103 | if url, found := resp["redirect_url"]; !found { 104 | return redirectUrl, &midtrans.Error{ 105 | Message: "Error redirect_url field notfound in json response", 106 | StatusCode: 0, 107 | } 108 | } else { 109 | redirectUrl = url.(string) 110 | return redirectUrl, nil 111 | } 112 | } 113 | 114 | // CreateTransactionUrlWithMap : Do `/transactions` API request to SNAP API to get Snap redirect url with map 115 | // as body parameter, will be converted to JSON, more detail refer to: https://snap-docs.midtrans.com 116 | func CreateTransactionUrlWithMap(req *RequestParamWithMap) (string, *midtrans.Error) { 117 | return getDefaultClient().CreateTransactionUrlWithMap(req) 118 | } 119 | 120 | // CreateTransaction : Do `/transactions` API request to SNAP API to get Snap token and redirect url with `snap.Request` 121 | // as body parameter, will be converted to JSON, more detail refer to: https://snap-docs.midtrans.com 122 | func (c Client) CreateTransaction(req *Request) (*Response, *midtrans.Error) { 123 | resp := &Response{} 124 | jsonReq, _ := json.Marshal(req) 125 | err := c.HttpClient.Call( 126 | http.MethodPost, 127 | fmt.Sprintf("%s/snap/v1/transactions", c.Env.SnapURL()), 128 | &c.ServerKey, 129 | c.Options, 130 | bytes.NewBuffer(jsonReq), 131 | resp, 132 | ) 133 | 134 | if err != nil { 135 | return resp, err 136 | } 137 | return resp, nil 138 | } 139 | 140 | // CreateTransaction : Do `/transactions` API request to SNAP API to get Snap token and redirect url with `snap.Request` 141 | // as body parameter, will be converted to JSON, more detail refer to: https://snap-docs.midtrans.com 142 | func CreateTransaction(req *Request) (*Response, *midtrans.Error) { 143 | return getDefaultClient().CreateTransaction(req) 144 | } 145 | 146 | // CreateTransactionToken : Do `/transactions` API request to SNAP API to get Snap token with `snap.Request` as 147 | // body parameter, will be converted to JSON, more detail refer to: https://snap-docs.midtrans.com 148 | func (c Client) CreateTransactionToken(req *Request) (string, *midtrans.Error) { 149 | var snapToken string 150 | resp, err := c.CreateTransaction(req) 151 | if err != nil { 152 | return snapToken, err 153 | } 154 | 155 | if resp.Token != "" { 156 | snapToken = resp.Token 157 | } 158 | return snapToken, nil 159 | } 160 | 161 | // CreateTransactionToken : Do `/transactions` API request to SNAP API to get Snap token with `snap.Request` as 162 | // body parameter, will be converted to JSON, more detail refer to: https://snap-docs.midtrans.com 163 | func CreateTransactionToken(req *Request) (string, *midtrans.Error) { 164 | return getDefaultClient().CreateTransactionToken(req) 165 | } 166 | 167 | // CreateTransactionUrl : Do `/transactions` API request to SNAP API to get Snap redirect url with `snap.Request` 168 | // as body parameter, will be converted to JSON, more detail refer to: https://snap-docs.midtrans.com 169 | func (c Client) CreateTransactionUrl(req *Request) (string, *midtrans.Error) { 170 | var redirectUrl string 171 | resp, err := c.CreateTransaction(req) 172 | if err != nil { 173 | return redirectUrl, err 174 | } 175 | 176 | if resp.RedirectURL != "" { 177 | redirectUrl = resp.RedirectURL 178 | } 179 | return redirectUrl, nil 180 | } 181 | 182 | // CreateTransactionUrl : Do `/transactions` API request to SNAP API to get Snap redirect url with `snap.Request` 183 | // as body parameter, will be converted to JSON, more detail refer to: https://snap-docs.midtrans.com 184 | func CreateTransactionUrl(req *Request) (string, *midtrans.Error) { 185 | return getDefaultClient().CreateTransactionUrl(req) 186 | } 187 | -------------------------------------------------------------------------------- /snap/client_test.go: -------------------------------------------------------------------------------- 1 | package snap 2 | 3 | import ( 4 | "fmt" 5 | "github.com/midtrans/midtrans-go" 6 | assert "github.com/stretchr/testify/require" 7 | "regexp" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | const sandboxServerKey = "SB-Mid-server-TOq1a2AVuiyhhOjvfs3U_KeO" 13 | 14 | func generateReqWithMap() *RequestParamWithMap { 15 | time.Sleep(3) 16 | return &RequestParamWithMap{ 17 | "transaction_details": map[string]interface{}{ 18 | "order_id": "MID-GO-TEST-" + time.Now().UTC().Format("2006010215040105"), 19 | "gross_amount": 10000, 20 | }, 21 | } 22 | } 23 | func TestSnapCreateTransactionWithMap(t *testing.T) { 24 | midtrans.ServerKey = sandboxServerKey 25 | assert.Equal(t, sandboxServerKey, midtrans.ServerKey) 26 | 27 | res, err := CreateTransactionWithMap(generateReqWithMap()) 28 | if err != nil { 29 | fmt.Println("Snap Request Error", err.GetMessage()) 30 | } 31 | fmt.Println("Snap response", res) 32 | 33 | assert.NotNil(t, res) 34 | } 35 | 36 | func TestSnapCreateTransactionTokenWithMap(t *testing.T) { 37 | midtrans.ServerKey = sandboxServerKey 38 | assert.Equal(t, sandboxServerKey, midtrans.ServerKey) 39 | 40 | res, err := CreateTransactionTokenWithMap(generateReqWithMap()) 41 | if err != nil { 42 | fmt.Println("Snap Request Error", err.GetMessage()) 43 | } 44 | fmt.Println("Snap response", res) 45 | 46 | assert.Equal(t, IsValidUUID(res), true) 47 | } 48 | 49 | func TestSnapCreateTransactionUrlWithMap(t *testing.T) { 50 | midtrans.ServerKey = sandboxServerKey 51 | assert.Equal(t, sandboxServerKey, midtrans.ServerKey) 52 | 53 | res, err := CreateTransactionUrlWithMap(generateReqWithMap()) 54 | if err != nil { 55 | fmt.Println("Snap Request Error", err.GetMessage()) 56 | } 57 | fmt.Println("Snap url response", res) 58 | 59 | assert.Nil(t, err) 60 | assert.NotNil(t, res) 61 | } 62 | 63 | func TestSnapCreateTransaction(t *testing.T) { 64 | s := Client{} 65 | s.New(sandboxServerKey, midtrans.Sandbox) 66 | 67 | res, err := s.CreateTransactionToken(GenerateSnapReq()) 68 | if err != nil { 69 | fmt.Println("Snap Request Error", err.GetMessage()) 70 | } 71 | fmt.Println("Snap response", res) 72 | 73 | assert.Nil(t, err) 74 | assert.NotNil(t, res) 75 | } 76 | 77 | func TestSnapCreateTransactionToken(t *testing.T) { 78 | s := Client{} 79 | s.New(sandboxServerKey, midtrans.Sandbox) 80 | 81 | res, err := s.CreateTransactionToken(GenerateSnapReq()) 82 | if err != nil { 83 | fmt.Println("Snap Request Error", err.GetMessage()) 84 | } 85 | fmt.Println("Snap response", res) 86 | 87 | assert.Nil(t, err) 88 | assert.Equal(t, IsValidUUID(res), true) 89 | } 90 | 91 | func TestSnapCreateTransactionUrl(t *testing.T) { 92 | s := Client{} 93 | s.New(sandboxServerKey, midtrans.Sandbox) 94 | 95 | res, err := s.CreateTransactionUrl(GenerateSnapReq()) 96 | if err != nil { 97 | fmt.Println("Snap Request Error", err.GetMessage()) 98 | } 99 | fmt.Println("Snap response", res) 100 | 101 | assert.Nil(t, err) 102 | assert.NotNil(t, res) 103 | } 104 | 105 | func IsValidUUID(uuid string) bool { 106 | r := regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$") 107 | return r.MatchString(uuid) 108 | } 109 | 110 | func GenerateSnapReq() *Request { 111 | 112 | // Initiate Customer address 113 | custAddress := &midtrans.CustomerAddress{ 114 | FName: "John", 115 | LName: "Doe", 116 | Phone: "081234567890", 117 | Address: "Baker Street 97th", 118 | City: "Jakarta", 119 | Postcode: "16000", 120 | CountryCode: "IDN", 121 | } 122 | 123 | // Initiate Snap Request 124 | snapReq := &Request{ 125 | TransactionDetails: midtrans.TransactionDetails{ 126 | OrderID: "MID-GO-ID-" + time.Now().UTC().Format("2006010215040105"), 127 | GrossAmt: 200000, 128 | }, 129 | CreditCard: &CreditCardDetails{ 130 | Secure: true, 131 | }, 132 | CustomerDetail: &midtrans.CustomerDetails{ 133 | FName: "John", 134 | LName: "Doe", 135 | Email: "john@doe.com", 136 | Phone: "081234567890", 137 | BillAddr: custAddress, 138 | ShipAddr: custAddress, 139 | }, 140 | Items: &[]midtrans.ItemDetails{ 141 | midtrans.ItemDetails{ 142 | ID: "ITEM1", 143 | Price: 200000, 144 | Qty: 1, 145 | Name: "Someitem", 146 | }, 147 | }, 148 | } 149 | return snapReq 150 | } 151 | -------------------------------------------------------------------------------- /snap/paymenttype.go: -------------------------------------------------------------------------------- 1 | package snap 2 | 3 | type SnapPaymentType string 4 | 5 | const ( 6 | // PaymentTypeCreditCard : credit_card 7 | PaymentTypeCreditCard SnapPaymentType = "credit_card" 8 | 9 | // PaymentTypeMandiriClickpay : mandiri_clickpay 10 | PaymentTypeMandiriClickpay SnapPaymentType = "mandiri_clickpay" 11 | 12 | // PaymentTypeCimbClicks : cimb_clicks 13 | PaymentTypeCimbClicks SnapPaymentType = "cimb_clicks" 14 | 15 | // PaymentTypeKlikBca : bca_klikbca 16 | PaymentTypeKlikBca SnapPaymentType = "bca_klikbca" 17 | 18 | // PaymentTypeBCAKlikpay : bca_klikpay 19 | PaymentTypeBCAKlikpay SnapPaymentType = "bca_klikpay" 20 | 21 | // PaymentTypeBRIEpay : bri_epay 22 | PaymentTypeBRIEpay SnapPaymentType = "bri_epay" 23 | 24 | // PaymentTypeTelkomselCash : telkomsel_cash 25 | PaymentTypeTelkomselCash SnapPaymentType = "telkomsel_cash" 26 | 27 | // PaymentTypeEChannel : echannel 28 | PaymentTypeEChannel SnapPaymentType = "echannel" 29 | 30 | // PaymentTypeMandiriEcash : mandiri_ecash 31 | PaymentTypeMandiriEcash SnapPaymentType = "mandiri_ecash" 32 | 33 | // PaymentTypePermataVA : permata_va 34 | PaymentTypePermataVA SnapPaymentType = "permata_va" 35 | 36 | // PaymentTypeOtherVA : other_va If you want to use other_va, either permata_va or bni_va 37 | // because Midtrans handles other bank transfer as either Permata or BNI VA. 38 | PaymentTypeOtherVA SnapPaymentType = "other_va" 39 | 40 | // PaymentTypeBCAVA : bca_va 41 | PaymentTypeBCAVA SnapPaymentType = "bca_va" 42 | 43 | // PaymentTypeBNIVA : bni_va 44 | PaymentTypeBNIVA SnapPaymentType = "bni_va" 45 | 46 | // PaymentTypeBRIVA : bca_va 47 | PaymentTypeBRIVA SnapPaymentType = "bri_va" 48 | 49 | // PaymentTypeBankTransfer : bank_transfer 50 | PaymentTypeBankTransfer SnapPaymentType = "bank_transfer" 51 | 52 | // PaymentTypeConvenienceStore : cstore 53 | PaymentTypeConvenienceStore SnapPaymentType = "cstore" 54 | 55 | // PaymentTypeIndomaret : indomaret 56 | PaymentTypeIndomaret SnapPaymentType = "indomaret" 57 | 58 | // PaymentTypeKioson : kioson 59 | PaymentTypeKioson SnapPaymentType = "kioson" 60 | 61 | // PaymentTypeDanamonOnline : danamon_online 62 | PaymentTypeDanamonOnline SnapPaymentType = "danamon_online" 63 | 64 | // PaymentTypeAkulaku : akulaku 65 | PaymentTypeAkulaku SnapPaymentType = "akulaku" 66 | 67 | // PaymentTypeGopay : gopay 68 | PaymentTypeGopay SnapPaymentType = "gopay" 69 | 70 | // PaymentTypeShopeepay : shopeepay 71 | PaymentTypeShopeepay SnapPaymentType = "shopeepay" 72 | 73 | // PaymentTypeAlfamart : alfamart 74 | PaymentTypeAlfamart SnapPaymentType = "alfamart" 75 | ) 76 | 77 | // AllSnapPaymentType : Get All available SnapPaymentType 78 | var AllSnapPaymentType = []SnapPaymentType{ 79 | PaymentTypeGopay, 80 | PaymentTypeShopeepay, 81 | PaymentTypeCreditCard, 82 | PaymentTypeBankTransfer, 83 | PaymentTypeBNIVA, 84 | PaymentTypePermataVA, 85 | PaymentTypeBCAVA, 86 | PaymentTypeBRIVA, 87 | PaymentTypeOtherVA, 88 | PaymentTypeMandiriClickpay, 89 | PaymentTypeCimbClicks, 90 | PaymentTypeDanamonOnline, 91 | PaymentTypeKlikBca, 92 | PaymentTypeBCAKlikpay, 93 | PaymentTypeBRIEpay, 94 | PaymentTypeMandiriEcash, 95 | PaymentTypeTelkomselCash, 96 | PaymentTypeEChannel, 97 | PaymentTypeIndomaret, 98 | PaymentTypeKioson, 99 | PaymentTypeAkulaku, 100 | PaymentTypeAlfamart, 101 | PaymentTypeConvenienceStore, 102 | } 103 | -------------------------------------------------------------------------------- /snap/request.go: -------------------------------------------------------------------------------- 1 | package snap 2 | 3 | import ( 4 | "github.com/midtrans/midtrans-go" 5 | ) 6 | 7 | // RequestParamWithMap SnapReqWithMap : Represent snap request with map payload 8 | type RequestParamWithMap map[string]interface{} 9 | 10 | // Request : Represent SNAP API request payload that are used in Create Snap Token parameter. 11 | // https://snap-docs.midtrans.com/#json-objects 12 | type Request struct { 13 | TransactionDetails midtrans.TransactionDetails `json:"transaction_details"` 14 | Items *[]midtrans.ItemDetails `json:"item_details,omitempty"` 15 | CustomerDetail *midtrans.CustomerDetails `json:"customer_details,omitempty"` 16 | EnabledPayments []SnapPaymentType `json:"enabled_payments,omitempty"` 17 | CreditCard *CreditCardDetails `json:"credit_card,omitempty"` 18 | BcaVa *BcaVa `json:"bca_va,omitempty"` 19 | BniVa *BniVa `json:"bni_va,omitempty"` 20 | BriVa *BriVa `json:"bri_va,omitempty"` 21 | PermataVa *PermataVa `json:"permata_va,omitempty"` 22 | Gopay *GopayDetails `json:"gopay,omitempty"` 23 | ShopeePay *ShopeePayDetails `json:"shopeepay,omitempty"` 24 | Callbacks *Callbacks `json:"callbacks,omitempty"` 25 | Expiry *ExpiryDetails `json:"expiry,omitempty"` 26 | UserId string `json:"user_id,omitempty"` 27 | Cstore *Cstore `json:"cstore,omitempty"` 28 | CustomField1 string `json:"custom_field1,omitempty"` 29 | CustomField2 string `json:"custom_field2,omitempty"` 30 | CustomField3 string `json:"custom_field3,omitempty"` 31 | Metadata interface{} `json:"metadata,omitempty"` 32 | } 33 | 34 | // CreditCardDetails : Represent credit card detail for PaymentTypeCreditCard payment type 35 | type CreditCardDetails struct { 36 | // indicate if generated token should be saved for next charge 37 | SaveCard bool `json:"save_card,omitempty"` 38 | 39 | // Use 3D-Secure authentication when using credit card. Default: false 40 | Secure bool `json:"secure,omitempty"` 41 | 42 | // Acquiring bank. Valid values: `midtrans.BankBca` `midtrans.BankMandiri`, `midtrans.BankBni`, 43 | //`midtrans.BankCimb`, `midtrans.BankMaybank`, and `midtrans.BankBri` 44 | Bank string `json:"bank,omitempty"` 45 | 46 | // Acquiring channel. Options: migs 47 | Channel string `json:"channel,omitempty"` 48 | 49 | // Credit card transaction type. Options: authorize, authorize_capture. Default: “authorize_capture” 50 | Type string `json:"type,omitempty"` 51 | 52 | // Snap for installment detail 53 | Installment *InstallmentDetail `json:"installment,omitempty"` 54 | 55 | // Allowed credit card BIN numbers. 56 | // The bin value can be either a prefix(upto 8 digits) of card number or the name of a bank, 57 | // in which case all the cards issued by that bank will be allowed. 58 | // The supported bank names are bni bca mandiri cimb bri and maybank. Default: allow all cards 59 | WhitelistBins []string `json:"whitelist_bins,omitempty"` 60 | 61 | DynamicDescriptor *DynamicDescriptor `json:"dynamic_descriptor,omitempty"` 62 | 63 | CardToken string `json:"card_token,omitempty"` 64 | } 65 | 66 | // InstallmentDetail : Represent installment detail 67 | type InstallmentDetail struct { 68 | // Force installment when using credit card. Default: false 69 | Required bool `json:"required,omitempty"` 70 | 71 | // Available installment terms 72 | Terms *InstallmentTermsDetail `json:"terms,omitempty"` 73 | } 74 | 75 | // InstallmentTermsDetail : Represent installment available banks 76 | type InstallmentTermsDetail struct { 77 | Bni []int8 `json:"bni,omitempty"` 78 | Mandiri []int8 `json:"mandiri,omitempty"` 79 | Cimb []int8 `json:"cimb,omitempty"` 80 | Mega []int8 `json:"mega,omitempty"` 81 | Bca []int8 `json:"bca,omitempty"` 82 | Bri []int8 `json:"bri,omitempty"` 83 | Maybank []int8 `json:"maybank,omitempty"` 84 | Offline []int8 `json:"offline,omitempty"` 85 | } 86 | 87 | type DynamicDescriptor struct { 88 | // First 25 digit on customer’s billing statement. Mostly used to show the merchant or product name. 89 | // Only works for BNI. 90 | MerchantName string `json:"merchant_name,omitempty"` 91 | 92 | // Next 13 digit on customer’s billing statement. It works as secondary metadata on the statement. 93 | // Mostly used to show city name or region. Only works for BNI. 94 | CityName string `json:"city_name,omitempty"` 95 | 96 | // Last 2 digit on customer’s billing statement. Mostly used to show country code. 97 | // The format is ISO 3166-1 alpha-2. Only works for BNI. 98 | CountryCode string `json:"country_code,omitempty"` 99 | } 100 | 101 | // BcaVa : BCA Virtual Account is a virtual payment method offered by Bank BCA. 102 | // https://snap-docs.midtrans.com/#bca-virtual-account 103 | type BcaVa struct { 104 | // VaNumber : Custom VA Number, Length should be within 1 to 11. 105 | // https://snap-docs.midtrans.com/#custom-virtual-account-number 106 | VaNumber string `json:"va_number,omitempty"` 107 | SubCompanyCode string `json:"sub_company_code,omitempty"` 108 | FreeText struct { 109 | Inquiry []struct { 110 | En string `json:"en"` 111 | Id string `json:"id"` 112 | } `json:"inquiry,omitempty"` 113 | Payment []struct { 114 | En string `json:"en"` 115 | Id string `json:"id"` 116 | } `json:"payment,omitempty"` 117 | } `json:"free_text,omitempty"` 118 | } 119 | 120 | // BniVa : BNI Virtual Account is a virtual payment method offered by Bank BNI. 121 | // https://snap-docs.midtrans.com/#bni-virtual-account 122 | type BniVa struct { 123 | // VaNumber : Custom VA Number, Length should be within 1 to 8. 124 | // https://snap-docs.midtrans.com/#custom-virtual-account-number 125 | VaNumber string `json:"va_number,omitempty"` 126 | } 127 | 128 | // BriVa : BRI Virtual Account is a virtual payment method offered by Bank BRI. 129 | // https://snap-docs.midtrans.com/#bri-virtual-account 130 | type BriVa struct { 131 | // VaNumber : Custom VA Number, Length should be within 1 to 13. 132 | // https://snap-docs.midtrans.com/#custom-virtual-account-number 133 | VaNumber string `json:"va_number,omitempty"` 134 | } 135 | 136 | // PermataVa : Permata Virtual Account is a virtual payment method offered by Bank Permata. 137 | // https://snap-docs.midtrans.com/#permata-virtual-account 138 | type PermataVa struct { 139 | // VaNumber : Custom VA Number, Length should be 10. Only supported for b2b transactions. 140 | // https://snap-docs.midtrans.com/#custom-virtual-account-number 141 | VaNumber string `json:"va_number,omitempty"` 142 | RecipientName string `json:"recipient_name,omitempty"` 143 | } 144 | 145 | // GopayDetails : Represent gopay detail 146 | type GopayDetails struct { 147 | // EnableCallback : Enable redirect back to merchant from GoJek apps. Default: false 148 | EnableCallback bool `json:"enable_callback,omitempty"` 149 | 150 | // CallbackUrl : Determine where should customer be redirected from GoJek apps. 151 | // It supports both HTTP and deeplink. Default: same value as finish url 152 | CallbackUrl string `json:"callback_url,omitempty"` 153 | } 154 | 155 | // ShopeePayDetails : Represent shopeepay detail 156 | type ShopeePayDetails struct { 157 | // CallbackUrl : Determine where should customer be redirected from Shopee apps. 158 | // It supports both HTTP and deeplink. Default: same value as finish url, if it’s also empty, 159 | // it will be redirected to default payment processed page 160 | CallbackUrl string `json:"callback_url,omitempty"` 161 | } 162 | 163 | // Cstore : Cstore object is for PaymentTypeAlfamart free text 164 | type Cstore struct { 165 | AlfamartFreeText1 string `json:"alfamart_free_text_1,omitempty"` 166 | AlfamartFreeText2 string `json:"alfamart_free_text_2,omitempty"` 167 | AlfamartFreeText3 string `json:"alfamart_free_text_3,omitempty"` 168 | } 169 | 170 | // ExpiryDetails : Represent SNAP expiry details 171 | type ExpiryDetails struct { 172 | // StartTime : Timestamp in yyyy-MM-dd HH:mm:ss Z format. If not specified, 173 | // transaction time will be used as start time (when customer charge) 174 | StartTime string `json:"start_time,omitempty"` 175 | 176 | // Unit Expiry unit. Options: day, hour, minute (plural term also accepted) 177 | Unit string `json:"unit"` 178 | 179 | // Duration Expiry duration 180 | Duration int64 `json:"duration"` 181 | } 182 | 183 | // Callbacks : Redirect URL after transaction is successfully paid (Overridden by JS callback). 184 | // Can also be set via Snap Settings menu in your dashboard. 185 | type Callbacks struct { 186 | Finish string `json:"finish,omitempty"` 187 | } 188 | -------------------------------------------------------------------------------- /snap/response.go: -------------------------------------------------------------------------------- 1 | package snap 2 | 3 | // Response : Snap response after calling the Snap API 4 | type Response struct { 5 | Token string `json:"token"` 6 | RedirectURL string `json:"redirect_url"` 7 | StatusCode string `json:"status_code,omitempty"` 8 | ErrorMessages []string `json:"error_messages,omitempty"` 9 | } 10 | 11 | // ResponseWithMap : Snap response with map after calling the Snap API 12 | type ResponseWithMap map[string]interface{} 13 | --------------------------------------------------------------------------------