docdata 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4760635525904b78d47e8390f2c371e45f316a18
4
- data.tar.gz: a91306598ed4b29b5adf81eb172da822ac4e3b3e
3
+ metadata.gz: 0b7fce0fc2d60f3333084ba16516eb7ad414b728
4
+ data.tar.gz: 52f071c31314b98fd530bb62b3f0fb007c4d4b33
5
5
  SHA512:
6
- metadata.gz: 2ad18cf614968cb60eccb8abc1c9577462ee8b9e67bfe701d9887f092d9a42cb55da399d80ac6932578431b81b62d53ffe8fbebde142719b78349f2fb79129b6
7
- data.tar.gz: 9f31c54925a1b75fd2bda0ddc6341438256a6de96817153204a7be24121470b7665a9793401a08ca1df93c4eed5fb39875eb0a351b6fe63933a95eb2c971a876
6
+ metadata.gz: 25d68049043573abdbd864625135dab093f86cd2cba3ce5095f9de81bca2b3d6429859e29f63bd2211aae374ada25d8c03d8a8614e0fb21600a6915812f53514
7
+ data.tar.gz: fe7766f96151b852e3fcb4702f977e6b84fbbd73810ac0d969f814cb62b685a1aaed37e65b55cc609403c2b6714fcdfb6f57625260e579fc1959cf2865d52d1c
@@ -1,3 +1,7 @@
1
+ ## v0.1.8
2
+ * You can now make refunds. See documentation.
3
+ * Updated README
4
+
1
5
  ## v0.1.7
2
6
  * Bug fixed where the gem didn't handle docdata XML response with multiple 'payment' nodes well. The gem assumed that the nodes where in chronological order, but they aren't.
3
7
 
