README.md in veritrans-2.3.0 vs README.md in veritrans-2.4.0

- old
+ new

@@ -1,383 +1,352 @@ -# Veritrans ruby library +# Midtrans Ruby library -Veritrans gem is the library that will help you to integrate seamlessly with -Midtrans (formerly known as Veritrans Indonesia). +Midtrans ❤️ Ruby! +This is the Official Ruby API client/library for Midtrans Payment API. Visit [https://midtrans.com](https://midtrans.com). More information about the product and see documentation at [http://docs.midtrans.com](https://docs.midtrans.com) for more technical details. + [![Gem Version](https://badge.fury.io/rb/veritrans.svg)](http://badge.fury.io/rb/veritrans) [![Build Status](https://travis-ci.org/veritrans/veritrans-ruby.svg?branch=master)](https://travis-ci.org/veritrans/veritrans-ruby) -To see it in action, we have made 3 examples: +## 1. Installation -1. Sinatra, which demonstrate in as succint code as possible. Please [have a look here](https://github.com/veritrans/veritrans-ruby/tree/master/example/sinatra) -2. Simplepay, demonstrated how to integrate Midtrans with a simple, succint Rails project. [Have a look](https://github.com/veritrans/veritrans-ruby/tree/master/example/rails/simplepay). -3. Cable, demonstrate a chat-commerce app where the payment is handled with Midtrans. [Have a look](https://github.com/veritrans/veritrans-ruby/tree/master/example/rails/cable). - -## How to use (Rails) - -### Add gem veritrans to Gemfile - +### Using Gemfile +Add gem veritrans to Gemfile ```ruby gem 'veritrans' ``` +Run this command in your terminal +```ruby +gem install veritrans +``` +```ruby +bundle install +``` - bundle install +## 2. Usage +### 2.1 Choose Product/Method -### Generate veritrans.yml +We have [3 different products](https://docs.midtrans.com/en/welcome/index.html) of payment that you can use: +- [Snap](#22A-snap) - Customizable payment popup will appear on **your web/app** (no redirection). [doc ref](https://snap-docs.midtrans.com/) +- [Snap Redirect](#22B-snap-redirect) - Customer need to be redirected to payment url **hosted by midtrans**. [doc ref](https://snap-docs.midtrans.com/) +- [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/) - rails g veritrans:install +Choose one that you think best for your unique needs. -### Create simple payment form (optional) +### 2.2 Client Initialization and Configuration - rails g veritrans:payment_form +Get your client key and server key from [Midtrans Dashboard](https://dashboard.midtrans.com) -### Edit api keys in config/veritrans.yml +Create instance of Midtrans client -```yml -# config/veritrans.yml -development: - client_key: # your api client key - server_key: # your api client key -``` +```ruby +require 'veritrans' -## STEP 1: Process credit cards +mt_client = Midtrans.new( + server_key: "your server key", + client_key: "your client key", + api_host: "https://api.sandbox.midtrans.com", # default + http_options: { }, # optional + logger: Logger.new(STDOUT), # optional + file_logger: Logger.new(STDOUT), # optional + ) -#### Snap +mt_client.status("order-id-123456") +``` -##### Pop-up +Alternatively, you can also set config by declaring each one like below: -This will result in payment page being a pop-up(iframe) inside your own web page, no need redirection, similar to our [demo](http://demo.midtrans.com) - -First, generate token in the back end provided with enough details as necessary -and as detailed as you wish to. - ```ruby -response = Veritrans.create_widget_token( - transaction_details: { - order_id: 'THE-ITEM-ORDER-ID', - gross_amount: 200000 - } -) - -@snap_token = response.token +Midtrans.config.server_key = "your server key" +Midtrans.config.client_key = "your client key" +Midtrans.config.api_host = "https://api.sandbox.midtrans.com" ``` -Then on the front end, the token is saved somewhere probably in the hidden field. +### 2.2.A Snap +You can see Snap example [with Sinatra](example/sinatra) and [without framework](example/snap). -``` -<div class='cart'> - <!-- some other codes --> - <input type='hidden' name='snap-token' id='snap-token' value='<%= %>'> - <%= hidden_field_tag 'snap_token', @snap_token %> - <a href='#' class='order-button'>Order</a> -</div> -``` +```ruby +# Create Snap payment page, with this version returning full API response +create_snap_token(parameter) -Then JavaScript can be used to invoke the SNAP dialog upon click on the -order button. +# Create Snap payment page, with this version returning token +create_snap_token_string(parameter) -```javascript -var token = jQuery("#snap_token").val(); - -jQuery(".order-button").on("click", function() { - snap.pay(token, { - onSuccess: function(res) { console.log("SUCCESS RESPONSE", res); }, - // you may also implement: - // onPending - // onError - }); -}); +# Create Snap payment page, with this version returning redirect url +create_snap_redirect_url_str(parameter) ``` +`parameter` is Object or String of JSON of [SNAP Parameter](https://snap-docs.midtrans.com/#json-objects) -##### Redirection +#### Get Snap Token -This will result in redirect_url, you can redirect customer to the url, payment page is securely hosted by Midtrans. - ```ruby -@result = Veritrans.create_snap_redirect_url( +result = Midtrans.create_snap_token( transaction_details: { - order_id: "my-unique-order-id", - gross_amount: 100_000 + order_id: "test-transaction-order-123", + gross_amount: 100000, + secure: true } ) - -redirect_to @result.redirect_url +@token = result.token ``` -> This is similar feature as old VT-Web +#### Initialize Snap JS when customer click pay button -#### VT-Web +On frontend / html: +Replace `PUT_TRANSACTION_TOKEN_HERE` with `transactionToken` acquired above +```html +<html> + <body> + <button id="pay-button">Pay!</button> + <pre><div id="result-json">JSON result will appear here after payment:<br></div></pre> -> !!! WARNING NOTE: VT-Web is deprecated, please use [Snap](#Snap) instead, it has better previous VT-Web feature and many more improvements, including redirect_url. +<!-- 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> + </body> +</html> +``` -*If you want to use VT-Web, add `payment_type: "VTWEB"`* +### 2.2.B Snap Redirect +You can see Snap example [with Sinatra](example/sinatra) and [without framework](example/snap). +#### Get Redirection URL of a Payment Page + ```ruby -@result = Veritrans.charge( - payment_type: "VTWEB", +result = Midtrans.create_snap_redirect_url( transaction_details: { - order_id: "my-unique-order-id", - gross_amount: 100_000 + order_id: "test-transaction-order-123", + gross_amount: 100000, + secure: true } ) - -redirect_to @result.redirect_url +@redirecturl = result.redirect_url ``` -#### VT-Direct / Core API +### 2.2.C Core API (VT-Direct) +You can see some Core API examples [with Sinatra](example/sinatra) and [without framework](example/coreapi). -It's little more complicated, because credit_card is sensitive data, -you need put credit card number in our safe storage first using `midtrans.min.js` library, then send received token to with other payment details. +Available methods for `CoreApi` class -We don't want you to send credit card number to your server, especially for websites not using https. +```ruby +# charge : Do `/charge` API request to Midtrans Core API +def charge(payment_type, data = nil) -File: "app/views/shared/_veritrans_include.erb" +# test_token : Do `/token` API request to Midtrans Core API +def test_token(options = {}) + +# point_inquiry : Do `/point_inquiry/{tokenId}` API request to Midtrans Core API +def point_inquiry(token_id) -```html -<script src="https://api.midtrans.com/v2/assets/js/midtrans.min.js"></script> +# status : Do `/{orderId}/status` API request to Midtrans Core API +def status(payment_id) -<script type="text/javascript"> - Veritrans.url = "<%= Veritrans.config.api_host %>/v2/token"; - Veritrans.client_key = "<%= Veritrans.config.client_key %>"; -</script> -``` +# approve : Do `/{orderId}/approve` API request to Midtrans Core API +def approve(payment_id, options = {}) -Payment form: (same as if you use `rails g veritrans:payment_form`) +# deny : Do `/{orderId}/deny` API request to Midtrans Core API +def deny(payment_id, options = {}) -```erb -<%= form_tag "/charge_vtdirect", id: "card_form" do %> - <%= hidden_field_tag :token_id, nil, id: "card_token" %> - <%= hidden_field_tag :gross_amount, 30000 %> - <p> - <%= label_tag "card_number", "Card number" %> - <%= text_field_tag :card_number, "4811 1111 1111 1114", name: nil, style: "width: 150px" %> - </p> - <p> - <%= label_tag "card_cvc", "Security Code" %> - <%= text_field_tag :card_cvc, "123", name: nil, style: "width: 30px", placeholder: "cvc" %> - </p> - <p> - <%= label_tag "card_exp", "Expiration date" %> - <%= text_field_tag :card_exp, "12 / 18", name: nil, placeholder: "MM / YY" %> - </p> - <%= submit_tag "Make payment", id: "submit_btn" %> -<% end %> -<iframe id="3d-secure-iframe" style="display: none; width: 500px; height: 600px"></iframe> -``` +# cancel : Do `/{orderId}/cancel` API request to Midtrans Core API +def cancel(payment_id, options = {}) -For sinatra: +# expire : Do `/{orderId}/expire` API request to Midtrans Core API +def expire(payment_id) + +# refund : Do `/{orderId}/refund` API request to Midtrans Core API +def refund(payment_id, options = {}) -```html -<form action="/charge_vtdirect" method="post" id="card_form"> - <input type="hidden" name="token_id" id="card_token"> - <input type="hidden" id="gross_amount" value="30000"> - <p> - <label for="card_number">Card number</label> - <input type="text" id="card_number" style="width: 150px" value="4811 1111 1111 1114"> - </p> - <p> - <label for="card_cvc">Security Code</label> - <input type="text" id="card_cvc" style="width: 30px" placeholder="cvc" value="123"> - </p> - <p> - <label for="card_exp">Expiration date</label> - <input type="text" id="card_exp" placeholder="MM / YY" value="12 / 18"> - </p> - <p> - <label for="card_secure">3D-secure</label> - <input id="card_secure" name="card_secure" type="checkbox" value="1" /> - </p> - <input id="submit_btn" type="submit"> -</form> -<iframe id="3d-secure-iframe" style="display: none; width: 500px; height: 600px"></iframe> -``` +# capture : Do `/{orderId}/capture` API request to Midtrans Core API +def capture(payment_id, gross_amount, options = {}) -Sending "get-token" request: +# link_payment_account : Do `/pay/account` API request to Midtrans Core API +def link_payment_account(param) -```js -$(document).ready(function () { - // function to prepare our credit card data before send - function createTokenData() { - return { - card_number: $('#card_number').val(), - card_cvv: $('#card_cvc').val(), - card_exp_month: $('#card_exp').val().match(/(\d+) \//)[1], - card_exp_year: '20' + $('#card_exp').val().match(/\/ (\d+)/)[1], - gross_amount: $('#gross_amount').val(), - secure: $('#card_secure')[0].checked - }; - } - // Add custom event for form submition - $('#card_form').on('submit', function (event) { - var form = this; - event.preventDefault(); +# get_payment_account : Do `/pay/account/{account_id}` API request to Midtrans Core API +def get_payment_account(account_id) - Veritrans.token(createTokenData, function (data) { - console.log('Token data:', data); - // when you making 3D-secure transaction, - // this callback function will be called again after user confirm 3d-secure - // but you can also redirect on server side - if (data.redirect_url) { - // if we get url then it's 3d-secure transaction - // so we need to open that page - $('#3d-secure-iframe').attr('src', data.redirect_url).show(); - // if no redirect_url and we have token_id then just make charge request - } else if (data.token_id) { - $('#card_token').val(data.token_id); - form.submit(); - // if no redirect_url and no token_id, then it should be error - } else { - alert(data.validation_messages ? data.validation_messages.join("\n") : data.status_message); - } - }); - }); -}); -``` +# unlink_payment_account : Do `/pay/account/{account_id}/unbind` API request to Midtrans Core API +def unlink_payment_account(account_id) -On a server side: +# create_subscription : Do `/subscription` API request to Midtrans Core API +def create_subscription(param) -```ruby -@result = Veritrans.charge( - payment_type: "credit_card", - credit_card: { token_id: params[:token_id] }, - transaction_details: { - order_id: @payment.order_id, - gross_amount: @payment.amount - } -) -if @result.success? - puts "Success" -end -``` +# get_subscription : Do `/subscription/{subscription_id}` API request to Midtrans Core API +def get_subscription(subscription_id) -## STEP 2: Process non credit cards payment +# disable_subscription : Do `/subscription/{subscription_id}/disable` API request to Midtrans Core API +def disable_subscription(subscription_id) -We provide many payment channels to accept payment, but the API call is almost the same. +# enable_subscription : Do `/subscription/{subscription_id}/enable` API request to Midtrans Core API +def enable_subscription(subscription_id) -For Snap / VT-Web in only one request, payment page will display all available payment options. +# update_subscription : Do `/subscription/{subscription_id}` API request to Midtrans Core API +def update_subscription(subscription_id, param) +``` -For Core API / VT-Direct you have to specify payment method (without get token step, credit card token required only for credit card transactions). +#### Credit Card Get Token +Get token should be handled on Frontend please refer to [API docs](https://docs.midtrans.com/en/core-api/credit-card). +Further example to demonstrate Core API card integration (including get card token on frontend), available on [Sinatra example](/example/sinatra) + +#### Credit Card Charge + ```ruby -@result = Veritrans.charge( - payment_type: "bank_transfer", - bank_transfer: { bank: 'permata' }, +result = Midtrans.charge( + payment_type: "credit_card", + credit_card: { + token_id: "CREDIT_CARD_TOKEN", # change with your card token, + authentication: true + }, transaction_details: { - order_id: @payment.order_id, - gross_amount: @payment.amount - } -) -puts "Please transfer fund to account no. #{@result.permata_va_number} in bank Permata" + order_id: "test-transaction-12345", + gross_amount: 20000 + }) +# result.data this will be Hash representation of the API JSON response: +puts result.data ``` -See [our documentation](https://api-docs.midtrans.com/#charge-features) for other available options. +#### 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](https://api-docs.midtrans.com/#card-features-3d-secure) -## STEP 3: Receive notification callback +For full example on Credit Card 3DS transaction refer to: +- [Sinatra example](/example/sinatra) that implement Snap & Core Api -For every transaction success and failed we will send you HTTP POST notification (aka webhook) -First you should set callback url in our dashboard https://dashboard.sandbox.midtrans.com/settings/vtweb_configuration +### 2.3 Handle 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. -For testing in development phase please read our [Testing webhooks tutorial](https://github.com/veritrans/veritrans-ruby/blob/master/testing_webhooks.md) and [command line tool](#command-line-tool) +Create separated web endpoint (notification url) to receive HTTP POST notification callback/webhook. +HTTP notification will be sent whenever transaction status is changed. +Example also available [here](example/sinatra) - -For rails: - ```ruby -# config/routes.rb -match "/payments/receive_webhook" => "payments#receive_webhook", via: [:post] +post_body = JSON.parse(request.body.read) +notification = Midtrans.status(post_body['transaction_id']) -# app/controllers/payments_controller.rb -def receive_webhook - post_body = request.body.read - callback_params = Veritrans.decode_notification_json(post_body) +order_id = notification.data[:order_id] +payment_type = notification.data[:payment_type] +transaction_status = notification.data[:transaction_status] +fraud_status = notification.data[:fraud_status] - verified_data = Veritrans.status(callback_params['transaction_id']) +puts "Transaction order_id: #{order_id}" +puts "Payment type: #{payment_type}" +puts "Transaction status: #{transaction_status}" +puts "Fraud status: #{fraud_status}" - if verified_data.status_code != 404 - puts "--- Transaction callback ---" - puts "Payment: #{verified_data.data[:order_id]}" - puts "Payment type: #{verified_data.data[:payment_type]}" - puts "Payment status: #{verified_data.data[:transaction_status]}" - puts "Fraud status: #{verified_data.data[:fraud_status]}" if verified_data.data[:fraud_status] - puts "Payment amount: #{verified_data.data[:gross_amount]}" - puts "--- Transaction callback ---" +return "Transaction notification received. Order ID: #{order_id}. Transaction status: #{transaction_status}. Fraud status: #{fraud_status}" - render text: "ok" - else - render text: "ok", :status => :not_found - end +# Sample transactionStatus handling logic +if transaction_status == "capture" && fraud_status == "challange" + # TODO set transaction status on your databaase to 'challenge' +elsif transaction_status == "capture" && fraud_status == "success" + # TODO set transaction status on your databaase to 'success' +elsif transaction_status == "settlement" + # TODO set transaction status on your databaase to 'success' +elsif transaction_status == "deny" + # TODO you can ignore 'deny', because most of the time it allows payment retries +elsif transaction_status == "cancel" || transaction_status == "expire" + # TODO set transaction status on your databaase to 'failure' +elsif transaction_status == "pending" + # Todo set transaction status on your databaase to 'pending' / waiting payment end ``` ----- +### 2.4 Transaction Action +For full example on transaction action refer to: [Api Reference](api_reference.md) -#### Veritrans::Events +## 3. Handling Error / Exception +When using function that result in Midtrans API call e.g: `Midtrans.charge(...)` or `Midtrans.create_snap_token(...)` +there's a chance it may throw error (`MidtransError` object), the error object will contains below properties that can be used as information to your error handling logic: -Other option to handle callbacks is our rack-based handler - ```ruby -# config/routes.rb -mount Veritrans::Events.new => '/vt_events' +begin + Midtrans.create_snap_token(parameter) +rescue MidtransError => e + puts e.message # Basic error message string + puts e.http_status_code # HTTP status code e.g: 400, 401, etc. + puts e.api_response # API response body in String + puts e.raw_http_client_data # Raw HTTP client response +end +``` -# config/initalizers/veritrans.rb -Veritrans.setup do - config.server_key = "..." - config.client_key = "..." +## 4. Advanced Usage +### Override Notification URL - events.subscribe('payment.success') do |payment| - # payment variable is hash with params recieved from Veritrans - # assuming you have model Order in your project - Order.find_by(order_id: payment.order_id).mark_paid!(payment.masked_card) - end - - events.subscribe('payment.failed', 'payment.challenge') do |payment| - # payment variable is hash with params recieved from Veritrans - # assuming you have model Order in your project - Order.find_by(order_id: payment.order_id) ... - end -end +You can opt to change or add custom notification urls on every transaction. It can be achieved by adding additional HTTP headers into charge request. +```ruby +# Add new notification url(s) alongside the settings on Midtrans Dashboard Portal (MAP) +Midtrans.config.append_notif_url = "https://example.com/test1,https://example.com/test2" +# Use new notification url(s) disregarding the settings on Midtrans Dashboard Portal (MAP) +Midtrans.config.override_notif_url = "https://example.com/test1" ``` -#### Logging +[More details](https://api-docs.midtrans.com/#override-notification-url) +> **Note:** When both `appendNotifUrl` and `overrideNotifUrl` are used together then only `overrideNotifUrl` will be used. -By default gem veritrans will show information via rails' logger. And in addition save important information to `RAILS_APP/log/veritrans.log` +> Both header can only receive up to maximum of **3 urls**. -It's configurable. +### Idempotency-Key +Is a unique value that is put on header on API request. Midtrans API accept Idempotency-Key on header to safely handle retry request +without performing the same operation twice. This is helpful for cases where merchant didn't receive the response because of network issue or other unexpected error. +You can opt to add idempotency key by adding additional HTTP headers into charge request. +```ruby +Midtrans.config.idempotency_key = "Unique-ID" +``` +[More details](http://api-docs.midtrans.com/#idempotent-requests) +### Log Configuration +By default if you are using Rails, gem Veritrans will show information via Rails logger and in addition save important information to `RAILS_APP/log/Midtrans.log` <br> +You can configure it like example below: + ```ruby -Veritrans.logger = Rails.logger -Veritrans.file_logger = Logger.new("/my/important_logs/veritrans.log") +Midtrans.logger = Rails.logger +# To set custom logger +Midtrans.file_logger = Logger.new("./log/midtrans.log") ``` -`Veritrans.file_logger` save information about: +`Midtrans.file_logger` save information about: * "charge", "cancel", "approve" api calls * Validation errors for "charge", "cancel", "approve" * Received http notifications * Errors and exception while processing http notifications ---- -#### Command line tool +### To see it in action, we have made example: -**Installation** +Sinatra, which demonstrate in as succint code as possible. Please [have a look here](https://github.com/veritrans/veritrans-ruby/tree/master/example/sinatra) - $ gem install veritrans -**Usage** +### Get help -Testing http notification: - - $ veritrans testhook http://localhost:3000/vt_events - $ veritrans testhook -o my-order-1 -c ~/path/to/veritrans.yml http://localhost:3000/vt_events - - - -#### Get help - * [Veritrans gem reference](https://github.com/veritrans/veritrans-ruby/blob/master/api_reference.md) * [Midtrans login](https://account.midtrans.com/login) * [Midtrans registration](https://account.midtrans.com/register) * [Midtrans documentation](http://docs.midtrans.com) * Technical support [support@midtrans.com](mailto:support@midtrans.com) + +## Important Changes + +### v2.4.0 +- API client methods will now raise `MidtransError` when getting unexpected API response. You may need to update your error handling. [Handling Error / Exception](#3-Handling-Error--Exception) +- Removed features: CLI, TestingLib. Mainly removed due to no longer relevant/essential to this library's purpose.