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.
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