killbill-cybersource 0.0.2 → 0.0.4

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: cb1cd0b317eb7b2b5338490d98587da7856373e0
4
- data.tar.gz: 6c4564dafa4d2b35963a464e285819c99b41faa0
3
+ metadata.gz: 1afabdf22905f7fad77d239026e4a3182831b7d5
4
+ data.tar.gz: eee1798b1e2bf264a2bd96023076755f24aa8929
5
5
  SHA512:
6
- metadata.gz: 2bb15e9bd4cf28cd9417a3f8e8d73c616a06cab43265c14b0fabfe6c0e8b1d6839e6f32b52925a1dcd1ebd8f03d978490b0c1991ce966a14796e6deb20d8a179
7
- data.tar.gz: 8db2e3193a6a4db09192016599939b35c75d8fee670c2fe210e226b8da923491381da00fe6f5f1435284520249a6ca6363742bb61a85b1b46da26b1a209cab5c
6
+ metadata.gz: ecc764a9caa40931a26495c1d4122b71c80a0467ae97c42ab25b3d83b4b04427409792e63d6c2e0148ca3e042b7d6dffc19113eaeff9aefc928b315b13ff2a63
7
+ data.tar.gz: 7a251ccb11d31b1a127ec6de804a8c4c22b672c23b9a09cb358ea5c4200ad2c64bcb477b947d565402e869ee24dcd8a99ddfe878cbc95d8ccdf825ee6ab0d576
data/Gemfile CHANGED
@@ -3,4 +3,3 @@ source 'http://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  #gem 'killbill', :path => '../killbill-plugin-framework-ruby'
6
-
data/NEWS CHANGED
@@ -1,3 +1,10 @@
1
+ 0.0.4
2
+ Update killbill gem to 3.1.11
3
+
4
+ 0.0.3
5
+ DDL: remove constraints in cybersource_transactions
6
+ Make payment calls idempotent using the CyberSource OnDemand API
7
+
1
8
  0.0.2
2
9
  Upgrade killbill framework for connection leaks
3
10
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.0.4
data/db/ddl.sql CHANGED
@@ -37,9 +37,9 @@ CREATE TABLE `cybersource_transactions` (
37
37
  `kb_payment_id` varchar(255) NOT NULL,
38
38
  `kb_payment_transaction_id` varchar(255) NOT NULL,
39
39
  `transaction_type` varchar(255) NOT NULL,
40
- `txn_id` varchar(255) NOT NULL,
41
- `amount_in_cents` int(11) NOT NULL,
42
- `currency` varchar(255) NOT NULL,
40
+ `txn_id` varchar(255),
41
+ `amount_in_cents` int(11),
42
+ `currency` varchar(255),
43
43
  `created_at` datetime NOT NULL,
44
44
  `updated_at` datetime NOT NULL,
45
45
  `kb_account_id` varchar(255) NOT NULL,
data/db/schema.rb CHANGED
@@ -39,8 +39,8 @@ ActiveRecord::Schema.define(:version => 20140410153635) do
39
39
  t.string "kb_payment_transaction_id", :null => false
40
40
  t.string "transaction_type", :null => false
41
41
  t.string "txn_id" # cybersource transaction id
42
- t.integer "amount_in_cents", :null => false
43
- t.string "currency", :null => false
42
+ t.integer "amount_in_cents"
43
+ t.string "currency"
44
44
  t.datetime "created_at", :null => false
45
45
  t.datetime "updated_at", :null => false
46
46
  t.string "kb_account_id", :null => false
@@ -22,7 +22,7 @@ Gem::Specification.new do |s|
22
22
 
23
23
  s.rdoc_options << '--exclude' << '.'
24
24
 
25
- s.add_dependency 'killbill', '~> 3.1.9'
25
+ s.add_dependency 'killbill', '~> 3.1.11'
26
26
  s.add_dependency 'activemerchant', '~> 1.42.9'
27
27
  s.add_dependency 'activerecord', '~> 4.1.0'
28
28
  s.add_dependency 'actionpack', '~> 4.1.0'
@@ -14,6 +14,14 @@ module Killbill #:nodoc:
14
14
  ::Killbill::Cybersource::CybersourceResponse)
