epay 0.0.4
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.
- data/.gitignore +15 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile +2 -0
- data/Guardfile +10 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +21 -0
- data/Rakefile +8 -0
- data/bin/epay-console +8 -0
- data/epay.gemspec +55 -0
- data/lib/epay.rb +104 -0
- data/lib/epay/api.rb +92 -0
- data/lib/epay/api/response.rb +29 -0
- data/lib/epay/card.rb +23 -0
- data/lib/epay/model.rb +23 -0
- data/lib/epay/subscription.rb +132 -0
- data/lib/epay/transaction.rb +171 -0
- data/lib/epay/version.rb +3 -0
- data/lib/extensions/hash.rb +4 -0
- data/spec/api/response_spec.rb +71 -0
- data/spec/api_spec.rb +58 -0
- data/spec/card_spec.rb +21 -0
- data/spec/fixtures/vcr_cassettes/existing_subscription.yml +75 -0
- data/spec/fixtures/vcr_cassettes/existing_transaction.yml +47 -0
- data/spec/fixtures/vcr_cassettes/non_existing_transaction.yml +48 -0
- data/spec/fixtures/vcr_cassettes/subscription_authorization.yml +95 -0
- data/spec/fixtures/vcr_cassettes/subscription_creation.yml +101 -0
- data/spec/fixtures/vcr_cassettes/subscription_invalid_creation.yml +47 -0
- data/spec/fixtures/vcr_cassettes/subscriptions.yml +87 -0
- data/spec/fixtures/vcr_cassettes/transaction_creation.yml +90 -0
- data/spec/fixtures/vcr_cassettes/transaction_invalid_creation.yml +47 -0
- data/spec/fixtures/vcr_cassettes/transactions.yml +50 -0
- data/spec/helpers/http_responses.rb +13 -0
- data/spec/model_spec.rb +36 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/subscription_spec.rb +139 -0
- data/spec/transaction_spec.rb +245 -0
- metadata +280 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
module Epay
|
2
|
+
class Subscription
|
3
|
+
include Model
|
4
|
+
|
5
|
+
attr_accessor :card_no
|
6
|
+
|
7
|
+
def self.inspectable_attributes
|
8
|
+
%w(id created_at description)
|
9
|
+
end
|
10
|
+
|
11
|
+
def created_at
|
12
|
+
Time.parse(data['created'])
|
13
|
+
end
|
14
|
+
|
15
|
+
def card
|
16
|
+
# The card number can be derived from last transaction
|
17
|
+
if transactions.any?
|
18
|
+
transactions.last.card
|
19
|
+
else
|
20
|
+
Card.new({
|
21
|
+
:exp_year => data['expyear'].to_i,
|
22
|
+
:exp_month => data['expmonth'].to_i,
|
23
|
+
:kind => data['cardtypeid'].downcase.to_sym,
|
24
|
+
:number => card_no
|
25
|
+
})
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def error
|
30
|
+
data['error']
|
31
|
+
end
|
32
|
+
|
33
|
+
def valid?
|
34
|
+
data['error'].nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
def description
|
38
|
+
data['description']
|
39
|
+
end
|
40
|
+
|
41
|
+
def transactions
|
42
|
+
return [] unless data['transactionList'].present?
|
43
|
+
|
44
|
+
# transactionList can be a hash or an array
|
45
|
+
Array.wrap(data['transactionList']['TransactionInformationType']).collect do |transaction_data|
|
46
|
+
Transaction.new(transaction_data['transactionid'].to_i, transaction_data)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Actions
|
51
|
+
def reload
|
52
|
+
response = Epay::Api.request(SUBSCRIPTION_SOAP_URL, 'getsubscriptions', :subscriptionid => id) do |response|
|
53
|
+
# API returns a list of subscriptions
|
54
|
+
if response.success?
|
55
|
+
@data = response.data['subscriptionAry']['SubscriptionInformationType']
|
56
|
+
self
|
57
|
+
else
|
58
|
+
raise(SubscriptionNotFound, "Couldn't find subscription with ID=#{id}")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def authorize(params = {})
|
64
|
+
post = Api.default_post_for_params(params)
|
65
|
+
|
66
|
+
post[:subscriptionid] = id
|
67
|
+
|
68
|
+
Epay::Api.request(SUBSCRIPTION_SOAP_URL, 'authorize', post) do |response|
|
69
|
+
if response.success?
|
70
|
+
Transaction.find(response.data['transactionid'])
|
71
|
+
else
|
72
|
+
Transaction.new(nil, 'error' => response.data['pbsresponse'], 'failed' => true)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def delete
|
78
|
+
Epay::Api.request(SUBSCRIPTION_SOAP_URL, 'delete', :subscriptionid => id).success?
|
79
|
+
end
|
80
|
+
|
81
|
+
class << self
|
82
|
+
# Epay::Subscription.find(1234)
|
83
|
+
def find(id)
|
84
|
+
Subscription.new(id).reload
|
85
|
+
end
|
86
|
+
|
87
|
+
# Epay::Subscription.create(:card_no => '...', :)
|
88
|
+
def create(params)
|
89
|
+
params.merge!(
|
90
|
+
:order_no => (Time.now.to_f * 10000).to_i.to_s, # Produce a unique order_no - it's not used anywhere, but ePay needs this
|
91
|
+
:amount => 0
|
92
|
+
)
|
93
|
+
|
94
|
+
post = Api.default_post_for_params(params).merge({
|
95
|
+
:subscription => 1,
|
96
|
+
:subscriptionname => params[:description]
|
97
|
+
})
|
98
|
+
|
99
|
+
query = Api.authorize(post)
|
100
|
+
|
101
|
+
if query['accept']
|
102
|
+
# Return the new subscriber
|
103
|
+
subscription = new(query["subscriptionid"].to_i).reload
|
104
|
+
|
105
|
+
# Set (obfuscated) card number:
|
106
|
+
subscription.card_no = query["tcardno"]
|
107
|
+
|
108
|
+
subscription
|
109
|
+
else
|
110
|
+
new(nil, 'error' => query["error"])
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def all
|
115
|
+
Epay::Api.request(SUBSCRIPTION_SOAP_URL, 'getsubscriptions') do |response|
|
116
|
+
if response.success?
|
117
|
+
collection = []
|
118
|
+
response.data['subscriptionAry']['SubscriptionInformationType'].each do |subscription|
|
119
|
+
collection << new_from_data(subscription)
|
120
|
+
end
|
121
|
+
|
122
|
+
collection
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def new_from_data(data)
|
128
|
+
new(data['subscriptionid'].to_i, data)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
module Epay
|
2
|
+
class Transaction
|
3
|
+
include Model
|
4
|
+
|
5
|
+
def self.inspectable_attributes
|
6
|
+
%w(id amount created_at order_no description cardholder acquirer currency group captured)
|
7
|
+
end
|
8
|
+
|
9
|
+
def amount
|
10
|
+
data['authamount'].to_f / 100
|
11
|
+
end
|
12
|
+
|
13
|
+
def created_at
|
14
|
+
return nil if failed?
|
15
|
+
Time.parse(data['authdate'])
|
16
|
+
end
|
17
|
+
|
18
|
+
def order_no
|
19
|
+
data['orderid']
|
20
|
+
end
|
21
|
+
|
22
|
+
def currency
|
23
|
+
Epay::CURRENCY_CODES.key(data['currency'])
|
24
|
+
end
|
25
|
+
|
26
|
+
def description
|
27
|
+
data['description']
|
28
|
+
end
|
29
|
+
|
30
|
+
def cardholder
|
31
|
+
data['cardholder']
|
32
|
+
end
|
33
|
+
|
34
|
+
def acquirer
|
35
|
+
data['acquirer']
|
36
|
+
end
|
37
|
+
|
38
|
+
def group
|
39
|
+
data['group']
|
40
|
+
end
|
41
|
+
|
42
|
+
def card
|
43
|
+
# We can find some card data in the history:
|
44
|
+
@card ||= Card.new(:kind => CARD_KINDS[data['cardtypeid'].to_i], :number => data['tcardno'], :exp_year => data['expyear'].to_i, :exp_month => data['expmonth'].to_i)
|
45
|
+
end
|
46
|
+
|
47
|
+
def credited_amount
|
48
|
+
data['creditedamount'].to_f / 100
|
49
|
+
end
|
50
|
+
|
51
|
+
def captured_amount
|
52
|
+
data['capturedamount'].to_f / 100
|
53
|
+
end
|
54
|
+
|
55
|
+
def captured_at
|
56
|
+
return nil if failed?
|
57
|
+
|
58
|
+
time = Time.parse(data['captureddate'].to_s)
|
59
|
+
time < created_at ? nil : time
|
60
|
+
end
|
61
|
+
|
62
|
+
def captured
|
63
|
+
!captured_at.nil?
|
64
|
+
end
|
65
|
+
alias_method :captured?, :captured
|
66
|
+
|
67
|
+
def success?
|
68
|
+
!failed?
|
69
|
+
end
|
70
|
+
|
71
|
+
def failed?
|
72
|
+
!!data['failed']
|
73
|
+
end
|
74
|
+
|
75
|
+
def error
|
76
|
+
data['error']
|
77
|
+
end
|
78
|
+
|
79
|
+
def temporary_error?
|
80
|
+
failed? && TEMPORARY_ERROR_CODES.include?(error)
|
81
|
+
end
|
82
|
+
|
83
|
+
def permanent_error?
|
84
|
+
failed? && !temporary_error?
|
85
|
+
end
|
86
|
+
|
87
|
+
def test?
|
88
|
+
data['mode'] == 'MODE_EPAY' || data['mode'] == 'MODE_TEST'
|
89
|
+
end
|
90
|
+
|
91
|
+
def production?
|
92
|
+
!test?
|
93
|
+
end
|
94
|
+
|
95
|
+
# Actions
|
96
|
+
def reload
|
97
|
+
response = Epay::Api.request(PAYMENT_SOAP_URL, 'gettransaction', :transactionid => id)
|
98
|
+
@data = response.data['transactionInformation']
|
99
|
+
self
|
100
|
+
end
|
101
|
+
|
102
|
+
def capture
|
103
|
+
response = Epay::Api.request(PAYMENT_SOAP_URL, 'capture', :transactionid => id, :amount => (amount * 100).to_i)
|
104
|
+
if response.success?
|
105
|
+
reload
|
106
|
+
true
|
107
|
+
else
|
108
|
+
false
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def credit(amount_to_be_credited = nil)
|
113
|
+
amount_to_be_credited ||= amount - credited_amount
|
114
|
+
|
115
|
+
Epay::Api.request(PAYMENT_SOAP_URL, 'credit', :transactionid => id, :amount => amount_to_be_credited * 100) do |response|
|
116
|
+
if response.success?
|
117
|
+
true
|
118
|
+
else
|
119
|
+
raise TransactionInGracePeriod if response.data['epayresponse'] == "-1021"
|
120
|
+
|
121
|
+
false
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def delete
|
127
|
+
Epay::Api.request(PAYMENT_SOAP_URL, 'delete', :transactionid => id).success?
|
128
|
+
end
|
129
|
+
|
130
|
+
# ClassMethods
|
131
|
+
class << self
|
132
|
+
def find(id)
|
133
|
+
transaction = new(id)
|
134
|
+
transaction.reload
|
135
|
+
end
|
136
|
+
|
137
|
+
# Epay::Transaction.create(:card_no => '1234...', :cvc => '123', :exp_month => '11', :exp_year => '12', :amount => '119', :order_no => 'ND-TEST')
|
138
|
+
|
139
|
+
def create(params)
|
140
|
+
post = Api.default_post_for_params(params).merge({
|
141
|
+
:orderid => params[:order_no],
|
142
|
+
:amount => params[:amount]*100,
|
143
|
+
:currency => Epay::CURRENCY_CODES[(params[:currency] || Epay.default_currency).to_sym],
|
144
|
+
:description => params[:description],
|
145
|
+
:cardholder => params[:cardholder],
|
146
|
+
:group => params[:group],
|
147
|
+
:instantcapture => params[:instant_capture] ? '1' : '0'
|
148
|
+
})
|
149
|
+
|
150
|
+
query = Api.authorize(post)
|
151
|
+
|
152
|
+
if query['accept']
|
153
|
+
# Find the transaction
|
154
|
+
transaction = Transaction.find(query["tid"].to_i)
|
155
|
+
else
|
156
|
+
# Return a new transaction with error code filled
|
157
|
+
new(nil, {
|
158
|
+
'failed' => true,
|
159
|
+
'error' => query["error"],
|
160
|
+
'orderid' => post[:orderid],
|
161
|
+
'authamount' => post[:amount],
|
162
|
+
'description' => post[:description],
|
163
|
+
'cardholder' => post[:cardholder],
|
164
|
+
'group' => post[:group],
|
165
|
+
'currency' => post[:currency]
|
166
|
+
})
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
data/lib/epay/version.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Epay
|
4
|
+
module Api
|
5
|
+
describe Response do
|
6
|
+
def soap_response(&block)
|
7
|
+
xml = Builder::XmlMarkup.new(:indent => 2)
|
8
|
+
xml.instruct!
|
9
|
+
xml.tag! 'soap:Envelope', { 'xmlns:xsi' => 'http://schemas.xmlsoap.org/soap/envelope/',
|
10
|
+
'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
|
11
|
+
'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' } do
|
12
|
+
|
13
|
+
xml.tag! 'soap:Body' do
|
14
|
+
xml.tag! 'actionResponse' do
|
15
|
+
yield xml
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
xml
|
21
|
+
end
|
22
|
+
|
23
|
+
let(:response) do
|
24
|
+
xml = soap_response do |x|
|
25
|
+
x.tag! 'actionResult', 'true'
|
26
|
+
end
|
27
|
+
|
28
|
+
raw_response = mock('response', :to_s => xml.target!, :code => 200, :headers => {:content_type => 'text/xml'})
|
29
|
+
Response.new(raw_response, 'action')
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#data" do
|
33
|
+
it "returns hash if successful response" do
|
34
|
+
response.data.should == {'actionResult' => 'true'}
|
35
|
+
end
|
36
|
+
|
37
|
+
it "returns string if content-type isn't xml" do
|
38
|
+
response.stub(:headers) { {:content_type => 'text/html' }}
|
39
|
+
response.data.should be_a String
|
40
|
+
end
|
41
|
+
|
42
|
+
it "returns string if status code isn't 200" do
|
43
|
+
response.stub(:code) { 404 }
|
44
|
+
response.data.should be_a String
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "#success?" do
|
49
|
+
it "is true if SOAP action was success" do
|
50
|
+
response.success?.should be_true
|
51
|
+
end
|
52
|
+
|
53
|
+
it "is false when raw_response status code isn't 200" do
|
54
|
+
response.raw_response.stub(:code) { 500 }
|
55
|
+
response.success?.should be_false
|
56
|
+
end
|
57
|
+
|
58
|
+
it "is false when soap response is false" do
|
59
|
+
xml = soap_response do |x|
|
60
|
+
x.tag! 'actionResult', 'false'
|
61
|
+
end
|
62
|
+
|
63
|
+
raw_response = mock('response', :to_s => xml.target!, :code => 200, :headers => {:content_type => 'text/xml'})
|
64
|
+
response = Response.new(raw_response, 'action')
|
65
|
+
|
66
|
+
response.success?.should be_false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/spec/api_spec.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
module Epay
|
5
|
+
describe Api do
|
6
|
+
describe ".authorize" do
|
7
|
+
it "hits the authorize endpoint" do
|
8
|
+
RestClient.should_receive(:post).with(Epay::AUTHORIZE_URL, anything)
|
9
|
+
Api.authorize(:amount => 10)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe ".request" do
|
14
|
+
before do
|
15
|
+
RestClient.stub(:post) { |url, body, headers| @url = url; @body = body; @headers = headers }
|
16
|
+
Api.request('https://ssl.ditonlinebetalingssystem.dk/remote/payment', 'gettransaction', :transactionid => 42)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "the request" do
|
20
|
+
it "has content-type xml" do
|
21
|
+
@headers['Content-Type'].should =~ /text\/xml/
|
22
|
+
end
|
23
|
+
|
24
|
+
it "has SOAPAction header" do
|
25
|
+
@headers['SOAPAction'].should == 'https://ssl.ditonlinebetalingssystem.dk/remote/payment/gettransaction'
|
26
|
+
end
|
27
|
+
|
28
|
+
context "body" do
|
29
|
+
def body_to_xml(body)
|
30
|
+
Nokogiri::XML(body).remove_namespaces!
|
31
|
+
end
|
32
|
+
|
33
|
+
it "has merchant number" do
|
34
|
+
body_to_xml(@body).xpath('//merchantnumber/child::text()').to_s.should == Epay.merchant_number.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
it "is adds parameters" do
|
38
|
+
xml = body_to_xml(@body)
|
39
|
+
xml.xpath('//Envelope/Body/gettransaction/transactionid/child::text()').to_s.should == "42"
|
40
|
+
end
|
41
|
+
|
42
|
+
context "if password is set" do
|
43
|
+
it "adds pwd field" do
|
44
|
+
Epay.password = 'password_for_api'
|
45
|
+
Api.request('https://ssl.ditonlinebetalingssystem.dk/remote/payment', 'gettransaction', :transactionid => 42)
|
46
|
+
body_to_xml(@body).xpath('//pwd/child::text()').to_s.should == 'password_for_api'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it "posts to endpoint" do
|
53
|
+
RestClient.should_receive(:post).with('https://ssl.ditonlinebetalingssystem.dk/remote/payment.asmx', anything, anything)
|
54
|
+
Api.request('https://ssl.ditonlinebetalingssystem.dk/remote/payment', 'gettransaction', :transactionid => 42)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/spec/card_spec.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Epay
|
4
|
+
describe Card do
|
5
|
+
let(:card) do
|
6
|
+
Card.new(:number => '333333XXXXXX3000', :exp_year => 15, :exp_month => 10, :kind => :visa)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "generates a hash" do
|
10
|
+
card.hash.should == '333333XXXXXX30001510'
|
11
|
+
end
|
12
|
+
|
13
|
+
it "has expires_at" do
|
14
|
+
card.expires_at.should == Date.new(2015, 10, 31)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "has last_digits" do
|
18
|
+
card.last_digits.should == '3000'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|