docdata-order 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1504b4d5df07a35ae4e4b403776da567d2bab632
4
+ data.tar.gz: '079dfe209ce3bc7a99f1040721a5290f2ac29c9b'
5
+ SHA512:
6
+ metadata.gz: 9d0d115f0d0427605c95c3d1d128e0f8f1e8f2667789b95366fd9a97ea887fbed2a1e6cbcc733b91b7226e7ec4e20a70a6ad186344b19e16f945e0c912d1810a
7
+ data.tar.gz: 69f6a2e859992879043497eb3d27f76e3e373e1bc965aab1e4ebfd87159d5912b97a1ecce6c388b21ccc5dfe738f4add1d39f2c9ca5ab4f3f5a35c2eeff86f63
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
13
+
14
+ .ruby-version
15
+ .ruby-gemset
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,38 @@
1
+ # Docdata-order RuboCop configuration
2
+
3
+ AllCops:
4
+ TargetRubyVersion: 2.3
5
+ DisplayCopNames: true
6
+ DisplayStyleGuide: true
7
+
8
+ Metrics/AbcSize:
9
+ Max: 53
10
+
11
+ Metrics/BlockLength:
12
+ Exclude:
13
+ - 'spec/**/*.rb'
14
+
15
+ Metrics/ClassLength:
16
+ Enabled: false
17
+
18
+ Metrics/LineLength:
19
+ Enabled: false
20
+
21
+ Metrics/MethodLength:
22
+ Enabled: false
23
+
24
+ Style/BlockDelimiters:
25
+ Exclude:
26
+ - 'spec/**/*.rb'
27
+
28
+ Style/GuardClause:
29
+ Enabled: false
30
+
31
+ Style/SafeNavigation:
32
+ Enabled: false
33
+
34
+ Style/StringLiterals:
35
+ Enabled: false
36
+
37
+ Style/SymbolArray:
38
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ sudo: false
3
+ cache: bundler
4
+ rvm:
5
+ - 2.2.9
6
+ - 2.3.6
7
+ - 2.4.3
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Docdata::Order changelog
2
+
3
+ ## 1.0.0 (2018-02-09)
4
+
5
+ - First public release.
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in docdata-order.gemspec
6
+ gemspec
7
+
8
+ gem 'rubocop', '~> 0.50.0'
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Peter Postma
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # Docdata::Order
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/docdata-order.svg)](https://badge.fury.io/rb/docdata-order)
4
+ [![Build Status](https://travis-ci.org/KentaaNL/docdata-order.svg?branch=master)](https://travis-ci.org/KentaaNL/docdata-order)
5
+ [![Code Climate](https://codeclimate.com/github/KentaaNL/docdata-order/badges/gpa.svg)](https://codeclimate.com/github/KentaaNL/docdata-order)
6
+
7
+ Docdata::Order is a Ruby client for the Docdata Order API version 1.3.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'docdata-order'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install docdata-order
24
+
25
+ ## Usage
26
+
27
+ ### Initialization
28
+
29
+ Create a Docdata Order client and configure it with your merchant name and password:
30
+
31
+ ```ruby
32
+ order = Docdata::Order::Client.new("name", "password")
33
+ ```
34
+
35
+ The client is configured in live mode by default. To put the client in test mode, use `test: true` as third parameter.
36
+
37
+ ### Create an order
38
+
39
+ Create a new order with the `create` method. You need to provide the following parameters to create the order:
40
+
41
+ ```ruby
42
+ options = {
43
+ amount: "12.50",
44
+ order_reference: "12345",
45
+ description: "Test order",
46
+ profile: "profile",
47
+ shopper: {
48
+ first_name: "John",
49
+ last_name: "Doe",
50
+ email: "john.doe@example.com",
51
+ language: "en",
52
+ gender: Docdata::Order::Gender::MALE
53
+ },
54
+ address: {
55
+ street: "Jansbuitensingel",
56
+ house_number: "29",
57
+ postal_code: "6811AD",
58
+ city: "Arnhem",
59
+ country: "NL"
60
+ }
61
+ }
62
+
63
+ response = order.create(options)
64
+
65
+ if response.success?
66
+ puts response.order_key
67
+ puts response.redirect_url
68
+ else
69
+ puts response.error_message
70
+ end
71
+ ```
72
+
73
+ The `redirect_url` in the response will redirect the user to the Docdata Payment Menu (One Page Checkout).
74
+
75
+ To create an order and automatically redirect to the bank using iDEAL as payment method, use the parameters above including:
76
+
77
+ ```ruby
78
+ response = order.create(options.merge(
79
+ payment_method: Docdata::Order::PaymentMethod::IDEAL,
80
+ issuer_id: Docdata::Order::Ideal::ISSUERS.first[0],
81
+ return_url: "http://yourwebshop.nl/payment_return"
82
+ ))
83
+ ```
84
+
85
+ ### Start a payment order
86
+
87
+ When using Webdirect, you can use `start` to start a payment order.
88
+
89
+ ```ruby
90
+ options = {
91
+ order_key: "12345",
92
+ payment_method: Docdata::Order::PaymentMethod::SEPA_DIRECT_DEBIT,
93
+ account_name: "Onderheuvel",
94
+ account_iban: "NL44RABO0123456789"
95
+ }
96
+
97
+ response = order.start(options)
98
+
99
+ if response.success?
100
+ puts response.payment_id
101
+ else
102
+ puts response.error_message
103
+ end
104
+ ```
105
+
106
+ ### Retrieve status of an order
107
+
108
+ To retrieve the status of an order, use `status` with the order key:
109
+
110
+ ```ruby
111
+ response = order.status(order_key: "12345")
112
+
113
+ if response.success?
114
+ puts response.paid?
115
+ else
116
+ puts response.error_message
117
+ end
118
+ ```
119
+
120
+ ## Development
121
+
122
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
123
+
124
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
125
+
126
+ ## Contributing
127
+
128
+ Bug reports and pull requests are welcome on GitHub at https://github.com/KentaaNL/docdata-order.
129
+
130
+
131
+ ## License
132
+
133
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
134
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+ require "rubocop/rake_task"
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ RuboCop::RakeTask.new(:rubocop)
9
+
10
+ task default: [:spec, :rubocop]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "docdata/order"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'docdata/order/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "docdata-order"
9
+ spec.version = Docdata::Order::VERSION
10
+ spec.authors = ["Kentaa"]
11
+ spec.email = ["support@kentaa.nl"]
12
+
13
+ spec.summary = "Ruby client for the Docdata Order API"
14
+ spec.homepage = "https://github.com/KentaaNL/docdata-order"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.required_ruby_version = ">= 2.0.0"
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.14"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ spec.add_development_dependency "rspec", "~> 3.0"
29
+ spec.add_development_dependency "webmock", "~> 2.3", ">= 2.3.2"
30
+
31
+ spec.add_dependency "savon", ">= 2.0", "< 3.0"
32
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "docdata/order/client"
4
+ require "docdata/order/exception"
5
+ require "docdata/order/gender"
6
+ require "docdata/order/ideal"
7
+ require "docdata/order/payment_method"
8
+ require "docdata/order/request"
9
+ require "docdata/order/response"
10
+ require "docdata/order/urls"
11
+ require "docdata/order/version"
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "savon"
4
+
5
+ module Docdata
6
+ module Order
7
+ # Client for the Docdata Order API.
8
+ class Client
9
+ XMLNS_DDP = "http://www.docdatapayments.com/services/paymentservice/1_3/"
10
+ DDP_VERSION = "1.3"
11
+
12
+ def initialize(name, password, options = {})
13
+ @options = options.merge(merchant: { name: name, password: password })
14
+ end
15
+
16
+ def create(options = {})
17
+ params = @options.merge(options)
18
+
19
+ response = client.call(:create, message: CreateRequest.new(params), attributes: { xmlns: XMLNS_DDP, version: DDP_VERSION })
20
+
21
+ raise Exception, response unless response.success?
22
+
23
+ CreateResponse.new(params, response)
24
+ end
25
+
26
+ def start(options = {})
27
+ params = @options.merge(options)
28
+
29
+ response = client.call(:start, message: StartRequest.new(params), attributes: { xmlns: XMLNS_DDP, version: DDP_VERSION })
30
+
31
+ raise Exception, response unless response.success?
32
+
33
+ StartResponse.new(params, response)
34
+ end
35
+
36
+ def status(options = {})
37
+ params = @options.merge(options)
38
+
39
+ response = client.call(:status_extended, message: ExtendedStatusRequest.new(params), attributes: { xmlns: XMLNS_DDP, version: DDP_VERSION })
40
+
41
+ raise Exception, response unless response.success?
42
+
43
+ ExtendedStatusResponse.new(params, response)
44
+ end
45
+
46
+ private
47
+
48
+ def client
49
+ @client ||= begin
50
+ params = { wsdl: wsdl_url, raise_errors: false, namespace_identifier: nil, namespaces: { "xmlns:ddp" => XMLNS_DDP } }
51
+
52
+ if @options[:debug]
53
+ params.merge!(log: true, log_level: :debug, pretty_print_xml: true)
54
+ end
55
+
56
+ params[:logger] = Rails.logger if defined?(Rails)
57
+
58
+ Savon.client(params)
59
+ end
60
+ end
61
+
62
+ def wsdl_url
63
+ if @options[:test]
64
+ Urls::WSDL_TEST_URL
65
+ else
66
+ Urls::WSDL_LIVE_URL
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docdata
4
+ module Order
5
+ class Exception < StandardError
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docdata
4
+ module Order
5
+ module Gender
6
+ MALE = "M"
7
+ FEMALE = "F"
8
+ UNDEFINED = "U"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docdata
4
+ module Order
5
+ module Ideal
6
+ ISSUERS = {
7
+ "ABNANL2A" => "ABN AMRO",
8
+ "ASNBNL21" => "ASN Bank",
9
+ "BUNQNL2A" => "Bunq",
10
+ "INGBNL2A" => "ING",
11
+ "KNABNL2H" => "Knab bank",
12
+ "RABONL2U" => "Rabobank",
13
+ "RBRBNL21" => "RegioBank",
14
+ "SNSBNL2A" => "SNS Bank",
15
+ "TRIONL2U" => "Triodos Bank",
16
+ "FVLBNL22" => "Van Lanschot"
17
+ }.freeze
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docdata
4
+ module Order
5
+ module PaymentMethod
6
+ IDEAL = "IDEAL"
7
+ VISA = "VISA"
8
+ MASTER_CARD = "MASTERCARD"
9
+ AMERICAN_EXPRESS = "AMEX"
10
+ PAYPAL = "PAYPAL_EXPRESS_CHECKOUT"
11
+ SEPA_DIRECT_DEBIT = "SEPA_DIRECT_DEBIT"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,282 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "builder/xmlmarkup"
4
+ require "bigdecimal"
5
+ require "securerandom"
6
+
7
+ module Docdata
8
+ module Order
9
+ # Base class for XML requests to Docdata.
10
+ class Request
11
+ attr_reader :options
12
+
13
+ def initialize(options = {})
14
+ @options = options
15
+ end
16
+
17
+ def to_s
18
+ builder = Builder::XmlMarkup.new
19
+
20
+ # Merchant credentials.
21
+ builder.merchant(name: merchant_name, password: merchant_password)
22
+
23
+ build_request(builder)
24
+
25
+ # This element contains information about the application contacting the webservice.
26
+ # This info is useful when debugging troubleshooting technical integration issues.
27
+ builder.integrationInfo do |integration|
28
+ # The name of the plugin used to contact this webservice.
29
+ integration.webshopPlugin("docdata-order")
30
+ # The version of the plugin used to contact this webservice.
31
+ integration.webshopPluginVersion(Docdata::Order::VERSION)
32
+ # The name of the plugin creator used to contact this webservice.
33
+ integration.integratorName("Kentaa")
34
+ # The programming language used to contact this webservice.
35
+ integration.programmingLanguage("Ruby #{RUBY_VERSION}")
36
+ # The operating system from which this webservice is contacted.
37
+ integration.operatingSystem(RUBY_PLATFORM)
38
+ # The full version number (including minor e.q. 1.3.0)
39
+ # of the xsd which is used during integration. DDP can make minor
40
+ # (non-breaking) changes to the xsd. These are reflected in a minor
41
+ # version number. It can therefore be useful to know if a different
42
+ # minor version of the xsd was used during merchant development than
43
+ # the one currently active in production.
44
+ integration.ddpXsdVersion(Docdata::Order::Client::DDP_VERSION)
45
+ end
46
+
47
+ builder.target!
48
+ end
49
+
50
+ private
51
+
52
+ def merchant_name
53
+ options.fetch(:merchant).fetch(:name)
54
+ end
55
+
56
+ def merchant_password
57
+ options.fetch(:merchant).fetch(:password)
58
+ end
59
+
60
+ def build_request
61
+ raise NotImplementedError
62
+ end
63
+ end
64
+
65
+ # Create an Order in the Docdata system.
66
+ class CreateRequest < Request
67
+ def build_request(builder)
68
+ # Unique merchant reference to this order.
69
+ builder.merchantOrderReference(order_reference)
70
+
71
+ # Preferences to use for this payment.
72
+ builder.paymentPreferences do |preferences|
73
+ # The profile that is used to select the payment methods that can be used to pay this order.
74
+ preferences.profile(profile)
75
+ preferences.numberOfDaysToPay(14)
76
+ end
77
+
78
+ # Information concerning the shopper who placed the order.
79
+ builder.shopper(id: shopper_id) do |shopper|
80
+ # Shopper's full name.
81
+ shopper.name do |name|
82
+ # The first given name.
83
+ name.first(shopper_first_name)
84
+ # Any subsequent given name or names. May also be used as middle initial.
85
+ name.middle(shopper_infix) if shopper_infix
86
+ # The family or inherited name(s).
87
+ name.last(shopper_last_name)
88
+ end
89
+
90
+ # Shopper's e-mail address.
91
+ shopper.email(shopper_email)
92
+ # Shopper's preferred language. Language code according to ISO 639.
93
+ shopper.language(code: shopper_language)
94
+ # Shopper's gender.
95
+ shopper.gender(shopper_gender)
96
+ # Ip address of the shopper. Will be used in the future for riskchecks. Can be ipv4 or ipv6.
97
+ shopper.ipAddress(shopper_ip_address) if shopper_ip_address
98
+ end
99
+
100
+ # Total order gross amount. The amount in the minor unit for the given currency.
101
+ # (E.g. for EUR in cents)
102
+ builder.totalGrossAmount(amount, currency: currency)
103
+
104
+ # Name and address to use for billing.
105
+ builder.billTo do |bill|
106
+ bill.name do |name|
107
+ # The first given name.
108
+ name.first(shopper_first_name)
109
+ # Any subsequent given name or names. May also be used as middle initial.
110
+ name.middle(shopper_infix) if shopper_infix
111
+ # The family or inherited name(s).
112
+ name.last(shopper_last_name)
113
+ end
114
+ # Address of the destination.
115
+ bill.address do |address|
116
+ # Address lines must be filled as specific as possible using the house number
117
+ # and optionally the house number addition field.
118
+ address.street(address_street)
119
+ # The house number.
120
+ address.houseNumber(address_house_number)
121
+ address.postalCode(address_postal_code)
122
+ address.city(address_city)
123
+ # Country code according to ISO 3166.
124
+ address.country(code: address_country)
125
+ end
126
+ end
127
+
128
+ # The description of the order.
129
+ builder.description(description)
130
+
131
+ # The description that is used by payment providers on shopper statements.
132
+ builder.receiptText(receipt_text)
133
+ end
134
+
135
+ private
136
+
137
+ def amount
138
+ decimal = BigDecimal.new(options.fetch(:amount).to_s)
139
+ decimal *= 100 # to cents
140
+ decimal.to_i
141
+ end
142
+
143
+ def order_reference
144
+ options.fetch(:order_reference)
145
+ end
146
+
147
+ def profile
148
+ options.fetch(:profile)
149
+ end
150
+
151
+ def shopper_id
152
+ options.fetch(:shopper)[:id] || SecureRandom.hex
153
+ end
154
+
155
+ def shopper_first_name
156
+ options.fetch(:shopper).fetch(:first_name)
157
+ end
158
+
159
+ def shopper_infix
160
+ options.fetch(:shopper)[:infix]
161
+ end
162
+
163
+ def shopper_last_name
164
+ options.fetch(:shopper).fetch(:last_name)
165
+ end
166
+
167
+ def shopper_email
168
+ options.fetch(:shopper).fetch(:email)
169
+ end
170
+
171
+ def shopper_language
172
+ options.fetch(:shopper).fetch(:language)
173
+ end
174
+
175
+ def shopper_gender
176
+ options.fetch(:shopper).fetch(:gender)
177
+ end
178
+
179
+ def shopper_ip_address
180
+ options.fetch(:shopper)[:ip_address]
181
+ end
182
+
183
+ def address_street
184
+ options.fetch(:address).fetch(:street)
185
+ end
186
+
187
+ def address_house_number
188
+ options.fetch(:address).fetch(:house_number)
189
+ end
190
+
191
+ def address_postal_code
192
+ options.fetch(:address).fetch(:postal_code)
193
+ end
194
+
195
+ def address_city
196
+ options.fetch(:address).fetch(:city)
197
+ end
198
+
199
+ def address_country
200
+ options.fetch(:address).fetch(:country)
201
+ end
202
+
203
+ def currency
204
+ options[:currency] || "EUR"
205
+ end
206
+
207
+ def description
208
+ options.fetch(:description)
209
+ end
210
+
211
+ def receipt_text
212
+ options.fetch(:description)[0, 49]
213
+ end
214
+ end
215
+
216
+ # Start a payment order (Webdirect).
217
+ class StartRequest < Request
218
+ def build_request(builder)
219
+ # Payment order key belonging to the order for which a transaction needs to be started.
220
+ builder.paymentOrderKey(order_key)
221
+
222
+ builder.payment do |payment|
223
+ payment.paymentMethod(payment_method)
224
+
225
+ case payment_method
226
+ when PaymentMethod::IDEAL
227
+ payment.iDealPaymentInput do |input|
228
+ input.issuerId(issuer_id)
229
+ end
230
+ when PaymentMethod::SEPA_DIRECT_DEBIT
231
+ payment.directDebitPaymentInput do |input|
232
+ input.holderName(account_name)
233
+ input.iban(account_iban)
234
+ input.bic(account_bic) if account_bic
235
+ end
236
+ else
237
+ raise ArgumentError, "Payment method not supported: #{payment_method}"
238
+ end
239
+ end
240
+ end
241
+
242
+ private
243
+
244
+ def order_key
245
+ options.fetch(:order_key)
246
+ end
247
+
248
+ def payment_method
249
+ options.fetch(:payment_method)
250
+ end
251
+
252
+ def issuer_id
253
+ options.fetch(:issuer_id)
254
+ end
255
+
256
+ def account_name
257
+ options.fetch(:account_name)
258
+ end
259
+
260
+ def account_iban
261
+ options.fetch(:account_iban)
262
+ end
263
+
264
+ def account_bic
265
+ options[:account_bic]
266
+ end
267
+ end
268
+
269
+ # Retrieve additional status information of an Order.
270
+ class ExtendedStatusRequest < Request
271
+ def build_request(builder)
272
+ builder.paymentOrderKey(order_key)
273
+ end
274
+
275
+ private
276
+
277
+ def order_key
278
+ options.fetch(:order_key)
279
+ end
280
+ end
281
+ end
282
+ end
@@ -0,0 +1,328 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module Docdata
6
+ module Order
7
+ # Base class for responses.
8
+ class Response
9
+ attr_reader :options, :response
10
+
11
+ def initialize(options, response)
12
+ @options = options
13
+ @response = response
14
+ end
15
+
16
+ def body
17
+ response.body
18
+ end
19
+
20
+ def error_message
21
+ errors[:error] if errors
22
+ end
23
+
24
+ def error_code
25
+ errors[:error].attributes["code"] if errors
26
+ end
27
+ end
28
+
29
+ # Response to a create operation.
30
+ class CreateResponse < Response
31
+ def data
32
+ body[:create_response]
33
+ end
34
+
35
+ def success?
36
+ data.key?(:create_success)
37
+ end
38
+
39
+ def error?
40
+ data.key?(:create_errors)
41
+ end
42
+
43
+ def errors
44
+ data[:create_errors]
45
+ end
46
+
47
+ def order_key
48
+ data[:create_success][:key]
49
+ end
50
+
51
+ def redirect_url
52
+ params = {
53
+ command: "show_payment_cluster",
54
+ merchant_name: merchant_name,
55
+ client_language: client_language,
56
+ payment_cluster_key: order_key
57
+ }
58
+
59
+ if payment_method
60
+ params[:default_pm] = payment_method
61
+
62
+ case payment_method
63
+ when PaymentMethod::IDEAL
64
+ params[:default_act] = true
65
+ params[:ideal_issuer_id] = issuer_id if issuer_id
66
+ when PaymentMethod::PAYPAL
67
+ params[:default_act] = true
68
+ end
69
+ end
70
+
71
+ if return_url
72
+ params.merge!(
73
+ return_url_success: build_return_url("success"),
74
+ return_url_canceled: build_return_url("cancelled"),
75
+ return_url_pending: build_return_url("pending"),
76
+ return_url_error: build_return_url("error")
77
+ )
78
+ end
79
+
80
+ uri = URI.parse(redirect_base_url)
81
+ uri.query = URI.encode_www_form(params)
82
+ uri.to_s
83
+ end
84
+
85
+ private
86
+
87
+ def redirect_base_url
88
+ if options[:test]
89
+ Urls::MENU_TEST_URL
90
+ else
91
+ Urls::MENU_LIVE_URL
92
+ end
93
+ end
94
+
95
+ def merchant_name
96
+ options.fetch(:merchant).fetch(:name)
97
+ end
98
+
99
+ def client_language
100
+ options.fetch(:shopper).fetch(:language)
101
+ end
102
+
103
+ def payment_method
104
+ options[:payment_method]
105
+ end
106
+
107
+ def issuer_id
108
+ options[:issuer_id]
109
+ end
110
+
111
+ def return_url
112
+ options[:return_url]
113
+ end
114
+
115
+ def build_return_url(status)
116
+ uri = URI.parse(return_url)
117
+ query = URI.decode_www_form(uri.query || "") << ["status", status]
118
+ uri.query = URI.encode_www_form(query)
119
+ uri.to_s
120
+ end
121
+ end
122
+
123
+ # Response to a start operation.
124
+ class StartResponse < Response
125
+ def data
126
+ body[:start_response]
127
+ end
128
+
129
+ def success?
130
+ data.key?(:start_success)
131
+ end
132
+
133
+ def error?
134
+ data.key?(:start_errors)
135
+ end
136
+
137
+ def errors
138
+ data[:start_errors]
139
+ end
140
+
141
+ def payment_response
142
+ data[:start_success][:payment_response]
143
+ end
144
+
145
+ def payment_success
146
+ payment_response[:payment_success] if payment_response
147
+ end
148
+
149
+ def payment_id
150
+ payment_success[:id] if payment_success
151
+ end
152
+ end
153
+
154
+ # Response to a extended status operation.
155
+ class ExtendedStatusResponse < Response
156
+ def data
157
+ body[:extended_status_response]
158
+ end
159
+
160
+ def success?
161
+ data.key?(:status_success)
162
+ end
163
+
164
+ def error?
165
+ data.key?(:status_errors)
166
+ end
167
+
168
+ def errors
169
+ data[:status_errors]
170
+ end
171
+
172
+ def report
173
+ data[:status_success][:report]
174
+ end
175
+
176
+ def payment
177
+ payment = report[:payment]
178
+
179
+ # When multiple payments are found, return the payment with the newest ID.
180
+ if payment.is_a?(Array)
181
+ payment.sort_by { |key, _value| key[:id] }.last
182
+ else
183
+ payment
184
+ end
185
+ end
186
+
187
+ def payment_id
188
+ payment[:id] if payment
189
+ end
190
+
191
+ def payment_method
192
+ payment[:payment_method] if payment
193
+ end
194
+
195
+ def authorization
196
+ payment[:authorization] if payment
197
+ end
198
+
199
+ # The state of the authorization. Current known values:
200
+ # NEW This is a new payment without any actions performed on it yet.
201
+ # RISK_CHECK_OK For this payment the risk check was okay.
202
+ # RISK_CHECK_FAILED The risk check for this payment triggered.
203
+ # STARTED The user has provided details and is authenticated.
204
+ # START_ERROR The payment system could not start the payment due to a technical error.
205
+ # AUTHENTICATED The shopper is authenticated by the acquirer.
206
+ # REDIRECTED_FOR_AUTHENTICATION The shopper is redirected to the acquirer web-site for authentication.
207
+ # AUTHENTICATION_FAILED The shopper is not authenticated by the acquirer.
208
+ # AUTHENTICATION_ERROR Unable to do the authentication of the shopper by the acquirer.
209
+ # AUTHORIZED This payment is authorized.
210
+ # REDIRECTED_FOR_AUTHORIZATION The shopper is redirected to the acquirer web-site for authorization.
211
+ # AUTHORIZATION_REQUESTED Requested authorization for this payment, waiting for a notification from acquirer.
212
+ # AUTHORIZATION_FAILED The payment was not authorized due to a functional error.
213
+ # AUTHORIZATION_ERROR Unable to do the payment authorization due to a technical error.
214
+ # CANCELED The payment is canceled.
215
+ # CANCEL_FAILED Payment is not canceled, due to functional error.
216
+ # CANCEL_ERROR Payment is not canceled, due to technical error.
217
+ # CANCEL_REQUESTED A cancel request sent to acquirer.
218
+ def authorization_status
219
+ authorization[:status] if authorization
220
+ end
221
+
222
+ def approximate_totals
223
+ report[:approximate_totals]
224
+ end
225
+
226
+ def total_registered
227
+ to_decimal(approximate_totals[:total_registered])
228
+ end
229
+
230
+ def total_shopper_pending
231
+ to_decimal(approximate_totals[:total_shopper_pending])
232
+ end
233
+
234
+ def total_acquirer_pending
235
+ to_decimal(approximate_totals[:total_acquirer_pending])
236
+ end
237
+
238
+ def total_acquirer_approved
239
+ to_decimal(approximate_totals[:total_acquirer_approved])
240
+ end
241
+
242
+ def total_captured
243
+ to_decimal(approximate_totals[:total_captured])
244
+ end
245
+
246
+ def total_refunded
247
+ to_decimal(approximate_totals[:total_refunded])
248
+ end
249
+
250
+ def total_reversed
251
+ to_decimal(approximate_totals[:total_chargedback])
252
+ end
253
+
254
+ def total_charged_back
255
+ to_decimal(approximate_totals[:total_chargedback])
256
+ end
257
+
258
+ def paid?
259
+ (total_registered == total_captured) || (total_registered == total_acquirer_approved)
260
+ end
261
+
262
+ def refunded?
263
+ total_registered == total_refunded
264
+ end
265
+
266
+ def charged_back?
267
+ total_registered == total_charged_back
268
+ end
269
+
270
+ def reversed?
271
+ total_registered == total_reversed
272
+ end
273
+
274
+ def started?
275
+ (authorization_status == "NEW" || authorization_status == "STARTED" ||
276
+ (total_captured.zero? && total_acquirer_approved.zero?)) && authorization_status != "CANCELED"
277
+ end
278
+
279
+ def cancelled?
280
+ authorization_status == "CANCELED"
281
+ end
282
+
283
+ def account_iban
284
+ case payment_method
285
+ when PaymentMethod::IDEAL
286
+ payment_info = payment[:extended][:i_deal_payment_info]
287
+ payment_info[:shopper_bank_account][:iban] if payment_info && payment_info[:shopper_bank_account]
288
+ when PaymentMethod::SEPA_DIRECT_DEBIT
289
+ payment_info = payment[:extended][:sepa_direct_debit_payment_info]
290
+ payment_info[:iban] if payment_info
291
+ end
292
+ end
293
+
294
+ def account_bic
295
+ case payment_method
296
+ when PaymentMethod::IDEAL
297
+ payment_info = payment[:extended][:i_deal_payment_info]
298
+ payment_info[:shopper_bank_account][:bic] if payment_info && payment_info[:shopper_bank_account]
299
+ when PaymentMethod::SEPA_DIRECT_DEBIT
300
+ payment_info = payment[:extended][:sepa_direct_debit_payment_info]
301
+ payment_info[:bic] if payment_info
302
+ end
303
+ end
304
+
305
+ def account_name
306
+ if payment_method == PaymentMethod::IDEAL
307
+ payment_info = payment[:extended][:i_deal_payment_info]
308
+ payment_info[:holder_name] if payment_info
309
+ end
310
+ end
311
+
312
+ def mandate_number
313
+ if payment_method == PaymentMethod::SEPA_DIRECT_DEBIT
314
+ payment_info = payment[:extended][:sepa_direct_debit_payment_info]
315
+ payment_info[:mandate_number] if payment_info
316
+ end
317
+ end
318
+
319
+ private
320
+
321
+ def to_decimal(cents)
322
+ total = BigDecimal.new(cents)
323
+ total /= 100.0
324
+ total
325
+ end
326
+ end
327
+ end
328
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docdata
4
+ module Order
5
+ module Urls
6
+ # WSDL location, see Order API 1.3.
7
+ WSDL_LIVE_URL = "https://secure.docdatapayments.com/ps/services/paymentservice/1_3?wsdl"
8
+ WSDL_TEST_URL = "https://test.docdatapayments.com/ps/services/paymentservice/1_3?wsdl"
9
+
10
+ # Payment Menu base URL, see Functional API.
11
+ MENU_LIVE_URL = "https://secure.docdatapayments.com/ps/menu"
12
+ MENU_TEST_URL = "https://test.docdatapayments.com/ps/menu"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docdata
4
+ module Order
5
+ VERSION = "1.0.0"
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,148 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: docdata-order
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Kentaa
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-02-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: webmock
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.3'
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 2.3.2
65
+ type: :development
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - "~>"
70
+ - !ruby/object:Gem::Version
71
+ version: '2.3'
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 2.3.2
75
+ - !ruby/object:Gem::Dependency
76
+ name: savon
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '2.0'
82
+ - - "<"
83
+ - !ruby/object:Gem::Version
84
+ version: '3.0'
85
+ type: :runtime
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '2.0'
92
+ - - "<"
93
+ - !ruby/object:Gem::Version
94
+ version: '3.0'
95
+ description:
96
+ email:
97
+ - support@kentaa.nl
98
+ executables: []
99
+ extensions: []
100
+ extra_rdoc_files: []
101
+ files:
102
+ - ".gitignore"
103
+ - ".rspec"
104
+ - ".rubocop.yml"
105
+ - ".travis.yml"
106
+ - CHANGELOG.md
107
+ - Gemfile
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - bin/console
112
+ - bin/setup
113
+ - docdata-order.gemspec
114
+ - lib/docdata/order.rb
115
+ - lib/docdata/order/client.rb
116
+ - lib/docdata/order/exception.rb
117
+ - lib/docdata/order/gender.rb
118
+ - lib/docdata/order/ideal.rb
119
+ - lib/docdata/order/payment_method.rb
120
+ - lib/docdata/order/request.rb
121
+ - lib/docdata/order/response.rb
122
+ - lib/docdata/order/urls.rb
123
+ - lib/docdata/order/version.rb
124
+ homepage: https://github.com/KentaaNL/docdata-order
125
+ licenses:
126
+ - MIT
127
+ metadata: {}
128
+ post_install_message:
129
+ rdoc_options: []
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: 2.0.0
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ requirements: []
143
+ rubyforge_project:
144
+ rubygems_version: 2.6.14
145
+ signing_key:
146
+ specification_version: 4
147
+ summary: Ruby client for the Docdata Order API
148
+ test_files: []