Midtrans Client - Rust

crates.io license

This is Unofficial Rust API client/library for Midtrans Payment API.

1. Installation

Add the follwing line to your Cargo.toml file

midtransclient = "X.X.X"

or using cargo

cargo add midtransclient

2. Usage

2.1 Choose Product/Method

2.2 Client Initialization and Configuration

Get your client key and server key from Midtrans Dashboard

Create API client object

rust // Create Core API instance let core = CoreApi::new(false, "YOUR_SERVER_KEY".to_string()) .client_key("YOUR_CLIENT_KEY".to_string()) .build() .unwrap();

rust // Create Snap API instance let snap = Snap::new(false, "YOUR_SERVER_KEY") .client_key("YOUR_CLIENT_KEY") .build() .unwrap();

You can also re-set config using core.set_api_config(...) or core.api_cofig.set_<fieldname>(...) example:

```rust let core = CoreApi::new(true, "SOMEKEY".tostring()).build().unwrap(); let newconfig = ApiConfig::new(false, "SERVERKEY".tostring()).build(); core.setapiconfig(newconfig);

// Or you can change spesific config let snap = Snap::new(true, "SOMEKEY".tostring()).build().unwrap(); snap.apiconfig.setisproduction(false); snap.apiconfig.setclientkey("CLIENTKEY".tostring()); ```

2.2.A Snap

You can see Snap example here.

Available methods for Snap class ```rust pub fn create_transaction(&self, parameters: &str) -> MidtransResult

pub fn createtransactiontoken(&self, parameters: &str) -> Result

pub fn createtransactionredirect_url(&self, parameters: &str) -> Result `` parameters` is String of JSON of SNAP Parameter

Get Snap Token

```rust // Create Snap API instance let snap = Snap::new(false, "YOURSERVERKEY") .clientkey("YOURCLIENT_KEY") .build() .unwrap();

// Prepare parameter let parameters = r#"{ "transactiondetails": { "orderid": "test-transaction-123", "grossamount": 200000 }, "creditcard":{ "secure" : True } }"#;

let transaction = snap.createtransaction(parameters).unwrap(); let transactiontoken = transaction["token"]; // alternative way to create transactiontoken: let transactiontoken = snap.createtransactiontoken(&parameters).unwrap(); ```

Initialize Snap JS when customer click pay button

Replace PUT_TRANSACTION_TOKEN_HERE with transaction_token acquired above ```html

JSON result will appear here after payment:

<!-- TODO: Remove ".sandbox" from script src URL for production environment. Also input your client key in "data-client-key" -->
<script src="https://app.sandbox.midtrans.com/snap/snap.js" data-client-key="<Set your ClientKey here>"></script>
<script type="text/javascript">
  document.getElementById('pay-button').onclick = function(){
    // SnapToken acquired from previous step
    snap.pay('PUT_TRANSACTION_TOKEN_HERE', {
      // Optional
      onSuccess: function(result){
        /* You may add your own js here, this is just example */
        document.getElementById('result-json').innerHTML += JSON.stringify(result, null, 2);
      },
      // Optional
      onPending: function(result){
        /* You may add your own js here, this is just example */
        document.getElementById('result-json').innerHTML += JSON.stringify(result, null, 2);
      },
      // Optional
      onError: function(result){
        /* You may add your own js here, this is just example */
        document.getElementById('result-json').innerHTML += JSON.stringify(result, null, 2);
      }
    });
  };
</script>

```

Implement Notification Handler

Refer to this section

2.2.B Snap Redirect

Get Redirection URL of a Payment Page

```rust // Create Snap API instance let snap = Snap::new(false, "YOURSERVERKEY") .clientkey("YOURCLIENT_KEY") .build() .unwrap();

// Prepare parameter let parameters = r#"{ "transactiondetails": { "orderid": "test-transaction-123", "grossamount": 200000 }, "creditcard":{ "secure" : True } }"#;

let transaction = snap.createtransaction(param).unwrap(); let transactionredirecturl = transaction['redirecturl']; // alternative way to create redirecturl let transactionredirecturl = snap.createtransactionredirecturl(param).unwrap(); ```

Implement Notification Handler

Refer to this section

2.2.C Core API (VT-Direct)

Available methods for CoreApi struct