15
15
  end
16
16
 
17
+ def start_plugin
18
+ super
19
+ gateway = lookup_gateway(:on_demand)
20
+ @report_api = CyberSourceOnDemand.new(gateway, logger)
21
+ rescue => e
22
+ @report_api = nil
23
+ end
24
+
17
25
  def authorize_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
18
26
  # Pass extra parameters for the gateway here
19
27
  options = {}
@@ -153,6 +161,26 @@ module Killbill #:nodoc:
153
161
  # gw_notification.entity =
154
162
  end
155
163
  end
164
+
165
+ # Make calls idempotent
166
+ def before_gateway(gateway, kb_transaction, last_transaction, payment_source, amount_in_cents, currency, options)
167
+ super
168
+ return if @report_api.nil?
169
+
170
+ merchant_reference_code = options[:order_id]
171
+ report = @report_api.single_transaction_report(merchant_reference_code, kb_transaction.created_date.strftime('%Y%m%d'))
172
+
173
+ if !report.nil? &&
174
+ !report['Report'].nil? &&
175
+ !report['Report']['Requests'].nil? &&
176
+ !report['Report']['Requests']['Request'].nil? &&
177
+ report['Report']['Requests']['Request']['MerchantReferenceNumber'] == merchant_reference_code
178
+ logger.info "Skipping gateway call for existing transaction #{kb_transaction.id}, merchant reference code #{merchant_reference_code}"
179
+ options[:skip_gw] = true
180
+ end
181
+ rescue => e
182
+ logger.warn "Error checking for duplicate payment: #{e.message}"
183
+ end
156
184
  end
157
185
  end
158
186
  end
@@ -0,0 +1,46 @@
1
+ module Killbill #:nodoc:
2
+ module Cybersource #:nodoc:
3
+ class CyberSourceOnDemand
4
+
5
+ @@live_url = 'https://ebc.cybersource.com/ebc/Query'
6
+ @@test_url = 'https://ebctest.cybersource.com/ebctest/Query'
7
+
8
+ def initialize(gateway, logger)
9
+ @gateway = gateway
10
+ @logger = logger
11
+ end
12
+
13
+ def single_transaction_report(merchant_reference_code, target_date)
14
+ params = {
15
+ :merchantID => @gateway.config[:merchantID],
16
+ :merchantReferenceNumber => merchant_reference_code,
17
+ :targetDate => target_date,
18
+ :type => 'transaction',
19
+ :subtype => 'transactionDetail',
20
+ :versionNumber => '1.7',
21
+ }
22
+
23
+ headers = {
24
+ # Don't use symbols or it will confuse Net/HTTP
25
+ 'Authorization' => 'Basic ' + Base64.encode64("#{@gateway.config[:username]}:#{@gateway.config[:password]}").chomp
26
+ }
27
+
28
+ data = URI.encode_www_form(params)
29
+ endpoint = @gateway.test? ? @@test_url : @@live_url
30
+
31
+ # Will raise ResponseError if the response code is > 300
32
+ parse(@gateway.ssl_post(endpoint, data, headers))
33
+ end
34
+
35
+ private
36
+
37
+ def parse(body)
38
+ # Thanks ActiveSupport!
39
+ Hash.from_xml(body)
40
+ rescue # Parser error - request failed
41
+ @logger.warn "Error checking for duplicate payment, CyberSource response: #{body}"
42
+ nil
43
+ end
44
+ end
45
+ end
46
+ end
data/lib/cybersource.rb CHANGED
@@ -3,17 +3,20 @@ require 'active_record'
3
3
  require 'action_view'
4
4
  require 'active_merchant'
5
5
  require 'active_support'
6
+ require 'base64'
6
7
  require 'bigdecimal'
7
8
  require 'money'
8
9
  require 'monetize'
9
10
  require 'pathname'
10
11
  require 'sinatra'
11
12
  require 'singleton'
