killbill-paypal-express 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
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