killbill-cybersource 0.0.2 → 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.
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