epay 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/.gitignore +15 -0
  2. data/.travis.yml +5 -0
  3. data/CHANGELOG.md +13 -0
  4. data/Gemfile +2 -0
  5. data/Guardfile +10 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.markdown +21 -0
  8. data/Rakefile +8 -0
  9. data/bin/epay-console +8 -0
  10. data/epay.gemspec +55 -0
  11. data/lib/epay.rb +104 -0
  12. data/lib/epay/api.rb +92 -0
  13. data/lib/epay/api/response.rb +29 -0
  14. data/lib/epay/card.rb +23 -0
  15. data/lib/epay/model.rb +23 -0
  16. data/lib/epay/subscription.rb +132 -0
  17. data/lib/epay/transaction.rb +171 -0
  18. data/lib/epay/version.rb +3 -0
  19. data/lib/extensions/hash.rb +4 -0
  20. data/spec/api/response_spec.rb +71 -0
  21. data/spec/api_spec.rb +58 -0
  22. data/spec/card_spec.rb +21 -0
  23. data/spec/fixtures/vcr_cassettes/existing_subscription.yml +75 -0
  24. data/spec/fixtures/vcr_cassettes/existing_transaction.yml +47 -0
  25. data/spec/fixtures/vcr_cassettes/non_existing_transaction.yml +48 -0
  26. data/spec/fixtures/vcr_cassettes/subscription_authorization.yml +95 -0
  27. data/spec/fixtures/vcr_cassettes/subscription_creation.yml +101 -0
  28. data/spec/fixtures/vcr_cassettes/subscription_invalid_creation.yml +47 -0
  29. data/spec/fixtures/vcr_cassettes/subscriptions.yml +87 -0
  30. data/spec/fixtures/vcr_cassettes/transaction_creation.yml +90 -0
  31. data/spec/fixtures/vcr_cassettes/transaction_invalid_creation.yml +47 -0
  32. data/spec/fixtures/vcr_cassettes/transactions.yml +50 -0
  33. data/spec/helpers/http_responses.rb +13 -0
  34. data/spec/model_spec.rb +36 -0
  35. data/spec/spec_helper.rb +20 -0
  36. data/spec/subscription_spec.rb +139 -0
  37. data/spec/transaction_spec.rb +245 -0
  38. 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
@@ -0,0 +1,3 @@
1
+ module Epay
2
+ VERSION = "0.0.4"
3
+ end
@@ -0,0 +1,4 @@
1
+ class Hash
2
+ # Alias for 1.8.7
3
+ alias_method :key, :index unless method_defined?(:key)
4
+ end
@@ -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
@@ -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
@@ -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