Skip to content

Commit

Permalink
Merge pull request #53 from visini/add-merchant-authorize
Browse files Browse the repository at this point in the history
Add `JSON::Transaction::MerchantAuthorize`
  • Loading branch information
andyundso authored Jan 11, 2024
2 parents e8206f1 + bb8c8ee commit d225bce
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 0 deletions.
45 changes: 45 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,51 @@ You can check the trasaction [status](https://api-reference.datatrans.ch/#tag/v1
end
```

Merchant Initiated Payments
---------

It's possible to authorize transactions without user interaction, via [merchant initiated payments](https://docs.datatrans.ch/docs/merchant-initiated-payments).

To perform a so-called "dedicated registration" (so we can later charge the card via its `alias`), you should follow the same steps as described above, but not provide an amount:

```ruby
transaction = datatrans.json_transaction(
refno: 'ABCDEF',
amount: 0, # omit amount for dedicated registrations
currency: "CHF",
payment_methods: ["ECA", "VIS"],
success_url: <your_application_return_url>,
cancel_url: <your_application_return_url>,
error_url: <your_application_return_url>
)

init = transaction.authorize

# successful authorization call returns in response a transaction id
if init
transaction_id = transaction.response.params["transactionId"]
end
```

Then, at a later point in time, and without needing any user interaction, you can create a payment via `merchant_authorize`:

```ruby
dedicated_registration = datatrans.json_transaction(transaction_id: transaction_id)
dedicated_registration.status # this will contain the card information

card_alias = dedicated_registration.response.params["card"]["alias"]
card_expiry_month = dedicated_registration.response.params["card"]["expiryMonth"]
card_expiry_year = dedicated_registration.response.params["card"]["expiryYear"]

transaction = datatrans.json_transaction(
refno: "ABCDEF",
amount: 1000,
currency: "CHF",
card: {alias: card_alias, expiryMonth: card_expiry_month, expiryYear: card_expiry_year}
)

transaction.merchant_authorize # this will charge the card without user interaction
```

XML Transactions
================
Expand Down
3 changes: 3 additions & 0 deletions lib/datatrans/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ def url(what, options = {})
when :init_transaction
subdomain = SUBDOMAINS[:server_to_server_api]
path = "/v1/transactions"
when :authorize_transaction
subdomain = SUBDOMAINS[:server_to_server_api]
path = "/v1/transactions/authorize"
when :start_json_transaction
subdomain = SUBDOMAINS[:payment_page]
path = "/v1/start/#{options[:transaction_id]}"
Expand Down
7 changes: 7 additions & 0 deletions lib/datatrans/json/transaction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ def authorize
@response.successful?
end

def merchant_authorize
self.request = MerchantAuthorize.new(self.datatrans, params)
@response = MerchantAuthorizeResponse.new(self.datatrans, request.process)
@response.successful?
end

def status
self.request = Status.new(self.datatrans, params)
@response = StatusResponse.new(self.datatrans, request.process)
Expand All @@ -35,5 +41,6 @@ def transaction_path
end

require 'datatrans/json/transaction/authorize'
require 'datatrans/json/transaction/merchant_authorize'
require 'datatrans/json/transaction/status'
require 'datatrans/json/transaction/settle'
45 changes: 45 additions & 0 deletions lib/datatrans/json/transaction/merchant_authorize.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
require 'httparty'
require 'datatrans/json/transaction/response'

class Datatrans::JSON::Transaction
class MerchantAuthorize
# class to authorize a transaction without user interaction https://api-reference.datatrans.ch/#tag/v1transactions/operation/authorize
attr_accessor :params, :datatrans

def initialize(datatrans, params)
@datatrans = datatrans
@params = params
end

def post(url, options = {})
options = options
.merge(self.datatrans.proxy)
.merge(:basic_auth => { :username => self.datatrans.merchant_id, :password => self.datatrans.password })
HTTParty.post(url, **options)
end

def process
post(self.datatrans.url(:authorize_transaction),
:headers => { 'Content-Type' => 'application/json' },
:body => request_body.to_json).parsed_response
end

def request_body
auto_settle = params[:auto_settle].nil? ? true : params[:auto_settle]

{
"currency": params[:currency],
"refno": params[:refno],
"amount": params[:amount],
"card": params[:card],
"autoSettle": auto_settle,
}
end
end

class MerchantAuthorizeResponse < Response
def successful?
params["error"].blank? && params["transactionId"].present?
end
end
end
97 changes: 97 additions & 0 deletions spec/json/merchant_authorize_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
require "spec_helper"

describe Datatrans::JSON::Transaction::MerchantAuthorize do
before do
@successful_response = {
"transactionId" => "230223022302230223",
"acquirerAuthorizationCode" => "123456",
"card" => {
"masked" => "411111xxxxxx1111"
}
}

@failed_response = {
"error" => {
"code" => "INVALID_PROPERTY",
"message" => "authorize.card.alias or number is mandatory"
}
}

@valid_params = {
currency: "CHF",
refno: "B4B4B4B4B",
amount: 1000,
card: {
alias: "AAABcH0Bq92s3kgAESIAAbGj5NIsAHWC",
expiryMonth: "01",
expiryYear: "23"
},
auto_settle: true
}

@expected_request_body = {
"currency": "CHF",
"refno": "B4B4B4B4B",
"amount": 1000,
"card": {
"alias": "AAABcH0Bq92s3kgAESIAAbGj5NIsAHWC",
"expiryMonth": "01",
"expiryYear": "23"
},
"autoSettle": true
}

@invalid_params = {
currency: "CHF",
refno: "B4B4B4B4B",
amount: 1000,
card: {
expiryMonth: "01",
expiryYear: "23"
}
}
end

context "successful response" do
before do
allow_any_instance_of(Datatrans::JSON::Transaction::MerchantAuthorize).to receive(:process).and_return(@successful_response)
end

it "generates correct request_body" do
request = Datatrans::JSON::Transaction::MerchantAuthorize.new(@datatrans, @valid_params)
expect(request.request_body).to eq(@expected_request_body)
end

it "#process handles a valid datatrans merchant authorize response" do
transaction = Datatrans::JSON::Transaction.new(@datatrans, @valid_params)
expect(transaction.merchant_authorize).to be true
end
end

context "with autoSettle specified" do
it "handles autoSettle correctly in request_body" do
params_with_auto_settle = @valid_params.merge(auto_settle: false)
request = Datatrans::JSON::Transaction::MerchantAuthorize.new(@datatrans, params_with_auto_settle)

expected_request_body_without_auto_settle = @expected_request_body.merge(autoSettle: false)
expect(request.request_body).to eq(expected_request_body_without_auto_settle)
end
end

context "failed response" do
before do
allow_any_instance_of(Datatrans::JSON::Transaction::MerchantAuthorize).to receive(:process).and_return(@failed_response)
@transaction = Datatrans::JSON::Transaction.new(@datatrans, @invalid_params)
end

it "#process handles a failed datatrans merchant authorize response" do
expect(@transaction.merchant_authorize).to be false
end

it "returns error details" do
@transaction.merchant_authorize
expect(@transaction.response.error_code).to eq "INVALID_PROPERTY"
expect(@transaction.response.error_message).to eq "authorize.card.alias or number is mandatory"
end
end
end

0 comments on commit d225bce

Please sign in to comment.