13
+ require 'uri'
12
14
  require 'yaml'
13
15
 
14
16
  require 'killbill'
15
17
  require 'killbill/helpers/active_merchant'
16
18
 
19
+ require 'cybersource/cyber_source_on_demand'
17
20
  require 'cybersource/api'
18
21
  require 'cybersource/private_api'
19
22
 
data/pom.xml CHANGED
@@ -25,7 +25,7 @@
25
25
  <groupId>org.kill-bill.billing.plugin.ruby</groupId>
26
26
  <artifactId>cybersource-plugin</artifactId>
27
27
  <packaging>pom</packaging>
28
- <version>0.0.2</version>
28
+ <version>0.0.4</version>
29
29
  <name>cybersource-plugin</name>
30
30
  <url>http://github.com/killbill/killbill-cybersource-plugin</url>
31
31
  <description>Plugin for accessing Cybersource as a payment gateway</description>
@@ -10,10 +10,11 @@ describe Killbill::Cybersource::PaymentPlugin do
10
10
  @plugin = Killbill::Cybersource::PaymentPlugin.new
11
11
 
12
12
  @account_api = ::Killbill::Plugin::ActiveMerchant::RSpec::FakeJavaUserAccountApi.new
13
- svcs = {:account_user_api => @account_api}
13
+ @payment_api = ::Killbill::Plugin::ActiveMerchant::RSpec::FakeJavaPaymentApi.new
14
+ svcs = {:account_user_api => @account_api, :payment_api => @payment_api}
14
15
  @plugin.kb_apis = Killbill::Plugin::KillbillApi.new('cybersource', svcs)
15
16
 
16
- @call_context = Killbill::Plugin::Model::CallContext.new
17
+ @call_context = ::Killbill::Plugin::Model::CallContext.new
17
18
  @call_context.tenant_id = '00000011-0022-0033-0044-000000000055'
18
19
  @call_context = @call_context.to_ruby(@call_context)
19
20
 
@@ -21,72 +22,129 @@ describe Killbill::Cybersource::PaymentPlugin do
21
22
  @plugin.logger.level = Logger::INFO
22
23
  @plugin.conf_dir = File.expand_path(File.dirname(__FILE__) + '../../../../')
23
24
  @plugin.start_plugin
25
+
26
+ @pm = create_payment_method(::Killbill::Cybersource::CybersourcePaymentMethod, nil, @call_context.tenant_id)
27
+ @amount = BigDecimal.new('100')
28
+ @currency = 'USD'
29
+ @properties = []
30
+
31
+ kb_payment_id = SecureRandom.uuid
32
+ 1.upto(6) do
33
+ @kb_payment = @payment_api.add_payment(kb_payment_id)
34
+ end
24
35
  end
25
36
 
26
37
  after(:each) do
27
38
  @plugin.stop_plugin
28
39
  end
29
40
 
30
- it 'should be able to charge a Credit Card directly' do
31
- properties = build_pm_properties
32
- amount = BigDecimal.new("100")
33
- currency = 'USD'
41
+ it 'should be able to charge a Credit Card directly and calls should be idempotent' do
42
+ # We created the payment method, hence the rows
43
+ Killbill::Cybersource::CybersourceResponse.all.size.should == 1
44
+ Killbill::Cybersource::CybersourceTransaction.all.size.should == 0
45
+
46
+ payment_response = @plugin.purchase_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
47
+ payment_response.amount.should == @amount
48
+ payment_response.status.should == :PROCESSED
49
+ payment_response.transaction_type.should == :PURCHASE
34
50
 
35
- payment_response = @plugin.purchase_payment SecureRandom.uuid, SecureRandom.uuid, SecureRandom.uuid, nil, amount, currency, properties, @call_context
36
- payment_response.amount.should == amount
51
+ responses = Killbill::Cybersource::CybersourceResponse.all
52
+ responses.size.should == 2
53
+ responses[0].api_call.should == 'add_payment_method'
54
+ responses[0].message.should == 'Successful transaction'
55
+ responses[1].api_call.should == 'purchase'
56
+ responses[1].message.should == 'Successful transaction'
57
+ transactions = Killbill::Cybersource::CybersourceTransaction.all
58
+ transactions.size.should == 1
59
+ transactions[0].api_call.should == 'purchase'
60
+
61
+ payment_response = @plugin.purchase_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
62
+ payment_response.amount.should == @amount
37
63
  payment_response.status.should == :PROCESSED