```rust pub fn charge(&self, parameters: &str) -> MidtransResult

pub fn capture(&self, parameters: &str) -> MidtransResult

pub fn card_register(&self, parameters: &str) -> MidtransResult

pub fn card_token(&self, parameters: &str) -> MidtransResult

pub fn cardpointinquiry(&self, token_id: &str) -> MidtransResult `` parameters` is String of JSON of SNAP Parameter

Credit Card Get Token

Get token should be handled on Frontend please refer to API docs

Credit Card Charge

```rust // Create Core API instance let snap = CoreApi::new(false, "YOURSERVERKEY") .clientkey("YOURCLIENT_KEY") .build() .unwrap();

// Prepare parameter let parameters = r#"{ "paymenttype": "creditcard", "transactiondetails": { "grossamount": 12145, "orderid": "test-transaction-54321", }, "creditcard":{ "token_id": "", "authentication": True } }"#;

// charge transaction let chargeresponse = core.charge(param).unwrap(); println!("chargeresponse: {:?}", charge_response); ```

Credit Card 3DS Authentication

The credit card charge result may contains redirect_url for 3DS authentication. 3DS Authentication should be handled on Frontend please refer to API docs

2.2.D Subscription API

Subscription API for Credit Card

To use subscription API for credit card, you should first obtain the 1-click saved token, refer to this docs.