data/README.md CHANGED
@@ -82,7 +82,7 @@ If you use `GIROPAY`, `SEPA` and `AFTERPAY` this is the case. (Maybe also in oth
82
82
 
83
83
 
84
84
  ## Configuration in Rails application
85
- Example usage. Use appropriate settings in `development.rb`, `production.rb` etc.
85
+ Example usage. Use appropriate settings in `development.rb`, `production.rb` etc.
86
86
  ```ruby
87
87
  config.docdata.username = "my_app_com"
88
88
  config.docdata.password = "HeJ35N"
@@ -243,17 +243,32 @@ When making a new `Docdata::Payment`, use the `default_act` parameter to redirec
243
243
  #### Check the status of a payment
244
244
  `payment = Docdata::Payment.find("KEY"); payment.status => <Payment::Status>`
245
245
 
246
-
247
246
  #### Cancel a payment
248
247
  To cancel an existing Payment, you can do one of the following:
249
248
  `payment = Docdata::Payment.find("KEY"); payment.cancel` or `Docdata::Payment.cancel("KEY")`
250
249
 
250
+ #### Make refunds
251
+ You can make a refund for a payment. In fact: each payment can have multiple refunds. Each refund has an amount (`Integer` type - cents) and as long as the refund amount doesn't exceed the total Payment amount, you can make as many partial refunds as you whish. Keep in mind that Docdata will charge you for each refund.
252
+
253
+ ```ruby
254
+ payment = Docdata::Payment.find("KEY") # find the payment
255
+ payment.refund(500) # => true or false
256
+ ```
257
+
258
+
259
+
260
+ ## Test credentials
261
+ In order tot test this gem, you'll need to set the following environment variables to make it work. With other words: you can't test this gem without valid test credentials and you can't use my test credentials. Tip: set the environment variables in your .bash_profile file.
262
+ ```
263
+ ENV["DOCDATA_PASSWORD"] = "your_password"
264
+ ENV["DOCDATA_USERNAME"] = "your_docdata_username"
265
+ ENV["DOCDATA_RETURN_URL"] = "http://return-url-here"
266
+ ```
251
267
 
252
268
 
253
269
  ## Contributing
254
270
  Want to contribute? Great!
255
271
 
256
-
257
272
  1. Fork it
258
273
  2. Create your feature branch (`git checkout -b my-new-feature`)
259
274
  3. Make changes, document them and add tests (rspec)
@@ -16,6 +16,7 @@ require "docdata/engine" if defined?(Rails) && Rails::VERSION::MAJOR.to_i >= 3
16
16
  require "docdata/docdata_error"
17
17
  require "docdata/shopper"
18
18
  require "docdata/payment"
19
+ require "docdata/refund"
19
20
  require "docdata/line_item"
20
21
  require "docdata/response"
21
22
  require "docdata/ideal"
@@ -54,6 +54,7 @@ module Docdata
54
54
  attr_accessor :key
55
55
  attr_accessor :default_act
56
56
  attr_accessor :canceled
57
+ attr_accessor :id
57
58
 
58
59
 
59
60
  #
@@ -121,6 +122,27 @@ module Docdata
121
122
  return true
122
123
  end
123
124
 
125
+ #
126
+ # This calls the 'refund' method of the SOAP API
127
+ # It refunds (part of) the amount paid
128
+ def refund(amount_to_refund, refund_description="")
129
+ p = Docdata::Payment.new(key: key)
130
+ p = p.status.payment
131
+ refund_object = Docdata::Refund.new(
132
+ currency: p.currency,
133
+ amount: amount_to_refund,
134
+ description: refund_description,
135
+ payment: p
136
+ )
137
+ if refund_object.valid?
138
+ refund_object.perform_refund
139
+ else
140
+ raise DocdataError.new(refund_object), refund_object.errors.full_messages.join(", ")
141
+ end
142
+ end
143
+
144
+
145
+
124
146
  # This method makes it possible to find and cancel a payment with only the key
125
147
  # It combines
126
148
  def self.cancel(api_key)
@@ -128,6 +150,14 @@ module Docdata
128
150
  p.cancel
129
151
  end
130
152
 
153
+ # This method makes it possible to find and refund a payment with only the key
154
+ # exmaple usage: Docdata::Payment.refund("APIT0K3N", 250)
155
+ def self.refund(api_key, amount_to_refund, refund_description="")
156
+ p = self.find(api_key)
157
+ p.refund(amount_to_refund, refund_description)
158
+ end
159
+
160
+
131
161
  # Initialize a Payment object with the key set
132
162
  def self.find(api_key)
133
163
  p = self.new(key: api_key)
@@ -153,9 +183,12 @@ module Docdata
153
183
  response = Docdata.client.call(:status, xml: xml)
154
184
  response_object = Docdata::Response.parse(:status, response)
155
185
 
186
+ response_object.set_attributes
187
+
188
+ self.id = response_object.pid
189
+ self.currency = response_object.currency
156
190
  response_object.key = key
157
191
  response_object.payment = self
158
-
159
192
  return response_object # Docdata::Response
160
193
  end
161
194
  alias_method :check, :status
@@ -216,6 +249,7 @@ module Docdata
216
249
  xml = ERB.new(template).result(namespace.instance_eval { binding })
217
250
  end
218
251
 
252
+
219
253
  private
220
254
 
221
255
  # In case there are any line_items, validate them all and
@@ -0,0 +1,77 @@
1
+ module Docdata
2
+
3
+
4
+ # Creates a validator
5
+ class RefundValidator
6
+ include Veto.validator
7
+ validates :amount, presence: true, integer: true
8
+ validates :currency, presence: true, format: /[A-Z]{3}/
9
+ validates :payment, presence: true
10
+ end
11
+
12
+
13
+ #
14
+ # Refund
15
+ #
16
+ # @example
17
+ # Refund.new({
18
+ # :amount => 2500,
19
+ # :currency => "EUR",
20
+ # :description => "Canceled order #123"
21
+ # :payment => @payment
22
+ # })
23
+ #
24
+
25
+ #
26
+ class Refund
27
+ attr_accessor :errors
28
+ attr_accessor :amount
29
+ attr_accessor :currency
30
+ attr_accessor :payment
31
+ attr_accessor :description
32
+ @@amount = "?"
33
+
34
+ #
35
+ # Initializer to transform a +Hash+ into an Refund object
36
+ #
37
+ # @param [Hash] args
38
+ def initialize(args=nil)
39
+ @line_items = []
40
+ return if args.nil?
41
+ args.each do |k,v|
42
+ instance_variable_set("@#{k}", v) unless v.nil?
43
+ end
44
+ end
45
+
46
+ # @return [Boolean] true/false, depending if this instanciated object is valid
47
+ def valid?
48
+ validator = RefundValidator.new
49
+ validator.valid?(self)
50
+ end
51
+
52
+ #
53
+ # This calls the 'refund' method of the SOAP API
54
+ # It refunds (part of) the payment and returns a Docdata::Response object
55
+ def perform_refund
56
+ # make the SOAP API call
57
+ response = Docdata.client.call(:refund, xml: refund_xml)
58
+ response_object = Docdata::Response.parse(:refund, response)
59
+ if response_object.success?
60
+ return true
61
+ else
62
+ return false
63
+ end
64
+ end
65
+
66
+ # @return [String] the xml to send in the SOAP API
67
+ def refund_xml
68
+ xml_file = "#{File.dirname(__FILE__)}/xml/refund.xml.erb"
69
+ template = File.read(xml_file)
70
+ namespace = OpenStruct.new(refund: self, payment: self.payment)
71
+ xml = ERB.new(template).result(namespace.instance_eval { binding })
72
+ end
73
+
74
+
75
+ end
76
+
77
+ end
@@ -133,6 +133,15 @@ module Docdata
133
133
  end
134
134
  end
135
135
 
136
+ # @return [String] the PID of the transaction
137
+ def pid
138
+ if report && Response.payment_node(report) && Response.payment_node(report)[:id]
139
+ Response.payment_node(report)[:id]
140
+ else
141
+ nil
142
+ end
143
+ end
144
+
136
145
  # @return [Boolean] true/false, depending wether this payment is considered paid.
137
146
  # @note Docdata doesn't explicitly say 'paid' or 'not paid', this is a little bit a gray area.
138
147
  # There are several approaches to determine if a payment is paid, some slow and safe, other quick and unreliable.
@@ -1,3 +1,3 @@
1
1
  module Docdata
2
- VERSION = "0.1.7"
2
+ VERSION = "0.1.8"
3
3
  end
@@ -0,0 +1,15 @@
1
+ <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:_1="http://www.docdatapayments.com/services/paymentservice/1_1/">
2
+ <soapenv:Header/>
3
+ <soapenv:Body>
4
+ <_1:refundRequest version="1.1">
5
+ <_1:merchant name="<%= Docdata::Config.username %>" password="<%= Docdata::Config.password %>"/>
6
+ <_1:paymentId><%= payment.id %></_1:paymentId>
7
+ <!--Optional:-->
8
+ <% if refund.description.present? %>
9
+ <_1:merchantRefundReference><%= refund.description %></_1:merchantRefundReference>
10
+ <% end %>
11
+ <!--Optional:-->
12
+ <_1:amount currency="<%= refund.currency %>"><%= refund.amount %></_1:amount>
13
+ </_1:refundRequest>
14
+ </soapenv:Body>
15
+ </soapenv:Envelope>
@@ -141,6 +141,18 @@ describe Docdata::Payment do
141
141
  expect(@new_payment).to be_kind_of(Docdata::Payment)
142
142
  end
143
143
 
144
+ it "returns a pid" do
145
+ file = "#{File.dirname(__FILE__)}/xml/status-paid-ideal.xml"
146
+ @xml = open(file)
147
+ @response = Docdata::Response.parse(:status, @xml)
148
+
149
+ @payment = @response.payment
150
+ expect(@response.is_paid?).to eq(true)
151
+ # expect(@payment).to be_kind_of(Docdata::Payment)
152
+ expect(@response.pid).to match /[0-9]{10}/
153
+
154
+ end
155
+
144
156
  it "raises error if order is not found" do
145
157
  VCR.use_cassette("perform-invalid-status-call") do
146
158
  Docdata.set_credentials_from_environment
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+
3
+ describe Docdata::Refund do
4
+
5
+ context "validation" do
6
+ it "has validation" do
7
+ refund = Docdata::Refund.new
8
+ expect(refund).not_to be_valid
9
+ end
10
+
11
+ it "validates amount" do
12
+ refund = Docdata::Refund.new
13
+ expect(refund).not_to be_valid
14
+ expect(refund.errors.full_messages).to eq(["amount is not present", "amount is not a number", "currency is not present", "currency is not valid", "payment is not present"])
15
+ end
16
+ end
17
+
18
+ context "valid refund" do
19
+ before(:each) do
20
+ Docdata.set_credentials_from_environment
21
+ Docdata::Config.test_mode = true
22
+ VCR.use_cassette("find-payment-by-key") do
23
+ @payment = Docdata::Payment.find("2BAFAEB26EF760458B9343DEA4950D91")
24
+ # puts @payment.inspect
25
+ end
26
+ end
27
+
28
+ it "has a currency" do
29
+ expect(@payment.currency).to be_present
30
+ expect(@payment.currency).to eq("EUR")
31
+ end
32
+
33
+ it "performs refund" do
34
+ VCR.use_cassette("refund-amount") do
35
+ expect(@payment.refund(100)).to eq(true)
36
+ end
37
+ end
38
+
39
+ it "performs refund with description" do
40
+ VCR.use_cassette("refund-amount-with-description") do
41
+ expect(@payment.refund(100, "user wanted to cancel...")).to eq(true)
42
+ end
43
+ end
44
+
45
+ end
46
+
47
+ context "successfull response" do
48
+ before(:each) do
49
+ file = "#{File.dirname(__FILE__)}/xml/refund_success.xml"
50
+ @xml = open(file)
51
+ @response = Docdata::Response.parse(:refund, @xml)
52
+ end
53
+
54
+
55
+ it "is successfull" do
56
+ expect(@response).to be_success
57
+ end
58
+
59
+ it "has xml attribute with raw data" do
60
+ expect(@response.xml).to be_present
61
+ end
62
+ end
63
+
64
+ context "invalid amount response" do
65
+ before(:each) do
66
+ file = "#{File.dirname(__FILE__)}/xml/refund_invalid_amount.xml"
67
+ @xml = open(file)
68
+ end
69
+
70
+ it "raises error" do
71
+ expect { Docdata::Response.parse(:refund, @xml) }.to raise_error(DocdataError, "Invalid amount.")
72
+ end
73
+ end
74
+
75
+ context "invalid amount response" do
76
+ before(:each) do
77
+ file = "#{File.dirname(__FILE__)}/xml/refund_no_amount_captured.xml"
78
+ @xml = open(file)
79
+ end
80
+
81
+ it "raises error" do
82
+ expect { Docdata::Response.parse(:refund, @xml) }.to raise_error(DocdataError, "No amount captured available to refund.")
83
+ end
84
+ end
85
+
86
+ end
@@ -33,4 +33,5 @@ end
33
33
 
34
34
  RSpec.configure do |config|
35
35
  config.order = "random"
36
+ # config.filter_run :focus => true
36
37
  end
@@ -0,0 +1,9 @@
1
+ <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
2
+ <S:Body>
3
+ <refundResponse xmlns="http://www.docdatapayments.com/services/paymentservice/1_1/">
4
+ <refundError>
5
+ <error code="REQUEST_DATA_INCORRECT">Invalid amount.</error>
6
+ </refundError>
7
+ </refundResponse>
8
+ </S:Body>
9
+ </S:Envelope>
@@ -0,0 +1,9 @@
1
+ <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
2
+ <S:Body>
3
+ <refundResponse xmlns="http://www.docdatapayments.com/services/paymentservice/1_1/">
4
+ <refundError>
5
+ <error code="REQUEST_DATA_INCORRECT">No amount captured available to refund.</error>
6
+ </refundError>
7
+ </refundResponse>
8
+ </S:Body>
9
+ </S:Envelope>
@@ -0,0 +1,9 @@
1
+ <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
2
+ <S:Body>
3
+ <refundResponse xmlns="http://www.docdatapayments.com/services/paymentservice/1_1/">
4
+ <refundSuccess>
5
+ <success code="SUCCESS">Operation successful.</success>
6
+ </refundSuccess>
7
+ </refundResponse>
8
+ </S:Body>
9
+ </S:Envelope>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: docdata
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Henk Meijer
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-09-24 00:00:00.000000000 Z
12
+ date: 2014-10-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -260,12 +260,14 @@ files:
260
260
  - lib/docdata/ideal.rb
261
261
  - lib/docdata/line_item.rb
262
262
  - lib/docdata/payment.rb
263
+ - lib/docdata/refund.rb
263
264
  - lib/docdata/response.rb
264
265
  - lib/docdata/shopper.rb
265
266
  - lib/docdata/version.rb
266
267
  - lib/docdata/xml/bank-list.xml
267
268
  - lib/docdata/xml/cancel.xml.erb
268
269
  - lib/docdata/xml/create.xml.erb
270
+ - lib/docdata/xml/refund.xml.erb
269
271
  - lib/docdata/xml/start.xml.erb
270
272
  - lib/docdata/xml/status.xml.erb
271
273
  - php-example/create.xml.erb
@@ -277,9 +279,13 @@ files:
277
279
  - spec/ideal_spec.rb
278
280
  - spec/line_item_spec.rb
279
281
  - spec/payment_spec.rb
282
+ - spec/refund_spec.rb
280
283
  - spec/response_spec.rb
281
284
  - spec/shopper_spec.rb
282
285
  - spec/spec_helper.rb
286
+ - spec/xml/refund_invalid_amount.xml
287
+ - spec/xml/refund_no_amount_captured.xml
288
+ - spec/xml/refund_success.xml
283
289
  - spec/xml/status-canceled-creditcard.xml
284
290
  - spec/xml/status-canceled-ideal.xml
285
291
  - spec/xml/status-new.xml
@@ -319,9 +325,13 @@ test_files:
319
325
  - spec/ideal_spec.rb
320
326
  - spec/line_item_spec.rb
321
327
  - spec/payment_spec.rb
328
+ - spec/refund_spec.rb
322
329
  - spec/response_spec.rb
323
330
  - spec/shopper_spec.rb
324
331
  - spec/spec_helper.rb
332
+ - spec/xml/refund_invalid_amount.xml
333
+ - spec/xml/refund_no_amount_captured.xml
334
+ - spec/xml/refund_success.xml
325
335
  - spec/xml/status-canceled-creditcard.xml
326
336
  - spec/xml/status-canceled-ideal.xml
327
337
  - spec/xml/status-new.xml