38
64
  payment_response.transaction_type.should == :PURCHASE
65
+
66
+ responses = Killbill::Cybersource::CybersourceResponse.all
67
+ responses.size.should == 3
68
+ responses[0].api_call.should == 'add_payment_method'
69
+ responses[0].message.should == 'Successful transaction'
70
+ responses[1].api_call.should == 'purchase'
71
+ responses[1].message.should == 'Successful transaction'
72
+ responses[2].api_call.should == 'purchase'
73
+ responses[2].message.should == 'Skipped Gateway call'
74
+ transactions = Killbill::Cybersource::CybersourceTransaction.all
75
+ transactions.size.should == 2
76
+ transactions[0].api_call.should == 'purchase'
77
+ transactions[0].txn_id.should_not be_nil
78
+ transactions[1].api_call.should == 'purchase'
79
+ transactions[1].txn_id.should be_nil
39
80
  end
40
81
 
41
82
  it 'should be able to charge and refund' do
42
- pm = create_payment_method(Killbill::Cybersource::CybersourcePaymentMethod, nil, @call_context.tenant_id)
43
- amount = BigDecimal.new("100")
44
- currency = 'USD'
45
- kb_payment_id = SecureRandom.uuid
46
- kb_payment_transaction_id = SecureRandom.uuid
47
-
48
- payment_response = @plugin.purchase_payment pm.kb_account_id, kb_payment_id, kb_payment_transaction_id, pm.kb_payment_method_id, amount, currency, [], @call_context
49
- payment_response.amount.should == amount
83
+ payment_response = @plugin.purchase_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
84
+ payment_response.amount.should == @amount
50
85
  payment_response.status.should == :PROCESSED
51
86
  payment_response.transaction_type.should == :PURCHASE
52
87
 
53
88
  # Try a full refund
54
- refund_response = @plugin.refund_payment pm.kb_account_id, kb_payment_id, kb_payment_transaction_id, pm.kb_payment_method_id, amount, currency, [], @call_context
55
- refund_response.amount.should == amount
89
+ refund_response = @plugin.refund_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[1].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
90
+ refund_response.amount.should == @amount
56
91
  refund_response.status.should == :PROCESSED
57
92
  refund_response.transaction_type.should == :REFUND
58
93
  end
59
94
 
60
95
  it 'should be able to auth, capture and refund' do
61
- pm = create_payment_method(Killbill::Cybersource::CybersourcePaymentMethod, nil, @call_context.tenant_id)
62
- amount = BigDecimal.new("100")
63
- currency = 'USD'
64
- kb_payment_id = SecureRandom.uuid
65
-
66
- payment_response = @plugin.authorize_payment pm.kb_account_id, kb_payment_id, SecureRandom.uuid, pm.kb_payment_method_id, amount, currency, [], @call_context
67
- payment_response.amount.should == amount
96
+ payment_response = @plugin.authorize_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
97
+ payment_response.amount.should == @amount
68
98
  payment_response.status.should == :PROCESSED
69
99
  payment_response.transaction_type.should == :AUTHORIZE
70
100
 
71
101
  # Try multiple partial captures
72
- partial_capture_amount = BigDecimal.new("10")
73
- 1.upto(3) do
74
- payment_response = @plugin.capture_payment pm.kb_account_id, kb_payment_id, SecureRandom.uuid, pm.kb_payment_method_id, partial_capture_amount, currency, [], @call_context
102
+ partial_capture_amount = BigDecimal.new('10')
103
+ 1.upto(3) do |i|
104
+ payment_response = @plugin.capture_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[i].id, @pm.kb_payment_method_id, partial_capture_amount, @currency, @properties, @call_context)
75
105
  payment_response.amount.should == partial_capture_amount
