killbill-paypal-express 4.0.0 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +28 -19
- data/Jarfile.lock +48 -48
- data/NEWS +3 -0
- data/README.md +123 -0
- data/VERSION +1 -1
- data/db/ddl.sql +1 -1
- data/db/schema.rb +2 -2
- data/killbill-paypal-express.gemspec +3 -3
- data/lib/paypal_express.rb +3 -0
- data/lib/paypal_express/api.rb +177 -49
- data/lib/paypal_express/application.rb +1 -0
- data/lib/paypal_express/ext/active_merchant/active_merchant.rb +18 -0
- data/lib/paypal_express/models/response.rb +8 -0
- data/lib/paypal_express/private_api.rb +9 -9
- data/pom.xml +1 -1
- data/release.sh +0 -0
- data/spec/paypal_express/base_plugin_spec.rb +0 -17
- data/spec/paypal_express/remote/baid_spec.rb +287 -0
- data/spec/paypal_express/remote/hpp_spec.rb +256 -0
- data/spec/spec_helper.rb +20 -0
- metadata +27 -5
- data/spec/paypal_express/remote/integration_spec.rb +0 -135
data/lib/paypal_express/api.rb
CHANGED
@@ -16,6 +16,7 @@ module Killbill #:nodoc:
|
|
16
16
|
::Killbill::PaypalExpress::PaypalExpressResponse)
|
17
17
|
|
18
18
|
@ip = ::Killbill::Plugin::ActiveMerchant::Utils.ip
|
19
|
+
@private_api = ::Killbill::PaypalExpress::PrivatePaymentPlugin.new
|
19
20
|
end
|
20
21
|
|
21
22
|
def on_event(event)
|
@@ -56,20 +57,73 @@ module Killbill #:nodoc:
|
|
56
57
|
end
|
57
58
|
|
58
59
|
def purchase_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
60
|
+
payment_processor_account_id = find_value_from_properties(properties, 'payment_processor_account_id')
|
61
|
+
|
62
|
+
# Callback from the plugin itself (HPP flow)
|
63
|
+
if find_value_from_properties(properties, 'from_hpp') == 'true'
|
64
|
+
token = find_value_from_properties(properties, 'token')
|
65
|
+
|
66
|
+
response = @response_model.create(:api_call => :build_form_descriptor,
|
67
|
+
:kb_account_id => kb_account_id,
|
68
|
+
:kb_payment_id => kb_payment_id,
|
69
|
+
:kb_payment_transaction_id => kb_payment_transaction_id,
|
70
|
+
:transaction_type => :PURCHASE,
|
71
|
+
:authorization => token,
|
72
|
+
:payment_processor_account_id => payment_processor_account_id,
|
73
|
+
:kb_tenant_id => context.tenant_id,
|
74
|
+
:success => true,
|
75
|
+
:created_at => Time.now.utc,
|
76
|
+
:updated_at => Time.now.utc,
|
77
|
+
:message => { :payment_plugin_status => :PENDING }.to_json)
|
78
|
+
transaction = response.to_transaction_info_plugin(nil)
|
79
|
+
transaction.amount = amount
|
80
|
+
transaction.currency = currency
|
81
|
+
transaction
|
82
|
+
else
|
83
|
+
options = {}
|
84
|
+
add_required_options(kb_payment_transaction_id, kb_payment_method_id, context, options)
|
85
|
+
|
86
|
+
# We have a baid on file
|
87
|
+
if options[:token]
|
88
|
+
gateway_call_proc = Proc.new do |gateway, linked_transaction, payment_source, amount_in_cents, options|
|
89
|
+
# Can't use default implementation: the purchase signature is for one-off payments only
|
90
|
+
gateway.reference_transaction(amount_in_cents, options)
|
91
|
+
end
|
92
|
+
else
|
93
|
+
# One-off payment
|
94
|
+
options[:token] = find_value_from_properties(properties, 'token') || find_last_token(kb_account_id, context.tenant_id)
|
95
|
+
gateway_call_proc = Proc.new do |gateway, linked_transaction, payment_source, amount_in_cents, options|
|
96
|
+
gateway.purchase(amount_in_cents, options)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Populate the Payer id if missing
|
101
|
+
options[:payer_id] = find_value_from_properties(properties, 'payer_id')
|
102
|
+
begin
|
103
|
+
options[:payer_id] ||= find_payer_id(options[:token],
|
104
|
+
kb_account_id,
|
105
|
+
context.tenant_id,
|
106
|
+
properties_to_hash(properties))
|
107
|
+
rescue => e
|
108
|
+
# Maybe invalid token?
|
109
|
+
response = @response_model.create(:api_call => :purchase,
|
110
|
+
:kb_account_id => kb_account_id,
|
111
|
+
:kb_payment_id => kb_payment_id,
|
112
|
+
:kb_payment_transaction_id => kb_payment_transaction_id,
|
113
|
+
:transaction_type => :PURCHASE,
|
114
|
+
:authorization => nil,
|
115
|
+
:payment_processor_account_id => payment_processor_account_id,
|
116
|
+
:kb_tenant_id => context.tenant_id,
|
117
|
+
:success => false,
|
118
|
+
:created_at => Time.now.utc,
|
119
|
+
:updated_at => Time.now.utc,
|
120
|
+
:message => { :payment_plugin_status => :CANCELED, :exception_class => e.class.to_s, :exception_message => e.message }.to_json)
|
121
|
+
return response.to_transaction_info_plugin(nil)
|
122
|
+
end
|
123
|
+
|
124
|
+
properties = merge_properties(properties, options)
|
125
|
+
dispatch_to_gateways(:purchase, kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context, gateway_call_proc)
|
70
126
|
end
|
71
|
-
|
72
|
-
dispatch_to_gateways(:purchase, kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context, gateway_call_proc)
|
73
127
|
end
|
74
128
|
|
75
129
|
def void_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, properties, context)
|
@@ -105,11 +159,14 @@ module Killbill #:nodoc:
|
|
105
159
|
end
|
106
160
|
|
107
161
|
def get_payment_info(kb_account_id, kb_payment_id, properties, context)
|
108
|
-
|
109
|
-
options = {}
|
162
|
+
t_info_plugins = super(kb_account_id, kb_payment_id, properties, context)
|
110
163
|
|
111
|
-
|
112
|
-
|
164
|
+
# Completed purchases will have two rows in the responses table (one for api_call 'build_form_descriptor', one for api_call 'purchase')
|
165
|
+
# Other transaction types don't support the :PENDING state
|
166
|
+
is_purchase_pending = t_info_plugins.find { |t_info_plugin| t_info_plugin.transaction_type == :PURCHASE && t_info_plugin.status != :PENDING }.nil?
|
167
|
+
t_info_plugins_without_purchase_pending = t_info_plugins.reject { |t_info_plugin| t_info_plugin.transaction_type == :PURCHASE && t_info_plugin.status == :PENDING }
|
168
|
+
|
169
|
+
is_purchase_pending ? t_info_plugins: t_info_plugins_without_purchase_pending
|
113
170
|
end
|
114
171
|
|
115
172
|
def search_payments(search_key, offset, limit, properties, context)
|
@@ -121,25 +178,22 @@ module Killbill #:nodoc:
|
|
121
178
|
end
|
122
179
|
|
123
180
|
def add_payment_method(kb_account_id, kb_payment_method_id, payment_method_props, set_default, properties, context)
|
124
|
-
|
125
|
-
token = find_value_from_properties(
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
:paypal_express_token => token,
|
141
|
-
:paypal_express_payer_id => response.payer_id
|
142
|
-
}
|
181
|
+
all_properties = (payment_method_props.nil? || payment_method_props.properties.nil? ? [] : payment_method_props.properties) + properties
|
182
|
+
token = find_value_from_properties(all_properties, 'token')
|
183
|
+
|
184
|
+
if token.nil?
|
185
|
+
# HPP flow
|
186
|
+
options = {
|
187
|
+
:skip_gw => true
|
188
|
+
}
|
189
|
+
else
|
190
|
+
# Go to Paypal to get the Payer id (GetExpressCheckoutDetails call)
|
191
|
+
payer_id = find_payer_id(token, kb_account_id, context.tenant_id, properties_to_hash(properties))
|
192
|
+
options = {
|
193
|
+
:paypal_express_token => token,
|
194
|
+
:paypal_express_payer_id => payer_id
|
195
|
+
}
|
196
|
+
end
|
143
197
|
|
144
198
|
properties = merge_properties(properties, options)
|
145
199
|
super(kb_account_id, kb_payment_method_id, payment_method_props, set_default, properties, context)
|
@@ -186,17 +240,49 @@ module Killbill #:nodoc:
|
|
186
240
|
end
|
187
241
|
|
188
242
|
def build_form_descriptor(kb_account_id, descriptor_fields, properties, context)
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
243
|
+
jcontext = @kb_apis.create_context(context.tenant_id)
|
244
|
+
|
245
|
+
all_properties = descriptor_fields + properties
|
246
|
+
options = properties_to_hash(all_properties)
|
247
|
+
|
248
|
+
kb_account = ::Killbill::Plugin::ActiveMerchant::Utils::LazyEvaluator.new { @kb_apis.account_user_api.get_account_by_id(kb_account_id, jcontext) }
|
249
|
+
amount = (options[:amount] || '0').to_f
|
250
|
+
currency = options[:currency] || kb_account.currency
|
251
|
+
|
252
|
+
response = initiate_express_checkout(kb_account_id, amount, currency, all_properties, context)
|
253
|
+
|
254
|
+
descriptor = super(kb_account_id, descriptor_fields, properties, context)
|
255
|
+
descriptor.form_url = @private_api.to_express_checkout_url(response, context.tenant_id, options)
|
256
|
+
descriptor.form_method = 'GET'
|
257
|
+
descriptor.properties << build_property('token', response.token)
|
258
|
+
|
259
|
+
# By default, pending payments are not created for HPP
|
260
|
+
create_pending_payment = ::Killbill::Plugin::ActiveMerchant::Utils.normalized(options, :create_pending_payment)
|
261
|
+
if create_pending_payment
|
262
|
+
custom_props = hash_to_properties(:from_hpp => true,
|
263
|
+
:token => response.token)
|
264
|
+
payment_external_key = ::Killbill::Plugin::ActiveMerchant::Utils.normalized(options, :payment_external_key)
|
265
|
+
transaction_external_key = ::Killbill::Plugin::ActiveMerchant::Utils.normalized(options, :transaction_external_key)
|
266
|
+
|
267
|
+
kb_payment_method = (@kb_apis.payment_api.get_account_payment_methods(kb_account_id, false, [], jcontext).find { |pm| pm.plugin_name == 'killbill-paypal-express' })
|
268
|
+
payment = @kb_apis.payment_api
|
269
|
+
.create_purchase(kb_account.send(:__instance_object__),
|
270
|
+
kb_payment_method.id,
|
271
|
+
nil,
|
272
|
+
amount,
|
273
|
+
currency,
|
274
|
+
payment_external_key,
|
275
|
+
transaction_external_key,
|
276
|
+
custom_props,
|
277
|
+
jcontext)
|
278
|
+
|
279
|
+
descriptor.properties << build_property('kb_payment_id', payment.id)
|
280
|
+
descriptor.properties << build_property('kb_payment_external_key', payment.external_key)
|
281
|
+
descriptor.properties << build_property('kb_transaction_id', payment.transactions.first.id)
|
282
|
+
descriptor.properties << build_property('kb_transaction_external_key', payment.transactions.first.external_key)
|
283
|
+
end
|
198
284
|
|
199
|
-
|
285
|
+
descriptor
|
200
286
|
end
|
201
287
|
|
202
288
|
def process_notification(notification, properties, context)
|
@@ -227,17 +313,59 @@ module Killbill #:nodoc:
|
|
227
313
|
|
228
314
|
private
|
229
315
|
|
316
|
+
def find_last_token(kb_account_id, kb_tenant_id)
|
317
|
+
@response_model.last_token(kb_account_id, kb_tenant_id)
|
318
|
+
end
|
319
|
+
|
320
|
+
def find_payer_id(token, kb_account_id, kb_tenant_id, options = {})
|
321
|
+
raise 'Could not find the payer_id: the token is missing' if token.blank?
|
322
|
+
|
323
|
+
# Go to Paypal to get the Payer id (GetExpressCheckoutDetails call)
|
324
|
+
payment_processor_account_id = options[:payment_processor_account_id] || :default
|
325
|
+
gateway = lookup_gateway(payment_processor_account_id, kb_tenant_id)
|
326
|
+
gw_response = gateway.details_for(token)
|
327
|
+
response, transaction = save_response_and_transaction(gw_response, :details_for, kb_account_id, kb_tenant_id, payment_processor_account_id)
|
328
|
+
|
329
|
+
raise response.message unless response.success?
|
330
|
+
raise "Could not find the payer_id for token #{token}" if response.payer_id.blank?
|
331
|
+
|
332
|
+
response.payer_id
|
333
|
+
end
|
334
|
+
|
230
335
|
def add_required_options(kb_payment_transaction_id, kb_payment_method_id, context, options)
|
231
336
|
payment_method = @payment_method_model.from_kb_payment_method_id(kb_payment_method_id, context.tenant_id)
|
232
337
|
|
233
|
-
options[:payer_id] ||= payment_method.paypal_express_payer_id
|
234
|
-
options[:token] ||= payment_method.paypal_express_token
|
235
|
-
options[:reference_id] ||= payment_method.token # baid
|
338
|
+
options[:payer_id] ||= payment_method.paypal_express_payer_id.presence
|
339
|
+
options[:token] ||= payment_method.paypal_express_token.presence
|
340
|
+
options[:reference_id] ||= payment_method.token.presence # baid
|
236
341
|
|
237
342
|
options[:payment_type] ||= 'Any'
|
238
343
|
options[:invoice_id] ||= kb_payment_transaction_id
|
239
344
|
options[:ip] ||= @ip
|
240
345
|
end
|
346
|
+
|
347
|
+
def initiate_express_checkout(kb_account_id, amount, currency, properties, context)
|
348
|
+
properties_hash = properties_to_hash(properties)
|
349
|
+
|
350
|
+
with_baid = ::Killbill::Plugin::ActiveMerchant::Utils.normalized(properties_hash, :with_baid)
|
351
|
+
|
352
|
+
options = {}
|
353
|
+
options[:return_url] = ::Killbill::Plugin::ActiveMerchant::Utils.normalized(properties_hash, :return_url)
|
354
|
+
options[:cancel_return_url] = ::Killbill::Plugin::ActiveMerchant::Utils.normalized(properties_hash, :cancel_return_url)
|
355
|
+
|
356
|
+
amount_in_cents = amount.nil? ? nil : to_cents(amount, currency)
|
357
|
+
response = @private_api.initiate_express_checkout(kb_account_id,
|
358
|
+
context.tenant_id.to_s,
|
359
|
+
amount_in_cents,
|
360
|
+
currency,
|
361
|
+
with_baid,
|
362
|
+
options)
|
363
|
+
unless response.success?
|
364
|
+
raise "Unable to initiate paypal express checkout: #{response.message}"
|
365
|
+
end
|
366
|
+
|
367
|
+
response
|
368
|
+
end
|
241
369
|
end
|
242
370
|
end
|
243
371
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ActiveMerchant #:nodoc:
|
2
|
+
module Billing #:nodoc:
|
3
|
+
module PaypalCommonAPI
|
4
|
+
|
5
|
+
DUPLICATE_REQUEST_CODE = '11607'
|
6
|
+
|
7
|
+
alias_method :original_successful?, :successful?
|
8
|
+
|
9
|
+
# Note: this may need more thoughts when/if we want to support MsgSubID
|
10
|
+
# See https://developer.paypal.com/docs/classic/express-checkout/integration-guide/ECRelatedAPIOps/#idempotency
|
11
|
+
# For now, we just want to correctly handle a subsequent payment using a one-time token
|
12
|
+
# (error "A successful transaction has already been completed for this token.")
|
13
|
+
def successful?(response)
|
14
|
+
response[:error_codes] == DUPLICATE_REQUEST_CODE ? false : original_successful?(response)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -59,6 +59,14 @@ module Killbill #:nodoc:
|
|
59
59
|
model)
|
60
60
|
end
|
61
61
|
|
62
|
+
def self.last_token(kb_account_id, kb_tenant_id)
|
63
|
+
response = where(:api_call => 'initiate_express_checkout',
|
64
|
+
:success => true,
|
65
|
+
:kb_account_id => kb_account_id,
|
66
|
+
:kb_tenant_id => kb_tenant_id).last
|
67
|
+
response.nil? ? nil : response.token
|
68
|
+
end
|
69
|
+
|
62
70
|
def to_transaction_info_plugin(transaction=nil)
|
63
71
|
t_info_plugin = super(transaction)
|
64
72
|
|
@@ -10,23 +10,23 @@ module Killbill #:nodoc:
|
|
10
10
|
end
|
11
11
|
|
12
12
|
# See https://cms.paypal.com/uk/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_ECReferenceTxns
|
13
|
-
def initiate_express_checkout(kb_account_id, kb_tenant_id, amount_in_cents=0, currency='USD', options = {})
|
14
|
-
payment_processor_account_id = (options[:payment_processor_account_id] || :default)
|
15
|
-
|
13
|
+
def initiate_express_checkout(kb_account_id, kb_tenant_id, amount_in_cents=0, currency='USD', with_baid=true, options = {})
|
16
14
|
options[:currency] ||= currency
|
17
15
|
|
18
16
|
# Required arguments
|
19
17
|
options[:return_url] ||= 'http://www.example.com/success'
|
20
18
|
options[:cancel_return_url] ||= 'http://www.example.com/sad_panda'
|
21
19
|
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
if with_baid
|
21
|
+
options[:billing_agreement] ||= {}
|
22
|
+
options[:billing_agreement][:type] ||= 'MerchantInitiatedBilling'
|
23
|
+
options[:billing_agreement][:description] ||= 'Kill Bill billing agreement'
|
24
|
+
end
|
25
25
|
|
26
26
|
# Go to Paypal (SetExpressCheckout call)
|
27
|
-
payment_processor_account_id
|
28
|
-
paypal_express_response
|
29
|
-
response, transaction
|
27
|
+
payment_processor_account_id = options[:payment_processor_account_id] || :default
|
28
|
+
paypal_express_response = gateway(payment_processor_account_id, kb_tenant_id).setup_authorization(amount_in_cents, options)
|
29
|
+
response, transaction = save_response_and_transaction(paypal_express_response, :initiate_express_checkout, kb_account_id, kb_tenant_id, payment_processor_account_id)
|
30
30
|
|
31
31
|
response
|
32
32
|
end
|
data/pom.xml
CHANGED
@@ -26,7 +26,7 @@
|
|
26
26
|
<groupId>org.kill-bill.billing.plugin.ruby</groupId>
|
27
27
|
<artifactId>paypal-express-plugin</artifactId>
|
28
28
|
<packaging>pom</packaging>
|
29
|
-
<version>4.
|
29
|
+
<version>4.1.0</version>
|
30
30
|
<name>paypal-express-plugin</name>
|
31
31
|
<url>http://github.com/killbill/killbill-paypal-express-plugin</url>
|
32
32
|
<description>Plugin for accessing Paypal Express Checkout as a payment gateway</description>
|
data/release.sh
CHANGED
File without changes
|
@@ -31,23 +31,6 @@ describe Killbill::PaypalExpress::PaymentPlugin do
|
|
31
31
|
@plugin.stop_plugin
|
32
32
|
end
|
33
33
|
|
34
|
-
it 'should generate forms correctly' do
|
35
|
-
kb_account_id = SecureRandom.uuid
|
36
|
-
kb_tenant_id = SecureRandom.uuid
|
37
|
-
context = @plugin.kb_apis.create_context(kb_tenant_id)
|
38
|
-
fields = @plugin.hash_to_properties({
|
39
|
-
:order_id => '1234',
|
40
|
-
:amount => 10
|
41
|
-
})
|
42
|
-
form = @plugin.build_form_descriptor kb_account_id, fields, [], context
|
43
|
-
|
44
|
-
form.kb_account_id.should == kb_account_id
|
45
|
-
form.form_method.should == 'POST'
|
46
|
-
form.form_url.should == 'https://www.paypal.com/cgi-bin/webscr'
|
47
|
-
|
48
|
-
form_fields = @plugin.properties_to_hash(form.form_fields)
|
49
|
-
end
|
50
|
-
|
51
34
|
it 'should receive notifications correctly' do
|
52
35
|
description = 'description'
|
53
36
|
|
@@ -0,0 +1,287 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
ActiveMerchant::Billing::Base.mode = :test
|
4
|
+
|
5
|
+
describe Killbill::PaypalExpress::PaymentPlugin do
|
6
|
+
|
7
|
+
include ::Killbill::Plugin::ActiveMerchant::RSpec
|
8
|
+
|
9
|
+
# Share the BAID
|
10
|
+
before(:all) do
|
11
|
+
@plugin = build_plugin(::Killbill::PaypalExpress::PaymentPlugin, 'paypal_express')
|
12
|
+
svcs = @plugin.kb_apis.proxied_services
|
13
|
+
svcs[:payment_api] = PaypalExpressJavaPaymentApi.new(@plugin)
|
14
|
+
@plugin.kb_apis = ::Killbill::Plugin::KillbillApi.new('paypal_express', svcs)
|
15
|
+
@plugin.start_plugin
|
16
|
+
|
17
|
+
@call_context = build_call_context
|
18
|
+
|
19
|
+
@properties = []
|
20
|
+
@amount = BigDecimal.new('100')
|
21
|
+
@currency = 'USD'
|
22
|
+
|
23
|
+
kb_account_id = SecureRandom.uuid
|
24
|
+
external_key, kb_account_id = create_kb_account(kb_account_id, @plugin.kb_apis.proxied_services[:account_user_api])
|
25
|
+
|
26
|
+
# Initiate the setup process
|
27
|
+
response = create_token(kb_account_id, @call_context.tenant_id)
|
28
|
+
token = response.token
|
29
|
+
print "\nPlease go to #{@plugin.to_express_checkout_url(response, @call_context.tenant_id)} to proceed and press any key to continue...
|
30
|
+
Note: you need to log-in with a paypal sandbox account (create one here: https://developer.paypal.com/webapps/developer/applications/accounts)\n"
|
31
|
+
$stdin.gets
|
32
|
+
|
33
|
+
# Complete the setup process
|
34
|
+
@properties << build_property('token', token)
|
35
|
+
@pm = create_payment_method(::Killbill::PaypalExpress::PaypalExpressPaymentMethod, kb_account_id, @call_context.tenant_id, @properties)
|
36
|
+
|
37
|
+
# Verify our table directly. Note that @pm.token is the baid
|
38
|
+
payment_methods = ::Killbill::PaypalExpress::PaypalExpressPaymentMethod.from_kb_account_id_and_token(@pm.token, kb_account_id, @call_context.tenant_id)
|
39
|
+
payment_methods.size.should == 1
|
40
|
+
payment_method = payment_methods.first
|
41
|
+
payment_method.should_not be_nil
|
42
|
+
payment_method.paypal_express_payer_id.should_not be_nil
|
43
|
+
payment_method.token.should == @pm.token
|
44
|
+
payment_method.kb_payment_method_id.should == @pm.kb_payment_method_id
|
45
|
+
end
|
46
|
+
|
47
|
+
before(:each) do
|
48
|
+
::Killbill::PaypalExpress::PaypalExpressTransaction.delete_all
|
49
|
+
::Killbill::PaypalExpress::PaypalExpressResponse.delete_all
|
50
|
+
|
51
|
+
kb_payment_id = SecureRandom.uuid
|
52
|
+
1.upto(6) do
|
53
|
+
@kb_payment = @plugin.kb_apis.proxied_services[:payment_api].add_payment(kb_payment_id)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should be able to charge and refund' do
|
58
|
+
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)
|
59
|
+
payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
|
60
|
+
payment_response.amount.should == @amount
|
61
|
+
payment_response.transaction_type.should == :PURCHASE
|
62
|
+
|
63
|
+
# Verify GET API
|
64
|
+
payment_infos = @plugin.get_payment_info(@pm.kb_account_id, @kb_payment.id, [], @call_context)
|
65
|
+
payment_infos.size.should == 1
|
66
|
+
payment_infos[0].kb_payment_id.should == @kb_payment.id
|
67
|
+
payment_infos[0].transaction_type.should == :PURCHASE
|
68
|
+
payment_infos[0].amount.should == @amount
|
69
|
+
payment_infos[0].currency.should == @currency
|
70
|
+
payment_infos[0].status.should == :PROCESSED
|
71
|
+
payment_infos[0].gateway_error.should == 'Success'
|
72
|
+
payment_infos[0].gateway_error_code.should be_nil
|
73
|
+
|
74
|
+
# Try a full refund
|
75
|
+
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)
|
76
|
+
refund_response.status.should eq(:PROCESSED), refund_response.gateway_error
|
77
|
+
refund_response.amount.should == @amount
|
78
|
+
refund_response.transaction_type.should == :REFUND
|
79
|
+
|
80
|
+
# Verify GET API
|
81
|
+
payment_infos = @plugin.get_payment_info(@pm.kb_account_id, @kb_payment.id, [], @call_context)
|
82
|
+
payment_infos.size.should == 2
|
83
|
+
payment_infos[0].kb_payment_id.should == @kb_payment.id
|
84
|
+
payment_infos[0].transaction_type.should == :PURCHASE
|
85
|
+
payment_infos[0].amount.should == @amount
|
86
|
+
payment_infos[0].currency.should == @currency
|
87
|
+
payment_infos[0].status.should == :PROCESSED
|
88
|
+
payment_infos[0].gateway_error.should == 'Success'
|
89
|
+
payment_infos[0].gateway_error_code.should be_nil
|
90
|
+
payment_infos[1].kb_payment_id.should == @kb_payment.id
|
91
|
+
payment_infos[1].transaction_type.should == :REFUND
|
92
|
+
payment_infos[1].amount.should == @amount
|
93
|
+
payment_infos[1].currency.should == @currency
|
94
|
+
payment_infos[1].status.should == :PROCESSED
|
95
|
+
payment_infos[1].gateway_error.should == 'Success'
|
96
|
+
payment_infos[1].gateway_error_code.should be_nil
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should be able to auth, capture and refund' do
|
100
|
+
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)
|
101
|
+
payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
|
102
|
+
payment_response.amount.should == @amount
|
103
|
+
payment_response.transaction_type.should == :AUTHORIZE
|
104
|
+
|
105
|
+
# Verify GET API
|
106
|
+
payment_infos = @plugin.get_payment_info(@pm.kb_account_id, @kb_payment.id, [], @call_context)
|
107
|
+
payment_infos.size.should == 1
|
108
|
+
payment_infos[0].kb_payment_id.should == @kb_payment.id
|
109
|
+
payment_infos[0].transaction_type.should == :AUTHORIZE
|
110
|
+
payment_infos[0].amount.should == @amount
|
111
|
+
payment_infos[0].currency.should == @currency
|
112
|
+
payment_infos[0].status.should == :PROCESSED
|
113
|
+
payment_infos[0].gateway_error.should == 'Success'
|
114
|
+
payment_infos[0].gateway_error_code.should be_nil
|
115
|
+
|
116
|
+
# Try multiple partial captures
|
117
|
+
partial_capture_amount = BigDecimal.new('10')
|
118
|
+
1.upto(3) do |i|
|
119
|
+
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)
|
120
|
+
payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
|
121
|
+
payment_response.amount.should == partial_capture_amount
|
122
|
+
payment_response.transaction_type.should == :CAPTURE
|
123
|
+
|
124
|
+
# Verify GET API
|
125
|
+
payment_infos = @plugin.get_payment_info(@pm.kb_account_id, @kb_payment.id, [], @call_context)
|
126
|
+
payment_infos.size.should == 1 + i
|
127
|
+
payment_infos[i].kb_payment_id.should == @kb_payment.id
|
128
|
+
payment_infos[i].transaction_type.should == :CAPTURE
|
129
|
+
payment_infos[i].amount.should == partial_capture_amount
|
130
|
+
payment_infos[i].currency.should == @currency
|
131
|
+
payment_infos[i].status.should == :PROCESSED
|
132
|
+
payment_infos[i].gateway_error.should == 'Success'
|
133
|
+
payment_infos[i].gateway_error_code.should be_nil
|
134
|
+
end
|
135
|
+
|
136
|
+
# Try a partial refund
|
137
|
+
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)
|
138
|
+
refund_response.status.should eq(:PROCESSED), refund_response.gateway_error
|
139
|
+
refund_response.amount.should == partial_capture_amount
|
140
|
+
refund_response.transaction_type.should == :REFUND
|
141
|
+
|
142
|
+
# Verify GET API
|
143
|
+
payment_infos = @plugin.get_payment_info(@pm.kb_account_id, @kb_payment.id, [], @call_context)
|
144
|
+
payment_infos.size.should == 5
|
145
|
+
payment_infos[4].kb_payment_id.should == @kb_payment.id
|
146
|
+
payment_infos[4].transaction_type.should == :REFUND
|
147
|
+
payment_infos[4].amount.should == partial_capture_amount
|
148
|
+
payment_infos[4].currency.should == @currency
|
149
|
+
payment_infos[4].status.should == :PROCESSED
|
150
|
+
payment_infos[4].gateway_error.should == 'Success'
|
151
|
+
payment_infos[4].gateway_error_code.should be_nil
|
152
|
+
|
153
|
+
# Try to capture again
|
154
|
+
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)
|
155
|
+
payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
|
156
|
+
payment_response.amount.should == partial_capture_amount
|
157
|
+
payment_response.transaction_type.should == :CAPTURE
|
158
|
+
|
159
|
+
# Verify GET API
|
160
|
+
payment_infos = @plugin.get_payment_info(@pm.kb_account_id, @kb_payment.id, [], @call_context)
|
161
|
+
payment_infos.size.should == 6
|
162
|
+
payment_infos[5].kb_payment_id.should == @kb_payment.id
|
163
|
+
payment_infos[5].transaction_type.should == :CAPTURE
|
164
|
+
payment_infos[5].amount.should == partial_capture_amount
|
165
|
+
payment_infos[5].currency.should == @currency
|
166
|
+
payment_infos[5].status.should == :PROCESSED
|
167
|
+
payment_infos[5].gateway_error.should == 'Success'
|
168
|
+
payment_infos[5].gateway_error_code.should be_nil
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'should be able to auth and void' do
|
172
|
+
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)
|
173
|
+
payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
|
174
|
+
payment_response.amount.should == @amount
|
175
|
+
payment_response.transaction_type.should == :AUTHORIZE
|
176
|
+
|
177
|
+
# Verify GET API
|
178
|
+
payment_infos = @plugin.get_payment_info(@pm.kb_account_id, @kb_payment.id, [], @call_context)
|
179
|
+
payment_infos.size.should == 1
|
180
|
+
payment_infos[0].kb_payment_id.should == @kb_payment.id
|
181
|
+
payment_infos[0].transaction_type.should == :AUTHORIZE
|
182
|
+
payment_infos[0].amount.should == @amount
|
183
|
+
payment_infos[0].currency.should == @currency
|
184
|
+
payment_infos[0].status.should == :PROCESSED
|
185
|
+
payment_infos[0].gateway_error.should == 'Success'
|
186
|
+
payment_infos[0].gateway_error_code.should be_nil
|
187
|
+
|
188
|
+
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)
|
189
|
+
payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
|
190
|
+
payment_response.transaction_type.should == :VOID
|
191
|
+
|
192
|
+
# Verify GET API
|
193
|
+
payment_infos = @plugin.get_payment_info(@pm.kb_account_id, @kb_payment.id, [], @call_context)
|
194
|
+
payment_infos.size.should == 2
|
195
|
+
payment_infos[0].kb_payment_id.should == @kb_payment.id
|
196
|
+
payment_infos[0].transaction_type.should == :AUTHORIZE
|
197
|
+
payment_infos[0].amount.should == @amount
|
198
|
+
payment_infos[0].currency.should == @currency
|
199
|
+
payment_infos[0].status.should == :PROCESSED
|
200
|
+
payment_infos[0].gateway_error.should == 'Success'
|
201
|
+
payment_infos[0].gateway_error_code.should be_nil
|
202
|
+
payment_infos[1].kb_payment_id.should == @kb_payment.id
|
203
|
+
payment_infos[1].transaction_type.should == :VOID
|
204
|
+
payment_infos[1].amount.should be_nil
|
205
|
+
payment_infos[1].currency.should be_nil
|
206
|
+
payment_infos[1].status.should == :PROCESSED
|
207
|
+
payment_infos[1].gateway_error.should == 'Success'
|
208
|
+
payment_infos[1].gateway_error_code.should be_nil
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'should be able to auth, partial capture and void' do
|
212
|
+
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)
|
213
|
+
payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
|
214
|
+
payment_response.amount.should == @amount
|
215
|
+
payment_response.transaction_type.should == :AUTHORIZE
|
216
|
+
|
217
|
+
# Verify GET API
|
218
|
+
payment_infos = @plugin.get_payment_info(@pm.kb_account_id, @kb_payment.id, [], @call_context)
|
219
|
+
payment_infos.size.should == 1
|
220
|
+
payment_infos[0].kb_payment_id.should == @kb_payment.id
|
221
|
+
payment_infos[0].transaction_type.should == :AUTHORIZE
|
222
|
+
payment_infos[0].amount.should == @amount
|
223
|
+
payment_infos[0].currency.should == @currency
|
224
|
+
payment_infos[0].status.should == :PROCESSED
|
225
|
+
payment_infos[0].gateway_error.should == 'Success'
|
226
|
+
payment_infos[0].gateway_error_code.should be_nil
|
227
|
+
|
228
|
+
partial_capture_amount = BigDecimal.new('10')
|
229
|
+
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)
|
230
|
+
payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
|
231
|
+
payment_response.amount.should == partial_capture_amount
|
232
|
+
payment_response.transaction_type.should == :CAPTURE
|
233
|
+
|
234
|
+
# Verify GET API
|
235
|
+
payment_infos = @plugin.get_payment_info(@pm.kb_account_id, @kb_payment.id, [], @call_context)
|
236
|
+
payment_infos.size.should == 2
|
237
|
+
payment_infos[1].kb_payment_id.should == @kb_payment.id
|
238
|
+
payment_infos[1].transaction_type.should == :CAPTURE
|
239
|
+
payment_infos[1].amount.should == partial_capture_amount
|
240
|
+
payment_infos[1].currency.should == @currency
|
241
|
+
payment_infos[1].status.should == :PROCESSED
|
242
|
+
payment_infos[1].gateway_error.should == 'Success'
|
243
|
+
payment_infos[1].gateway_error_code.should be_nil
|
244
|
+
|
245
|
+
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)
|
246
|
+
payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
|
247
|
+
payment_response.transaction_type.should == :VOID
|
248
|
+
|
249
|
+
# Verify GET API
|
250
|
+
payment_infos = @plugin.get_payment_info(@pm.kb_account_id, @kb_payment.id, [], @call_context)
|
251
|
+
payment_infos.size.should == 3
|
252
|
+
payment_infos[2].kb_payment_id.should == @kb_payment.id
|
253
|
+
payment_infos[2].transaction_type.should == :VOID
|
254
|
+
payment_infos[2].amount.should be_nil
|
255
|
+
payment_infos[2].currency.should be_nil
|
256
|
+
payment_infos[2].status.should == :PROCESSED
|
257
|
+
payment_infos[2].gateway_error.should == 'Success'
|
258
|
+
payment_infos[2].gateway_error_code.should be_nil
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'should generate forms correctly' do
|
262
|
+
context = @plugin.kb_apis.create_context(@call_context.tenant_id)
|
263
|
+
fields = @plugin.hash_to_properties(
|
264
|
+
:order_id => '1234',
|
265
|
+
:amount => 12,
|
266
|
+
)
|
267
|
+
|
268
|
+
properties = @plugin.hash_to_properties(
|
269
|
+
:create_pending_payment => false
|
270
|
+
)
|
271
|
+
|
272
|
+
form = @plugin.build_form_descriptor(@pm.kb_account_id, fields, properties, context)
|
273
|
+
|
274
|
+
form.kb_account_id.should == @pm.kb_account_id
|
275
|
+
form.form_method.should == 'GET'
|
276
|
+
form.form_url.should start_with('https://www.sandbox.paypal.com/cgi-bin/webscr')
|
277
|
+
end
|
278
|
+
|
279
|
+
private
|
280
|
+
|
281
|
+
def create_token(kb_account_id, kb_tenant_id)
|
282
|
+
private_plugin = ::Killbill::PaypalExpress::PrivatePaymentPlugin.new
|
283
|
+
response = private_plugin.initiate_express_checkout(kb_account_id, kb_tenant_id)
|
284
|
+
response.success.should be_true
|
285
|
+
response
|
286
|
+
end
|
287
|
+
end
|