killbill-paypal-express 1.0.2 → 1.0.3

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.
data/Jarfile CHANGED
@@ -1,3 +1,3 @@
1
- jar 'com.ning.billing:killbill-api', '0.1.63'
2
- jar 'com.ning.billing:killbill-util:tests', '0.1.63'
1
+ jar 'com.ning.billing:killbill-api', '0.1.78'
2
+ jar 'com.ning.billing:killbill-util:tests', '0.1.78'
3
3
  jar 'javax.servlet:javax.servlet-api', '3.0.1'
data/README.md CHANGED
@@ -4,4 +4,89 @@
4
4
  killbill-paypal-express-plugin
5
5
  ==============================
6
6
 
7
- Plugin to use Express Checkout as a gateway
7
+ Plugin to use Express Checkout as a gateway.
8
+
9
+ Usage
10
+ -----
11
+
12
+ Issue the following call to generate a Paypal token:
13
+
14
+ ```
15
+ curl -v \
16
+ -X POST \
17
+ -H "Content-Type: application/json" \
18
+ --data-binary '{
19
+ "kb_account_id": "13d26090-b8d7-11e2-9e96-0800200c9a66",
20
+ "currency": "USD",
21
+ "options": {
22
+ "return_url": "http://www.google.com/?q=SUCCESS",
23
+ "cancel_return_url": "http://www.google.com/?q=FAILURE",
24
+ "billing_agreement": {
25
+ "description": "Your subscription"
26
+ }
27
+ }
28
+ }' \
29
+ "http://$HOST:8080/plugins/killbill-paypal-express/1.0/setup-checkout"
30
+ ```
31
+
32
+ Kill Bill will return a 303 See Other on success. The customer should be redirected to the url specified in the Location header, e.g. https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-20G53990M6953444J.
33
+
34
+ Once the customer comes back from the PayPal flow, save the BAID in Kill Bill:
35
+
36
+ ```
37
+ curl -v \
38
+ -X POST \
39
+ -H "Content-Type: application/json" \
40
+ -H "X-Killbill-CreatedBy: Web server" \
41
+ -H "X-Killbill-Reason: New account" \
42
+ --data-binary '{
43
+ "pluginName": "killbill-paypal-express",
44
+ "pluginInfo": {
45
+ "properties": [{
46
+ "key": "token",
47
+ "value": "20G53990M6953444J"
48
+ }]
49
+ }
50
+ }' \
51
+ "http://$HOST:8080/1.0/kb/accounts/13d26090-b8d7-11e2-9e96-0800200c9a66/paymentMethods?isDefault=true"
52
+ ```
53
+
54
+ To display the payment method details for that account, one can call:
55
+
56
+ ```
57
+ curl -v \
58
+ "http://$HOST:8080/1.0/kb/accounts/13d26090-b8d7-11e2-9e96-0800200c9a66/paymentMethods?withPluginInfo=true"
59
+ ```
60
+
61
+ Requirements
62
+ ------------
63
+
64
+ The plugin needs a database. The latest version of the schema can be found here: https://raw.github.com/killbill/killbill-paypal-express-plugin/master/db/ddl.sql.
65
+
66
+ Configuration
67
+ -------------
68
+
69
+ The plugin expects a `paypal_express.yml` configuration file containing the following:
70
+
71
+ ```
72
+ :paypal:
73
+ :signature: 'your-paypal-signature'
74
+ :login: 'your-username-facilitator.something.com'
75
+ :password: 'your-password'
76
+ :log_file: '/var/tmp/paypal.log'
77
+ # Switch to false for production
78
+ :test: true
79
+
80
+ :database:
81
+ :adapter: 'sqlite3'
82
+ :database: 'test.db'
83
+ # For MySQL
84
+ # :adapter: 'jdbc'
85
+ # :username: 'your-username'
86
+ # :password: 'your-password'
87
+ # :driver: 'com.mysql.jdbc.Driver'
88
+ # :url: 'jdbc:mysql://127.0.0.1:3306/your-database'
89
+ ```
90
+
91
+ By default, the plugin will look at the plugin directory root (where `killbill.properties` is located) to find this file.
92
+ Alternatively, set the Kill Bill system property `-Dcom.ning.billing.osgi.bundles.jruby.conf.dir=/my/directory` to specify another location.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.2
1
+ 1.0.3
@@ -0,0 +1,83 @@
1
+ CREATE TABLE `paypal_express_payment_methods` (
2
+ `id` int(11) NOT NULL AUTO_INCREMENT,
3
+ `kb_account_id` varchar(255) NOT NULL,
4
+ `kb_payment_method_id` varchar(255) DEFAULT NULL,
5
+ `paypal_express_payer_id` varchar(255) DEFAULT NULL,
6
+ `paypal_express_baid` varchar(255) DEFAULT NULL,
7
+ `paypal_express_token` varchar(255) NOT NULL,
8
+ `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
9
+ `created_at` datetime NOT NULL,
10
+ `updated_at` datetime NOT NULL,
11
+ PRIMARY KEY (`id`)
12
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
13
+
14
+ CREATE TABLE `paypal_express_transactions` (
15
+ `id` int(11) NOT NULL AUTO_INCREMENT,
16
+ `paypal_express_response_id` int(11) NOT NULL,
17
+ `api_call` varchar(255) NOT NULL,
18
+ `kb_payment_id` varchar(255) NOT NULL,
19
+ `paypal_express_txn_id` varchar(255) NOT NULL,
20
+ `amount_in_cents` int(11) NOT NULL,
21
+ `created_at` datetime NOT NULL,
22
+ `updated_at` datetime NOT NULL,
23
+ PRIMARY KEY (`id`)
24
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
25
+
26
+ CREATE TABLE `paypal_express_responses` (
27
+ `id` int(11) NOT NULL AUTO_INCREMENT,
28
+ `api_call` varchar(255) NOT NULL,
29
+ `kb_payment_id` varchar(255) DEFAULT NULL,
30
+ `message` varchar(255) DEFAULT NULL,
31
+ `authorization` varchar(255) DEFAULT NULL,
32
+ `fraud_review` tinyint(1) DEFAULT NULL,
33
+ `test` tinyint(1) DEFAULT NULL,
34
+ `token` varchar(255) DEFAULT NULL,
35
+ `payer_id` varchar(255) DEFAULT NULL,
36
+ `billing_agreement_id` varchar(255) DEFAULT NULL,
37
+ `payer_name` varchar(255) DEFAULT NULL,
38
+ `payer_email` varchar(255) DEFAULT NULL,
39
+ `payer_country` varchar(255) DEFAULT NULL,
40
+ `contact_phone` varchar(255) DEFAULT NULL,
41
+ `ship_to_address_name` varchar(255) DEFAULT NULL,
42
+ `ship_to_address_company` varchar(255) DEFAULT NULL,
43
+ `ship_to_address_address1` varchar(255) DEFAULT NULL,
44
+ `ship_to_address_address2` varchar(255) DEFAULT NULL,
45
+ `ship_to_address_city` varchar(255) DEFAULT NULL,
46
+ `ship_to_address_state` varchar(255) DEFAULT NULL,
47
+ `ship_to_address_country` varchar(255) DEFAULT NULL,
48
+ `ship_to_address_zip` varchar(255) DEFAULT NULL,
49
+ `ship_to_address_phone` varchar(255) DEFAULT NULL,
50
+ `receiver_info_business` varchar(255) DEFAULT NULL,
51
+ `receiver_info_receiver` varchar(255) DEFAULT NULL,
52
+ `receiver_info_receiverid` varchar(255) DEFAULT NULL,
53
+ `payment_info_transactionid` varchar(255) DEFAULT NULL,
54
+ `payment_info_parenttransactionid` varchar(255) DEFAULT NULL,
55
+ `payment_info_receiptid` varchar(255) DEFAULT NULL,
56
+ `payment_info_transactiontype` varchar(255) DEFAULT NULL,
57
+ `payment_info_paymenttype` varchar(255) DEFAULT NULL,
58
+ `payment_info_paymentdate` varchar(255) DEFAULT NULL,
59
+ `payment_info_grossamount` varchar(255) DEFAULT NULL,
60
+ `payment_info_feeamount` varchar(255) DEFAULT NULL,
61
+ `payment_info_taxamount` varchar(255) DEFAULT NULL,
62
+ `payment_info_exchangerate` varchar(255) DEFAULT NULL,
63
+ `payment_info_paymentstatus` varchar(255) DEFAULT NULL,
64
+ `payment_info_pendingreason` varchar(255) DEFAULT NULL,
65
+ `payment_info_reasoncode` varchar(255) DEFAULT NULL,
66
+ `payment_info_protectioneligibility` varchar(255) DEFAULT NULL,
67
+ `payment_info_protectioneligibilitytype` varchar(255) DEFAULT NULL,
68
+ `payment_info_shipamount` varchar(255) DEFAULT NULL,
69
+ `payment_info_shiphandleamount` varchar(255) DEFAULT NULL,
70
+ `payment_info_shipdiscount` varchar(255) DEFAULT NULL,
71
+ `payment_info_insuranceamount` varchar(255) DEFAULT NULL,
72
+ `payment_info_subject` varchar(255) DEFAULT NULL,
73
+ `avs_result_code` varchar(255) DEFAULT NULL,
74
+ `avs_result_message` varchar(255) DEFAULT NULL,
75
+ `avs_result_street_match` varchar(255) DEFAULT NULL,
76
+ `avs_result_postal_match` varchar(255) DEFAULT NULL,
77
+ `cvv_result_code` varchar(255) DEFAULT NULL,
78
+ `cvv_result_message` varchar(255) DEFAULT NULL,
79
+ `success` tinyint(1) DEFAULT NULL,
80
+ `created_at` datetime NOT NULL,
81
+ `updated_at` datetime NOT NULL,
82
+ PRIMARY KEY (`id`)
83
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@@ -22,8 +22,8 @@ Gem::Specification.new do |s|
22
22
 
23
23
  s.rdoc_options << '--exclude' << '.'
24
24
 
25
- s.add_dependency 'killbill', '~> 1.0.13'
26
- s.add_dependency 'activemerchant', '~> 1.31.1'
25
+ s.add_dependency 'killbill', '~> 1.0.16'
26
+ s.add_dependency 'activemerchant', '~> 2.0.0'
27
27
  s.add_dependency 'activerecord', '~> 3.2.1'
28
28
  s.add_dependency 'sinatra', '~> 1.3.4'
29
29
  if defined?(JRUBY_VERSION)
@@ -6,9 +6,6 @@ require 'singleton'
6
6
  require 'yaml'
7
7
 
8
8
  require 'killbill'
9
- require 'killbill/response/payment_method_response'
10
- require 'killbill/response/payment_response'
11
- require 'killbill/response/refund_response'
12
9
 
13
10
  require 'paypal_express/config/configuration'
14
11
  require 'paypal_express/config/properties'
@@ -1,7 +1,7 @@
1
1
  module Killbill::PaypalExpress
2
2
  class PaymentPlugin < Killbill::Plugin::Payment
3
3
  def start_plugin
4
- Killbill::PaypalExpress.initialize! "#{@root}/paypal_express.yml", @logger
4
+ Killbill::PaypalExpress.initialize! @logger, @conf_dir
5
5
  @gateway = Killbill::PaypalExpress.gateway
6
6
 
7
7
  @ip = Utils.ip
@@ -11,7 +11,16 @@ module Killbill::PaypalExpress
11
11
  @logger.info "Killbill::PaypalExpress::PaymentPlugin started"
12
12
  end
13
13
 
14
+ # return DB connections to the Pool if required
15
+ def after_request
16
+ ActiveRecord::Base.connection.close
17
+ end
18
+
14
19
  def process_payment(kb_account_id, kb_payment_id, kb_payment_method_id, amount_in_cents, currency, options = {})
20
+ # If the payment was already made, just return the status
21
+ paypal_express_transaction = PaypalExpressTransaction.from_kb_payment_id(kb_payment_id) rescue nil
22
+ return paypal_express_transaction.paypal_express_response.to_payment_response unless paypal_express_transaction.nil?
23
+
15
24
  options[:currency] ||= currency
16
25
  options[:payment_type] ||= 'Any'
17
26
  options[:invoice_id] ||= kb_payment_id
@@ -31,9 +40,7 @@ module Killbill::PaypalExpress
31
40
  end
32
41
 
33
42
  def process_refund(kb_account_id, kb_payment_id, amount_in_cents, currency, options = {})
34
- # Find one successful charge which amount is at least the amount we are trying to refund
35
- paypal_express_transaction = PaypalExpressTransaction.where("paypal_express_transactions.amount_in_cents >= ?", amount_in_cents).find_last_by_api_call_and_kb_payment_id(:charge, kb_payment_id)
36
- raise "Unable to find Paypal Express transaction id for payment #{kb_payment_id}" if paypal_express_transaction.nil?
43
+ paypal_express_transaction = PaypalExpressTransaction.find_candidate_transaction_for_refund(kb_payment_id, amount_in_cents)
37
44
 
38
45
  options[:currency] ||= currency
39
46
  options[:refund_type] ||= paypal_express_transaction.amount_in_cents != amount_in_cents ? 'Partial' : 'Full'
@@ -75,7 +82,7 @@ module Killbill::PaypalExpress
75
82
  payer_id = response.payer_id
76
83
  unless payer_id.nil?
77
84
  # Go to Paypal to create the BAID for recurring payments (CreateBillingAgreement call)
78
- paypal_express_baid_response = @gateway.create_billing_agreement :token => token
85
+ paypal_express_baid_response = @gateway.store token
79
86
  response = save_response_and_transaction paypal_express_baid_response, :create_billing_agreement
80
87
  return false unless response.success?
81
88
 
@@ -113,7 +120,7 @@ module Killbill::PaypalExpress
113
120
  response = PaypalExpressResponse.from_response(api_call, kb_payment_id, paypal_express_response)
114
121
  response.save!
115
122
 
116
- if response.success and !response.authorization.blank?
123
+ if response.success and !kb_payment_id.blank? and !response.authorization.blank?
117
124
  # Record the transaction
118
125
  transaction = response.create_paypal_express_transaction!(:amount_in_cents => amount_in_cents, :api_call => api_call, :kb_payment_id => kb_payment_id, :paypal_express_txn_id => response.authorization)
119
126
  @logger.debug "Recorded transaction: #{transaction.inspect}"
@@ -9,9 +9,10 @@ module Killbill::PaypalExpress
9
9
  mattr_reader :initialized
10
10
  mattr_reader :test
11
11
 
12
- def self.initialize!(config_file='paypal_express.yml', logger=Logger.new(STDOUT))
12
+ def self.initialize!(logger=Logger.new(STDOUT), conf_dir=File.expand_path('../../../', File.dirname(__FILE__)))
13
13
  @@logger = logger
14
14
 
15
+ config_file = "#{conf_dir}/paypal_express.yml"
15
16
  @@config = Properties.new(config_file)
16
17
  @@config.parse!
17
18
 
@@ -38,7 +38,7 @@ module Killbill::PaypalExpress
38
38
  # We don't store extra information in Paypal Express
39
39
  properties = []
40
40
 
41
- Killbill::Plugin::PaymentMethodResponse.new external_payment_method_id, is_default, properties
41
+ Killbill::Plugin::Model::PaymentMethodPlugin.new(external_payment_method_id, is_default, properties, "PayPal", nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
42
42
  end
43
43
  end
44
44
  end
@@ -118,31 +118,40 @@ module Killbill::PaypalExpress
118
118
  end
119
119
 
120
120
  def to_payment_response
121
- to_killbill_response Killbill::Plugin::PaymentResponse
121
+ to_killbill_response :payment
122
122
  end
123
123
 
124
124
  def to_refund_response
125
- to_killbill_response Killbill::Plugin::RefundResponse
125
+ to_killbill_response :refund
126
126
  end
127
127
 
128
128
  private
129
129
 
130
- def to_killbill_response(klass)
130
+ def to_killbill_response(type)
131
131
  if paypal_express_transaction.nil?
132
132
  # payment_info_grossamount is e.g. "100.00" - we need to convert it in cents
133
133
  amount_in_cents = payment_info_grossamount ? (payment_info_grossamount.to_f * 100).to_i : nil
134
134
  created_date = created_at
135
+ first_payment_reference_id = nil
136
+ second_payment_reference_id = nil
135
137
  else
136
138
  amount_in_cents = paypal_express_transaction.amount_in_cents
137
139
  created_date = paypal_express_transaction.created_at
140
+ first_payment_reference_id = paypal_express_transaction.paypal_express_txn_id
141
+ second_payment_reference_id = paypal_express_transaction.id.to_s
138
142
  end
139
143
 
140
144
  effective_date = created_date
141
- status = message
142
- gateway_error = nil
145
+ gateway_error = message
143
146
  gateway_error_code = nil
144
147
 
145
- klass.new(amount_in_cents, created_date, effective_date, status, gateway_error, gateway_error_code)
148
+ if type == :payment
149
+ status = success ? Killbill::Plugin::Model::PaymentPluginStatus.new(:PROCESSED) : Killbill::Plugin::Model::PaymentPluginStatus.new(:ERROR)
150
+ Killbill::Plugin::Model::PaymentInfoPlugin.new(amount_in_cents, created_date, effective_date, status, gateway_error, gateway_error_code, first_payment_reference_id, second_payment_reference_id)
151
+ else
152
+ status = success ? Killbill::Plugin::Model::RefundPluginStatus.new(:PROCESSED) : Killbill::Plugin::Model::RefundPluginStatus.new(:ERROR)
153
+ Killbill::Plugin::Model::RefundInfoPlugin.new(amount_in_cents, created_date, effective_date, status, gateway_error, gateway_error_code, first_payment_reference_id)
154
+ end
146
155
  end
147
156
 
148
157
  # Paypal has various response formats depending on the API call and the ActiveMerchant Paypal plugin doesn't try to
@@ -12,5 +12,22 @@ module Killbill::PaypalExpress
12
12
  raise "Killbill payment mapping to multiple Paypal Express transactions for payment #{kb_payment_id}" if paypal_express_transactions.size > 1
13
13
  paypal_express_transactions[0]
14
14
  end
15
+
16
+ def self.find_candidate_transaction_for_refund(kb_payment_id, amount_in_cents)
17
+ # Find one successful charge which amount is at least the amount we are trying to refund
18
+ paypal_express_transactions = PaypalExpressTransaction.where("paypal_express_transactions.amount_in_cents >= ?", amount_in_cents)
19
+ .find_all_by_api_call_and_kb_payment_id(:charge, kb_payment_id)
20
+ raise "Unable to find Paypal Express transaction id for payment #{kb_payment_id}" if paypal_express_transactions.size == 0
21
+
22
+ # We have candidates, but we now need to make sure we didn't refund more than for the specified amount
23
+ amount_refunded_in_cents = Killbill::PaypalExpress::PaypalExpressTransaction.where("api_call = ? and kb_payment_id = ?", :refund, kb_payment_id)
24
+ .sum("amount_in_cents")
25
+
26
+ amount_left_to_refund_in_cents = -amount_refunded_in_cents
27
+ paypal_express_transactions.map { |transaction| amount_left_to_refund_in_cents += transaction.amount_in_cents }
28
+ raise "Amount #{amount_in_cents} too large to refund for payment #{kb_payment_id}" if amount_left_to_refund_in_cents < amount_in_cents
29
+
30
+ paypal_express_transactions.first
31
+ end
15
32
  end
16
33
  end
data/pom.xml CHANGED
@@ -25,7 +25,7 @@
25
25
  <groupId>com.ning.killbill.ruby</groupId>
26
26
  <artifactId>paypal-express-plugin</artifactId>
27
27
  <packaging>pom</packaging>
28
- <version>1.0.0</version>
28
+ <version>1.0.2</version>
29
29
  <name>paypal-express-plugin</name>
30
30
  <scm>
31
31
  <connection>scm:git:git://github.com/killbill/killbill-paypal-express-plugin.git</connection>
data/release.sh CHANGED
@@ -1,4 +1,20 @@
1
+ set -e
2
+
1
3
  VERSION=`grep -E '<version>([0-9]+\.[0-9]+\.[0-9]+)</version>' pom.xml | sed 's/[\t \n]*<version>\(.*\)<\/version>[\t \n]*/\1/'`
4
+ if [ "$VERSION" != "$(cat $PWD/VERSION)" ]; then
5
+ echo "Unable to release: make sure the versions in pom.xml and VERSION match"
6
+ exit 1
7
+ fi
8
+
9
+ echo "Cleaning up"
10
+ rake killbill:clean ; rake build
11
+
12
+ echo "Pushing the gem to Rubygems"
13
+ rake release
14
+
15
+ echo "Building artifact"
16
+ rake killbill:package
17
+
2
18
  ARTIFACT="$PWD/pkg/killbill-paypal-express-$VERSION.tar.gz"
3
19
  echo "Pushing $ARTIFACT to Maven Central"
4
20
  mvn gpg:sign-and-deploy-file \
@@ -17,8 +17,8 @@ eos
17
17
  file.close
18
18
 
19
19
  @plugin = Killbill::PaypalExpress::PaymentPlugin.new
20
- @plugin.root = File.dirname(file)
21
20
  @plugin.logger = Logger.new(STDOUT)
21
+ @plugin.conf_dir = File.dirname(file)
22
22
 
23
23
  # Start the plugin here - since the config file will be deleted
24
24
  @plugin.start_plugin
@@ -6,7 +6,7 @@ ActiveMerchant::Billing::Base.mode = :test
6
6
  describe Killbill::PaypalExpress::PaymentPlugin do
7
7
  before(:each) do
8
8
  @plugin = Killbill::PaypalExpress::PaymentPlugin.new
9
- @plugin.root = File.expand_path(File.dirname(__FILE__) + '../../../../')
9
+ @plugin.conf_dir = File.expand_path(File.dirname(__FILE__) + '../../../../')
10
10
 
11
11
  logger = Logger.new(STDOUT)
12
12
  logger.level = Logger::DEBUG
metadata CHANGED
@@ -2,14 +2,14 @@
2
2
  name: killbill-paypal-express
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 1.0.2
5
+ version: 1.0.3
6
6
  platform: ruby
7
7
  authors:
8
8
  - Killbill core team
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-11 00:00:00.000000000 Z
12
+ date: 2013-05-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: killbill
@@ -17,13 +17,13 @@ dependencies:
17
17
  requirements:
18
18
  - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: 1.0.13
20
+ version: 1.0.16
21
21
  none: false
22
22
  requirement: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 1.0.13
26
+ version: 1.0.16
27
27
  none: false
28
28
  prerelease: false
29
29
  type: :runtime
@@ -33,13 +33,13 @@ dependencies:
33
33
  requirements:
34
34
  - - "~>"
35
35
  - !ruby/object:Gem::Version
36
- version: 1.31.1
36
+ version: 2.0.0
37
37
  none: false
38
38
  requirement: !ruby/object:Gem::Requirement
39
39
  requirements:
40
40
  - - "~>"
41
41
  - !ruby/object:Gem::Version
42
- version: 1.31.1
42
+ version: 2.0.0
43
43
  none: false
44
44
  prerelease: false
45
45
  type: :runtime
@@ -169,6 +169,7 @@ files:
169
169
  - Rakefile
170
170
  - VERSION
171
171
  - config.ru
172
+ - db/ddl.sql
172
173
  - db/schema.rb
173
174
  - killbill-paypal-express.gemspec
174
175
  - killbill.properties