docdata-order 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []