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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +18 -3
- data/lib/docdata.rb +1 -0
- data/lib/docdata/payment.rb +35 -1
- data/lib/docdata/refund.rb +77 -0
- data/lib/docdata/response.rb +9 -0
- data/lib/docdata/version.rb +1 -1
- data/lib/docdata/xml/refund.xml.erb +15 -0
- data/spec/payment_spec.rb +12 -0
- data/spec/refund_spec.rb +86 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/xml/refund_invalid_amount.xml +9 -0
- data/spec/xml/refund_no_amount_captured.xml +9 -0
- data/spec/xml/refund_success.xml +9 -0
- metadata +12 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b7fce0fc2d60f3333084ba16516eb7ad414b728
|
4
|
+
data.tar.gz: 52f071c31314b98fd530bb62b3f0fb007c4d4b33
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 25d68049043573abdbd864625135dab093f86cd2cba3ce5095f9de81bca2b3d6429859e29f63bd2211aae374ada25d8c03d8a8614e0fb21600a6915812f53514
|
7
|
+
data.tar.gz: fe7766f96151b852e3fcb4702f977e6b84fbbd73810ac0d969f814cb62b685a1aaed37e65b55cc609403c2b6714fcdfb6f57625260e579fc1959cf2865d52d1c
|
data/CHANGELOG.md
CHANGED
@@ -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)
|
data/lib/docdata.rb
CHANGED
@@ -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"
|
data/lib/docdata/payment.rb
CHANGED
@@ -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
|
data/lib/docdata/response.rb
CHANGED
@@ -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.
|
data/lib/docdata/version.rb
CHANGED
@@ -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>
|
data/spec/payment_spec.rb
CHANGED
@@ -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
|
data/spec/refund_spec.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
CHANGED
@@ -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.
|
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-
|
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
|