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 +4 -4
- data/Gemfile +0 -1
- data/NEWS +7 -0
- data/VERSION +1 -1
- data/db/ddl.sql +3 -3
- data/db/schema.rb +2 -2
- data/killbill-cybersource.gemspec +1 -1
- data/lib/cybersource/api.rb +28 -0
- data/lib/cybersource/cyber_source_on_demand.rb +46 -0
- data/lib/cybersource.rb +3 -0
- data/pom.xml +1 -1
- data/spec/cybersource/remote/integration_spec.rb +88 -30
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1afabdf22905f7fad77d239026e4a3182831b7d5
|
4
|
+
data.tar.gz: eee1798b1e2bf264a2bd96023076755f24aa8929
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ecc764a9caa40931a26495c1d4122b71c80a0467ae97c42ab25b3d83b4b04427409792e63d6c2e0148ca3e042b7d6dffc19113eaeff9aefc928b315b13ff2a63
|
7
|
+
data.tar.gz: 7a251ccb11d31b1a127ec6de804a8c4c22b672c23b9a09cb358ea5c4200ad2c64bcb477b947d565402e869ee24dcd8a99ddfe878cbc95d8ccdf825ee6ab0d576
|
data/Gemfile
CHANGED
data/NEWS
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
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)
|
41
|
-
`amount_in_cents` int(11)
|
42
|
-
`currency` varchar(255)
|
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"
|
43
|
-
t.string "currency"
|
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.
|
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'
|
data/lib/cybersource/api.rb
CHANGED
@@ -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.
|
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
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
36
|
-
|
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
|
-
|
43
|
-
amount
|
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
|
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
|
-
|
62
|
-
amount
|
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(
|
73
|
-
1.upto(3) do
|
74
|
-
payment_response = @plugin.capture_payment
|
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
|
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
|
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.
|
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-
|
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.
|
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.
|
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
|