You will receive saved_token_id as part of the response when the initial card payment is accepted (will also available in the HTTP notification's JSON), refer to this docs.

```rust // Create Subscription API instance let core = CoreApi::new(false, "SERVERKEY") .clientkey("CLIENT_KEY") .build() .unwrap();

// Prepare parameter let parameters = r#"{ "name": "SUBSCRIPTION-STARTER-1", "amount": "100000", "currency": "IDR", "paymenttype": "creditcard", "token": "436502qFfqfAQKScMtPRPdZDOaeg7199", "schedule": { "interval": 1, "intervalunit": "month", "maxinterval": 3, "starttime": "2021-10-01 07:25:01 +0700" }, "metadata": { "description": "Recurring payment for STARTER 1" }, "customerdetails": { "firstname": "John A", "lastname": "Doe A", "email": "johndoe@email.com", "phone": "+62812345678" } }"#;

let createresponse = core.createsubscription(parameters).unwrap(); let subscriptionid = createresponse["id"].as_str().unwrap();

// Get subscription by subscriptionid let getresponse = core.getsubscription(subscriptionid).unwrap();

// Disable subscription by subscriptionid let disableresponse = core.disablesubscription(subscriptionid).unwrap();

// Enable subscription by subscriptionid let enableresponse = core.enablesubscription(subscriptionid).unwrap();

// Update subscription by subscription_id let parameters = r#"{ "name": "SUBSCRIPTION-STARTER-1-UPDATE", "amount": "100000", "currency": "IDR", "token": "436502qFfqfAQKScMtPRPdZDOaeg7199", "schedule": { "interval": 1 } }"#;

let updateresponse = core.updatesubscription(subscription_id, parameters).unwrap(); ```

Subscription API for Gopay

To use subscription API for gopay, you should first link your customer gopay account with gopay tokenization API, refer to this section

You will receive gopay payment token using get_payment_account API call

2.2.E Tokenization API

```rust // Create Subscription API instance let core = CoreApi::new(false, "SERVERKEY") .clientkey("CLIENT_KEY") .build() .unwrap();

// Prepare parameter // please redirecturl update with your redirect URL let parameters = r#"{ "paymenttype": "gopay", "gopaypartner": { "phonenumber": "81234567891", "countrycode": "62", "redirecturl": "https://mywebstore.com/gopay-linking-finish" } }"#;

// Link payment account let linkresponse = core.linkpayment_account(parameters).unwrap();

// Get payment account let getresponse = core.getpaymentaccount(activeaccount_id).unwrap();

// unlink payment account let unlinkresposne = core.unlinkpaymentaccount(activeaccount_id).unwrap(); ```

2.3 Hanlde HTTP Notification

IMPORTANT NOTE: To update transaction status on your backend/database, DO NOT solely rely on frontend callbacks! For security reason to make sure the status is authentically coming from Midtrans, only update transaction status based on HTTP Notification or API Get Status.

Create separated web endpoint (notification url) to receive HTTP POST notification callback/webhook. HTTP notification will be sent whenever transaction status is changed

``rust // Create Core API / Snap instance (both have sharedtransactions` methods) let core = CoreApi::new(false, "SERVERKEY") .clientkey("CLIENT_KEY") .build() .unwrap();

let statusresponse = core.status(transactionid).unwrap(); let notificationresponse = core.notificationfromjson(statusresponse).unwrap();

println!( "Transaction notification received. Order ID: {}. Transaction status: {}. Fraud status: {}", notificationresponse["orderid"], notificationresponse["transactionstatus"], notificationresponse["fraudstatus"] );

// Sample transactionstatus handling logic if transactionstatus == "capture" { if fraudstatus == "challenge" { // TODO set transaction status on your databaase to "challenge" } else if fraudstatus == "accept" { // TODO set transaction status on your databaase to"success" } } else if transactionstatus == "cancel" || transactionstatus == "deny" || transactionstatus == "expire" { // TODO set transaction status on your databaase to "failure" } else if transactionstatus == "pending" { // TODO set transaction status on your databaase to "pending" / waiting payment } ```

2.4 Transaction Actions

Get Status

rust // Get status of transaction that already recorded on midtrans (already `charge`-ed) let status_response = core.status(transaction_id).unwrap();

Get Status B2B

rust // Get transaction status of VA b2b transaction let status_response = core.statusb2b(transaction_id).unwrap();

Approve Transaction

rust // Approve a credit card transaction with `challange` fraud status let status_response = core.approve(transaction_id).unwrap();

Deny Transaction

rust // Deny a credit card transaction with `challange` fraud status let status_response = core.deny(transaction_id).unwrap();

Cancel Transaction

rust // cancel a credit card transaction or pending transaction let status_response = core.cancel(transaction_id).unwrap();

Expire Transaction

rust // expire a pending transaction let status_response = core.expire(transaction_id).unwrap();

Refund Transaction

rust // refund a transaction (not all payment channel allow refund via API) let parameters = r#"{ "refund_key": "order1-ref1", "amount": 5000, "reason": "Item out of stock" }"#; let status_response = core.refund(transaction_id, parameters).unwrap();

Refund Transaction with Direct Refund

rust // refund a transaction (not all payment channel allow refund via API) with Direct Refund let parameters = r#"{ "refund_key": "order1-ref1", "amount": 5000, "reason": "Item out of stock" }"#; let status_response = core.refund_direct(transaction_id, parameters).unwrap();

3. Handling MidtransError

When using method that return Result<_, MidtransError> in Midtrans API calls: core.charge(...) or snap.create_transaction(...), you can handle it kinda like this:

```rust let charge_response = core.charge(parameters);

match charge_response { Ok(ref result) => println!("{:?}", result), Err(ref err) => match err { MidtransError::RequestError(e) => println!("{e}"), MidtransError::JsonDecodeError(e) => println!("{e}"), MidtransError::ParseError(e) => println!("{e}"), MidtransError::ApiError(e) => println!("{e}") } }; ```

4. Advanced Usage

Custom HTTP Headers

```rust // Create Core API instance let core = CoreApi::new(false, "SERVERKEY") .clientkey("CLIENT_KEY") .build() .unwrap();

// Set custom HTTP header for every request from this instance let mut customheaders = HeaderMap::new(); customheaders.insert("X-Custom-Header", "Some Value".parse().unwrap()); core.apiconfig.setcustomheaders(customheaders); ```

Override/Append HTTP Notification URL

As described in API docs, 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.

This can be achieved by: ```rust // Create Snap instance let snap = Snap::new(false, "SERVERKEY") .clientkey("CLIENT_KEY") .build() .unwrap();

// set custom HTTP header that will be used by Midtrans API to override notification url let mut customheaders = HeaderMap::new(); customheaders.insert("x-override-notification", "https://example.org".parse().unwrap()); snap.apiconfig.setcustomheaders(customheaders);

// or append notification let mut customheaders = HeaderMap::new(); customheaders.insert("x-append-notification", "https://example.org".parse().unwrap()); snap.apiconfig.setcustomheaders(customheaders); ```

Custom HTTP Proxy

```rust // Create Snap instance let snap = Snap::new(false, "SERVERKEY") .clientkey("CLIENT_KEY") .build() .unwrap();

let proxies = reqwest::Proxy::http("https://secure.example").unwrap(); snap.apiconfig.setproxies(proxies); ```

Under the hood this API wrapper is using reqwest as http client. You can further learn about proxies on its documentation

Get help