76
106
  payment_response.status.should == :PROCESSED
77
107
  payment_response.transaction_type.should == :CAPTURE
78
108
  end
79
109
 
80
110
  # Try a partial refund
81
- refund_response = @plugin.refund_payment pm.kb_account_id, kb_payment_id, SecureRandom.uuid, pm.kb_payment_method_id, partial_capture_amount, currency, [], @call_context
111
+ refund_response = @plugin.refund_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[4].id, @pm.kb_payment_method_id, partial_capture_amount, @currency, @properties, @call_context)
82
112
  refund_response.amount.should == partial_capture_amount
83
113
  refund_response.status.should == :PROCESSED
84
114
  refund_response.transaction_type.should == :REFUND
85
115
 
86
116
  # Try to capture again
87
- payment_response = @plugin.capture_payment pm.kb_account_id, kb_payment_id, SecureRandom.uuid, pm.kb_payment_method_id, partial_capture_amount, currency, [], @call_context
117
+ payment_response = @plugin.capture_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[5].id, @pm.kb_payment_method_id, partial_capture_amount, @currency, @properties, @call_context)
118
+ payment_response.amount.should == partial_capture_amount
119
+ payment_response.status.should == :PROCESSED
120
+ payment_response.transaction_type.should == :CAPTURE
121
+ end
122
+
123
+ it 'should be able to auth and void' do
124
+ payment_response = @plugin.authorize_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
125
+ payment_response.amount.should == @amount
126
+ payment_response.status.should == :PROCESSED
127
+ payment_response.transaction_type.should == :AUTHORIZE
128
+
129
+ payment_response = @plugin.void_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[1].id, @pm.kb_payment_method_id, @properties, @call_context)
130
+ payment_response.status.should == :PROCESSED
131
+ payment_response.transaction_type.should == :VOID
132
+ end
133
+
134
+ it 'should be able to auth, partial capture and void' do
135
+ payment_response = @plugin.authorize_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
136
+ payment_response.amount.should == @amount
137
+ payment_response.status.should == :PROCESSED
138
+ payment_response.transaction_type.should == :AUTHORIZE
139
+
140
+ partial_capture_amount = BigDecimal.new('10')
141
+ payment_response = @plugin.capture_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[1].id, @pm.kb_payment_method_id, partial_capture_amount, @currency, @properties, @call_context)
88
142
  payment_response.amount.should == partial_capture_amount
89
143
  payment_response.status.should == :PROCESSED
90
144
  payment_response.transaction_type.should == :CAPTURE
145
+
146
+ payment_response = @plugin.void_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[2].id, @pm.kb_payment_method_id, @properties, @call_context)
147
+ payment_response.status.should == :PROCESSED
148
+ payment_response.transaction_type.should == :VOID
91
149
  end
92
150
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: killbill-cybersource
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kill Bill core team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-16 00:00:00.000000000 Z
11
+ date: 2014-10-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: killbill
@@ -16,12 +16,12 @@ dependencies:
16
16
  requirements:
17
17
  - - ~>
18
18
  - !ruby/object:Gem::Version
19
- version: 3.1.9
19
+ version: 3.1.11
20
20
  requirement: !ruby/object:Gem::Requirement
21
21
  requirements:
22
22
  - - ~>
23
23
  - !ruby/object:Gem::Version
24
- version: 3.1.9
24
+ version: 3.1.11
25
25
  prerelease: false
26
26
  type: :runtime
27
27
  - !ruby/object:Gem::Dependency
@@ -244,6 +244,7 @@ files:
244
244
  - lib/cybersource.rb
245
245
  - lib/cybersource/api.rb
246
246
  - lib/cybersource/application.rb
247
+ - lib/cybersource/cyber_source_on_demand.rb
247
248
  - lib/cybersource/models/payment_method.rb
248
249
  - lib/cybersource/models/response.rb
249
250
  - lib/cybersource/models/transaction.rb