killbill-paypal-express 4.0.0 → 4.1.0
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 +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
|