pay 3.0.23 → 4.0.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of pay might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/app/controllers/pay/webhooks/braintree_controller.rb +1 -1
- data/app/controllers/pay/webhooks/paddle_controller.rb +1 -1
- data/app/controllers/pay/webhooks/stripe_controller.rb +1 -1
- data/app/jobs/pay/customer_sync_job.rb +1 -3
- data/app/mailers/pay/application_mailer.rb +1 -1
- data/app/mailers/pay/user_mailer.rb +3 -3
- data/app/models/pay/charge.rb +23 -0
- data/app/models/pay/customer.rb +2 -6
- data/app/models/pay/merchant.rb +6 -0
- data/app/models/pay/subscription.rb +35 -8
- data/app/views/pay/user_mailer/receipt.html.erb +6 -6
- data/app/views/pay/user_mailer/refund.html.erb +6 -6
- data/config/locales/en.yml +31 -24
- data/config/routes.rb +3 -3
- data/lib/pay/attributes.rb +28 -2
- data/lib/pay/billable/sync_customer.rb +3 -3
- data/lib/pay/braintree/billable.rb +61 -48
- data/lib/pay/braintree/subscription.rb +8 -3
- data/lib/pay/braintree/webhooks/subscription_canceled.rb +6 -1
- data/lib/pay/braintree/webhooks/subscription_charged_successfully.rb +2 -2
- data/lib/pay/braintree/webhooks/subscription_charged_unsuccessfully.rb +1 -1
- data/lib/pay/braintree/webhooks/subscription_trial_ended.rb +1 -1
- data/lib/pay/braintree.rb +6 -2
- data/lib/pay/currency.rb +8 -2
- data/lib/pay/engine.rb +22 -4
- data/lib/pay/fake_processor/billable.rb +11 -8
- data/lib/pay/fake_processor/subscription.rb +11 -3
- data/lib/pay/paddle/billable.rb +0 -4
- data/lib/pay/paddle/subscription.rb +2 -2
- data/lib/pay/paddle/webhooks/signature_verifier.rb +45 -41
- data/lib/pay/paddle/webhooks/subscription_cancelled.rb +7 -2
- data/lib/pay/paddle/webhooks/subscription_payment_refunded.rb +3 -3
- data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +7 -7
- data/lib/pay/paddle/webhooks/subscription_updated.rb +15 -15
- data/lib/pay/paddle.rb +14 -2
- data/lib/pay/receipts.rb +106 -32
- data/lib/pay/stripe/billable.rb +58 -21
- data/lib/pay/stripe/charge.rb +93 -11
- data/lib/pay/stripe/merchant.rb +1 -1
- data/lib/pay/stripe/subscription.rb +132 -30
- data/lib/pay/stripe/webhooks/charge_refunded.rb +2 -2
- data/lib/pay/stripe/webhooks/charge_succeeded.rb +2 -2
- data/lib/pay/stripe/webhooks/payment_action_required.rb +6 -6
- data/lib/pay/stripe/webhooks/subscription_renewing.rb +6 -10
- data/lib/pay/stripe/webhooks/subscription_updated.rb +1 -1
- data/lib/pay/stripe.rb +10 -1
- data/lib/pay/version.rb +1 -1
- data/lib/pay.rb +39 -4
- data/lib/tasks/pay.rake +1 -1
- metadata +3 -4
- data/lib/pay/merchant.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '09c79f10c2e9258ebfd7a54a58283d0d14eb33121b5f51f4a5b563088f672d41'
|
4
|
+
data.tar.gz: 14af03dc31b7b65f9975c90876d1ffb764009e3cae1038abe2bce37822901c49
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb583c78584c50bbf21d8d15f7a97449073864cb7064c6ae265639d3f75a97d528868d9db1b2b5d4a8e4e1036d19dd55a4ecdd49f96b9f24149f8e38c4cd4eab
|
7
|
+
data.tar.gz: 95f41a1f851f09d9dae598a7d2f795668f4c1b5f28b0fec2ac05e7aed1fd95ad5aefdc5f1fab28f4cbf9a16b6385d55d40bb0f78562906335d8373eb4d76cdee
|
data/README.md
CHANGED
@@ -49,7 +49,7 @@ Want to add a new payment provider? Contributions are welcome.
|
|
49
49
|
* **Marketplaces**
|
50
50
|
* [Stripe Connect](docs/marketplaces/stripe_connect.md)
|
51
51
|
* **Contributing**
|
52
|
-
* [Adding A Payment Processor](docs/contributing/
|
52
|
+
* [Adding A Payment Processor](docs/contributing/adding_a_payment_processor.md)
|
53
53
|
|
54
54
|
## 🙏 Contributing
|
55
55
|
|
@@ -17,7 +17,7 @@ module Pay
|
|
17
17
|
def queue_event(event)
|
18
18
|
return unless Pay::Webhooks.delegator.listening?("braintree.#{event.kind}")
|
19
19
|
|
20
|
-
record = Pay::Webhook.create(
|
20
|
+
record = Pay::Webhook.create!(
|
21
21
|
processor: :braintree,
|
22
22
|
event_type: event.kind,
|
23
23
|
event: {bt_signature: params[:bt_signature], bt_payload: params[:bt_payload]}
|
@@ -17,7 +17,7 @@ module Pay
|
|
17
17
|
def queue_event(event)
|
18
18
|
return unless Pay::Webhooks.delegator.listening?("paddle.#{params[:alert_name]}")
|
19
19
|
|
20
|
-
record = Pay::Webhook.create(processor: :paddle, event_type: params[:alert_name], event: event)
|
20
|
+
record = Pay::Webhook.create!(processor: :paddle, event_type: params[:alert_name], event: event)
|
21
21
|
Pay::Webhooks::ProcessJob.perform_later(record)
|
22
22
|
end
|
23
23
|
|
@@ -18,7 +18,7 @@ module Pay
|
|
18
18
|
def queue_event(event)
|
19
19
|
return unless Pay::Webhooks.delegator.listening?("stripe.#{event.type}")
|
20
20
|
|
21
|
-
record = Pay::Webhook.create(processor: :stripe, event_type: event.type, event: event)
|
21
|
+
record = Pay::Webhook.create!(processor: :stripe, event_type: event.type, event: event)
|
22
22
|
Pay::Webhooks::ProcessJob.perform_later(record)
|
23
23
|
end
|
24
24
|
|
@@ -1,11 +1,9 @@
|
|
1
1
|
module Pay
|
2
2
|
class CustomerSyncJob < ApplicationJob
|
3
|
-
queue_as :default
|
4
|
-
|
5
3
|
def perform(pay_customer_id)
|
6
4
|
Pay::Customer.find(pay_customer_id).update_customer!
|
7
5
|
rescue ActiveRecord::RecordNotFound
|
8
|
-
Rails.logger.info "Couldn't find a Pay::Customer with ID = #{
|
6
|
+
Rails.logger.info "Couldn't find a Pay::Customer with ID = #{pay_customer_id}"
|
9
7
|
end
|
10
8
|
end
|
11
9
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module Pay
|
2
|
-
class UserMailer <
|
2
|
+
class UserMailer < Pay.parent_mailer.constantize
|
3
3
|
def receipt
|
4
|
-
if params[:
|
5
|
-
attachments[params[:
|
4
|
+
if params[:pay_charge].respond_to? :receipt
|
5
|
+
attachments[params[:pay_charge].filename] = params[:pay_charge].receipt
|
6
6
|
end
|
7
7
|
|
8
8
|
mail to: to
|
data/app/models/pay/charge.rb
CHANGED
@@ -17,6 +17,7 @@ module Pay
|
|
17
17
|
|
18
18
|
# Store the payment method kind (card, paypal, etc)
|
19
19
|
store_accessor :data, :paddle_receipt_url
|
20
|
+
store_accessor :data, :stripe_receipt_url
|
20
21
|
store_accessor :data, :stripe_account
|
21
22
|
|
22
23
|
# Payment method attributes
|
@@ -29,6 +30,18 @@ module Pay
|
|
29
30
|
store_accessor :data, :username # Venmo
|
30
31
|
store_accessor :data, :bank
|
31
32
|
|
33
|
+
store_accessor :data, :amount_captured
|
34
|
+
store_accessor :data, :invoice_id
|
35
|
+
store_accessor :data, :payment_intent_id
|
36
|
+
store_accessor :data, :period_start
|
37
|
+
store_accessor :data, :period_end
|
38
|
+
store_accessor :data, :line_items
|
39
|
+
store_accessor :data, :subtotal # subtotal amount in cents
|
40
|
+
store_accessor :data, :tax # total tax amount in cents
|
41
|
+
store_accessor :data, :discounts # array of discount IDs applied to the Stripe Invoice
|
42
|
+
store_accessor :data, :total_discount_amounts # array of discount details
|
43
|
+
store_accessor :data, :total_tax_amounts # array of tax details for each jurisdiction
|
44
|
+
|
32
45
|
# Helpers for payment processors
|
33
46
|
%w[braintree stripe paddle fake_processor].each do |processor_name|
|
34
47
|
define_method "#{processor_name}?" do
|
@@ -38,6 +51,8 @@ module Pay
|
|
38
51
|
scope processor_name, -> { where(processor: processor_name) }
|
39
52
|
end
|
40
53
|
|
54
|
+
delegate :capture, :credit_note!, :credit_notes, to: :payment_processor
|
55
|
+
|
41
56
|
def self.find_by_processor_and_id(processor, processor_id)
|
42
57
|
joins(:customer).find_by(processor_id: processor_id, pay_customers: {processor: processor})
|
43
58
|
end
|
@@ -54,6 +69,10 @@ module Pay
|
|
54
69
|
payment_processor.charge
|
55
70
|
end
|
56
71
|
|
72
|
+
def captured?
|
73
|
+
amount_captured > 0
|
74
|
+
end
|
75
|
+
|
57
76
|
def refund!(refund_amount = nil)
|
58
77
|
refund_amount ||= amount
|
59
78
|
payment_processor.refund!(refund_amount)
|
@@ -111,5 +130,9 @@ module Pay
|
|
111
130
|
payment_method_type&.titleize
|
112
131
|
end
|
113
132
|
end
|
133
|
+
|
134
|
+
def line_items
|
135
|
+
Array.wrap(super)
|
136
|
+
end
|
114
137
|
end
|
115
138
|
end
|
data/app/models/pay/customer.rb
CHANGED
@@ -41,12 +41,8 @@ module Pay
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def subscribed?(name: Pay.default_product_name, processor_plan: nil)
|
44
|
-
|
45
|
-
|
46
|
-
return false if subscription.nil?
|
47
|
-
return subscription.active? if processor_plan.nil?
|
48
|
-
|
49
|
-
subscription.active? && subscription.processor_plan == processor_plan
|
44
|
+
query = {name: name, processor_plan: processor_plan}.compact
|
45
|
+
subscriptions.active.where(query).exists?
|
50
46
|
end
|
51
47
|
|
52
48
|
def on_trial?(name: Pay.default_product_name, plan: nil)
|
data/app/models/pay/merchant.rb
CHANGED
@@ -16,5 +16,11 @@ module Pay
|
|
16
16
|
return if processor.blank?
|
17
17
|
@pay_processor ||= self.class.pay_processor_for(processor).new(self)
|
18
18
|
end
|
19
|
+
|
20
|
+
def onboarding_complete?
|
21
|
+
ActiveModel::Type::Boolean.new.cast(
|
22
|
+
(data.presence || {})["onboarding_complete"]
|
23
|
+
)
|
24
|
+
end
|
19
25
|
end
|
20
26
|
end
|
@@ -11,7 +11,7 @@ module Pay
|
|
11
11
|
scope :on_trial, -> { where.not(trial_ends_at: nil).where("#{table_name}.trial_ends_at > ?", Time.zone.now) }
|
12
12
|
scope :cancelled, -> { where.not(ends_at: nil) }
|
13
13
|
scope :on_grace_period, -> { cancelled.where("#{table_name}.ends_at > ?", Time.zone.now) }
|
14
|
-
scope :active, -> { where(ends_at: nil).or(on_grace_period).or(on_trial) }
|
14
|
+
scope :active, -> { where(status: ["trialing", "active"], ends_at: nil).or(on_grace_period).or(on_trial) }
|
15
15
|
scope :incomplete, -> { where(status: :incomplete) }
|
16
16
|
scope :past_due, -> { where(status: :past_due) }
|
17
17
|
scope :with_active_customer, -> { joins(:customer).merge(Customer.active) }
|
@@ -24,6 +24,10 @@ module Pay
|
|
24
24
|
store_accessor :data, :paddle_cancel_url
|
25
25
|
store_accessor :data, :paddle_paused_from
|
26
26
|
store_accessor :data, :stripe_account
|
27
|
+
store_accessor :data, :metered
|
28
|
+
store_accessor :data, :subscription_items
|
29
|
+
store_accessor :data, :pause_behavior
|
30
|
+
store_accessor :data, :pause_resumes_at
|
27
31
|
|
28
32
|
attribute :prorate, :boolean, default: true
|
29
33
|
|
@@ -31,15 +35,10 @@ module Pay
|
|
31
35
|
validates :name, presence: true
|
32
36
|
validates :processor_id, presence: true, uniqueness: {scope: :customer_id, case_sensitive: true}
|
33
37
|
validates :processor_plan, presence: true
|
34
|
-
validates :quantity, presence: true, numericality: {only_integer: true, greater_than_or_equal_to:
|
38
|
+
validates :quantity, presence: true, numericality: {only_integer: true, greater_than_or_equal_to: 0}
|
35
39
|
validates :status, presence: true
|
36
40
|
|
37
|
-
|
38
|
-
:paused?,
|
39
|
-
:pause,
|
40
|
-
:cancel,
|
41
|
-
:cancel_now!,
|
42
|
-
to: :payment_processor
|
41
|
+
delegate_missing_to :payment_processor
|
43
42
|
|
44
43
|
# Helper methods for payment processors
|
45
44
|
%w[braintree stripe paddle fake_processor].each do |processor_name|
|
@@ -50,6 +49,24 @@ module Pay
|
|
50
49
|
scope processor_name, -> { joins(:customer).where(pay_customers: {processor: processor_name}) }
|
51
50
|
end
|
52
51
|
|
52
|
+
def self.with_metered_items
|
53
|
+
case Pay::Adapter.current_adapter
|
54
|
+
when "sqlite3"
|
55
|
+
where("json_extract(data, '$.\"metered\"') = true")
|
56
|
+
# For SQLite 3.38+ we could use the arrows
|
57
|
+
# where("data->'metered' = ?", "true")
|
58
|
+
when "mysql2"
|
59
|
+
where("data->'$.\"metered\"' = true")
|
60
|
+
when "postgresql", "postgis"
|
61
|
+
# Single quotes are important for json keys apparently
|
62
|
+
where("data->>'metered' = 'true'")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def metered_items?
|
67
|
+
!!metered
|
68
|
+
end
|
69
|
+
|
53
70
|
def self.find_by_processor_and_id(processor, processor_id)
|
54
71
|
joins(:customer).find_by(processor_id: processor_id, pay_customers: {processor: processor})
|
55
72
|
end
|
@@ -62,6 +79,10 @@ module Pay
|
|
62
79
|
@payment_processor ||= self.class.pay_processor_for(customer.processor).new(self)
|
63
80
|
end
|
64
81
|
|
82
|
+
def sync!
|
83
|
+
self.class.pay_processor_for(customer.processor).sync(processor_id)
|
84
|
+
end
|
85
|
+
|
65
86
|
def no_prorate
|
66
87
|
self.prorate = false
|
67
88
|
end
|
@@ -138,6 +159,12 @@ module Pay
|
|
138
159
|
end
|
139
160
|
end
|
140
161
|
|
162
|
+
def pause_resumes_at
|
163
|
+
if (resumes_at = super)
|
164
|
+
Time.zone.parse(resumes_at)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
141
168
|
private
|
142
169
|
|
143
170
|
def cancel_if_active
|
@@ -5,13 +5,13 @@ Questions? Please reply to this email.<br/>
|
|
5
5
|
------------------------------------<br/>
|
6
6
|
RECEIPT - SUBSCRIPTION<br/>
|
7
7
|
<br/>
|
8
|
-
Amount: <%= params[:
|
8
|
+
Amount: <%= params[:pay_charge].amount_with_currency %><br/>
|
9
9
|
<br/>
|
10
|
-
Charged to: <%= params[:
|
11
|
-
Transaction ID: <%= params[:
|
12
|
-
Date: <%= params[:
|
13
|
-
<% if params[:
|
14
|
-
<%= params[:
|
10
|
+
Charged to: <%= params[:pay_charge].charged_to %><br/>
|
11
|
+
Transaction ID: <%= params[:pay_charge].id %><br/>
|
12
|
+
Date: <%= params[:pay_charge].created_at %><br/>
|
13
|
+
<% if params[:pay_charge].customer.owner.try(:extra_billing_info?) %>
|
14
|
+
<%= params[:pay_charge].customer.owner.extra_billing_info %><br/>
|
15
15
|
<% end %>
|
16
16
|
<br/>
|
17
17
|
<br/>
|
@@ -6,13 +6,13 @@ Questions? Please reply to this email.<br/>
|
|
6
6
|
------------------------------------<br/>
|
7
7
|
RECEIPT - REFUND<br/>
|
8
8
|
<br/>
|
9
|
-
Amount: <%= params[:
|
9
|
+
Amount: <%= params[:pay_charge].amount_refunded_with_currency %><br/>
|
10
10
|
<br/>
|
11
|
-
Refunded to: <%= params[:
|
12
|
-
Transaction ID: <%= params[:
|
13
|
-
Date: <%= params[:
|
14
|
-
<% if params[:
|
15
|
-
<%= params[:
|
11
|
+
Refunded to: <%= params[:pay_charge].charged_to %><br/>
|
12
|
+
Transaction ID: <%= params[:pay_charge].id %><br/>
|
13
|
+
Date: <%= params[:pay_charge].created_at %><br/>
|
14
|
+
<% if params[:pay_charge].customer.owner.try(:extra_billing_info?) %>
|
15
|
+
<%= params[:pay_charge].customer.owner.extra_billing_info %><br/>
|
16
16
|
<% end %>
|
17
17
|
<br/>
|
18
18
|
<br/>
|
data/config/locales/en.yml
CHANGED
@@ -1,37 +1,44 @@
|
|
1
1
|
en:
|
2
2
|
pay:
|
3
3
|
successful:
|
4
|
-
header: Payment Successful
|
5
|
-
description: This payment was already successfully confirmed.
|
4
|
+
header: "Payment Successful"
|
5
|
+
description: "This payment was already successfully confirmed."
|
6
6
|
cancelled:
|
7
|
-
header: Payment Cancelled
|
8
|
-
description: This payment was cancelled.
|
7
|
+
header: "Payment Cancelled"
|
8
|
+
description: "This payment was cancelled."
|
9
9
|
requires_action:
|
10
|
-
header: Confirm your %{amount} payment
|
11
|
-
description: Extra confirmation is needed to process your payment. Please confirm your payment by filling out your payment details below.
|
12
|
-
full_name: Full name
|
13
|
-
card: Credit or debit card
|
14
|
-
button: Pay %{amount}
|
15
|
-
name_missing: Please provide your name.
|
16
|
-
success: The payment was successful.
|
17
|
-
all_rights_reserved: All rights reserved.
|
18
|
-
back: Go back
|
19
|
-
|
20
|
-
|
21
|
-
account_billed: Account Billed
|
22
|
-
product: Product
|
23
|
-
amount: Amount
|
24
|
-
charged_to: Charged to
|
25
|
-
additional_info: Additional Info
|
26
|
-
refunded: Refunded
|
27
|
-
paid: Paid
|
28
|
-
invoice:
|
10
|
+
header: "Confirm your %{amount} payment"
|
11
|
+
description: "Extra confirmation is needed to process your payment. Please confirm your payment by filling out your payment details below."
|
12
|
+
full_name: "Full name"
|
13
|
+
card: "Credit or debit card"
|
14
|
+
button: "Pay %{amount}"
|
15
|
+
name_missing: "Please provide your name."
|
16
|
+
success: "The payment was successful."
|
17
|
+
all_rights_reserved: "All rights reserved."
|
18
|
+
back: "Go back"
|
19
|
+
refund: "Refund"
|
20
|
+
line_items:
|
29
21
|
amount: "Amount"
|
30
|
-
|
22
|
+
description: "Description"
|
31
23
|
quantity: "Quantity"
|
32
24
|
subtotal: "Subtotal"
|
25
|
+
discount: "Discount"
|
26
|
+
tax: "Tax"
|
33
27
|
total: "Total"
|
34
28
|
unit_price: "Unit Price"
|
29
|
+
percent_discount: "%{name} (%{percent}% off)"
|
30
|
+
amount_discount: "%{name} (%{amount} off)"
|
31
|
+
receipt:
|
32
|
+
date: "Date"
|
33
|
+
number: "Receipt Number"
|
34
|
+
paid: "Paid"
|
35
|
+
payment_method: "Payment Method"
|
36
|
+
amount_paid: "Amount paid"
|
37
|
+
refunded: "Refunded"
|
38
|
+
invoice:
|
39
|
+
number: "Invoice Number"
|
40
|
+
date: "Date"
|
41
|
+
payment_method: "Payment method"
|
35
42
|
errors:
|
36
43
|
action_required: "This payment attempt failed because additional action is required before it can be completed."
|
37
44
|
invalid_payment: "This payment attempt failed because of an invalid payment method."
|
data/config/routes.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Pay::Engine.routes.draw do
|
4
4
|
resources :payments, only: [:show], module: :pay
|
5
|
-
post "webhooks/stripe", to: "pay/webhooks/stripe#create"
|
6
|
-
post "webhooks/braintree", to: "pay/webhooks/braintree#create"
|
7
|
-
post "webhooks/paddle", to: "pay/webhooks/paddle#create"
|
5
|
+
post "webhooks/stripe", to: "pay/webhooks/stripe#create" if Pay::Stripe.enabled?
|
6
|
+
post "webhooks/braintree", to: "pay/webhooks/braintree#create" if Pay::Braintree.enabled?
|
7
|
+
post "webhooks/paddle", to: "pay/webhooks/paddle#create" if Pay::Paddle.enabled?
|
8
8
|
end
|
data/lib/pay/attributes.rb
CHANGED
@@ -8,6 +8,10 @@ module Pay
|
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
10
|
included do
|
11
|
+
cattr_accessor :pay_default_payment_processor
|
12
|
+
cattr_accessor :pay_stripe_customer_attributes
|
13
|
+
cattr_accessor :pay_braintree_customer_attributes
|
14
|
+
|
11
15
|
has_many :pay_customers, class_name: "Pay::Customer", as: :owner, inverse_of: :owner
|
12
16
|
has_many :charges, through: :pay_customers, class_name: "Pay::Charge"
|
13
17
|
has_many :subscriptions, through: :pay_customers, class_name: "Pay::Subscription"
|
@@ -35,6 +39,24 @@ module Pay
|
|
35
39
|
reload_payment_processor
|
36
40
|
end
|
37
41
|
|
42
|
+
def add_payment_processor(processor_name, allow_fake: false, **attributes)
|
43
|
+
raise Pay::Error, "Processor `#{processor_name}` is not allowed" if processor_name.to_s == "fake_processor" && !allow_fake
|
44
|
+
|
45
|
+
pay_customer = pay_customers.active.where(processor: processor_name).first_or_initialize
|
46
|
+
pay_customer.update!(attributes)
|
47
|
+
pay_customer
|
48
|
+
end
|
49
|
+
|
50
|
+
def payment_processor
|
51
|
+
current_processor = super
|
52
|
+
|
53
|
+
if current_processor.blank? && self.class.pay_default_payment_processor.present?
|
54
|
+
set_payment_processor(self.class.pay_default_payment_processor, allow_fake: true)
|
55
|
+
else
|
56
|
+
current_processor
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
38
60
|
def cancel_active_pay_subscriptions!
|
39
61
|
subscriptions.active.each(&:cancel_now!)
|
40
62
|
end
|
@@ -61,12 +83,16 @@ module Pay
|
|
61
83
|
end
|
62
84
|
|
63
85
|
class_methods do
|
64
|
-
def pay_customer
|
86
|
+
def pay_customer(options = {})
|
65
87
|
include Billable::SyncCustomer
|
66
88
|
include CustomerExtension
|
89
|
+
|
90
|
+
self.pay_default_payment_processor = options[:default_payment_processor]
|
91
|
+
self.pay_stripe_customer_attributes = options[:stripe_attributes]
|
92
|
+
self.pay_braintree_customer_attributes = options[:braintree_attributes]
|
67
93
|
end
|
68
94
|
|
69
|
-
def pay_merchant
|
95
|
+
def pay_merchant(options = {})
|
70
96
|
include MerchantExtension
|
71
97
|
end
|
72
98
|
end
|
@@ -8,7 +8,7 @@ module Pay
|
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
10
|
included do
|
11
|
-
after_update :
|
11
|
+
after_update :enqeue_customer_sync_job, if: :pay_should_sync_customer?
|
12
12
|
end
|
13
13
|
|
14
14
|
def pay_should_sync_customer?
|
@@ -17,8 +17,8 @@ module Pay
|
|
17
17
|
|
18
18
|
private
|
19
19
|
|
20
|
-
def
|
21
|
-
if
|
20
|
+
def enqeue_customer_sync_job
|
21
|
+
if pay_should_sync_customer?
|
22
22
|
# Queue job to update each payment processor for this customer
|
23
23
|
pay_customers.pluck(:id).each do |pay_customer_id|
|
24
24
|
CustomerSyncJob.perform_later(pay_customer_id)
|
@@ -15,6 +15,24 @@ module Pay
|
|
15
15
|
@pay_customer = pay_customer
|
16
16
|
end
|
17
17
|
|
18
|
+
# Returns a hash of attributes for the Stripe::Customer object
|
19
|
+
def customer_attributes
|
20
|
+
owner = pay_customer.owner
|
21
|
+
|
22
|
+
attributes = case owner.class.pay_braintree_customer_attributes
|
23
|
+
when Symbol
|
24
|
+
owner.send(owner.class.pay_braintree_customer_attributes, pay_customer)
|
25
|
+
when Proc
|
26
|
+
owner.class.pay_braintree_customer_attributes.call(pay_customer)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Guard against attributes being returned nil
|
30
|
+
attributes ||= {}
|
31
|
+
|
32
|
+
first_name, last_name = customer_name.split(" ", 2)
|
33
|
+
{email: email, first_name: first_name, last_name: last_name}.merge(attributes)
|
34
|
+
end
|
35
|
+
|
18
36
|
# Retrieve the Braintree::Customer object
|
19
37
|
#
|
20
38
|
# - If no processor_id is present, creates a Customer.
|
@@ -30,8 +48,7 @@ module Pay
|
|
30
48
|
|
31
49
|
customer
|
32
50
|
else
|
33
|
-
|
34
|
-
result = gateway.customer.create(email: email, first_name: first_name, last_name: last_name, payment_method_nonce: payment_method_token)
|
51
|
+
result = gateway.customer.create(customer_attributes.merge(payment_method_nonce: payment_method_token))
|
35
52
|
raise Pay::Braintree::Error, result unless result.success?
|
36
53
|
pay_customer.update!(processor_id: result.customer.id)
|
37
54
|
|
@@ -49,10 +66,10 @@ module Pay
|
|
49
66
|
end
|
50
67
|
|
51
68
|
# Syncs name and email to Braintree::Customer
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
gateway.customer.update(processor_id,
|
69
|
+
# You can also pass in other attributes that will be merged into the default attributes
|
70
|
+
def update_customer!(**attributes)
|
71
|
+
customer unless processor_id?
|
72
|
+
gateway.customer.update(processor_id, customer_attributes.merge(attributes))
|
56
73
|
end
|
57
74
|
|
58
75
|
def charge(amount, options = {})
|
@@ -135,10 +152,6 @@ module Pay
|
|
135
152
|
raise Pay::Braintree::Error, e
|
136
153
|
end
|
137
154
|
|
138
|
-
def update_email!
|
139
|
-
gateway.customer.update(processor_id, email: email, first_name: try(:first_name), last_name: try(:last_name))
|
140
|
-
end
|
141
|
-
|
142
155
|
def trial_end_date(subscription)
|
143
156
|
return unless subscription.trial_period
|
144
157
|
# Braintree returns dates without time zones, so we'll assume they're UTC
|
@@ -174,44 +187,44 @@ module Pay
|
|
174
187
|
|
175
188
|
def save_payment_method(payment_method, default:)
|
176
189
|
attributes = case payment_method
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
190
|
+
when ::Braintree::CreditCard, ::Braintree::ApplePayCard, ::Braintree::GooglePayCard, ::Braintree::SamsungPayCard, ::Braintree::VisaCheckoutCard
|
191
|
+
{
|
192
|
+
payment_method_type: :card,
|
193
|
+
brand: payment_method.card_type,
|
194
|
+
last4: payment_method.last_4,
|
195
|
+
exp_month: payment_method.expiration_month,
|
196
|
+
exp_year: payment_method.expiration_year
|
197
|
+
}
|
198
|
+
|
199
|
+
when ::Braintree::PayPalAccount
|
200
|
+
{
|
201
|
+
payment_method_type: :paypal,
|
202
|
+
brand: "PayPal",
|
203
|
+
email: payment_method.email
|
204
|
+
}
|
205
|
+
when ::Braintree::VenmoAccount
|
206
|
+
{
|
207
|
+
payment_method_type: :venmo,
|
208
|
+
brand: "Venmo",
|
209
|
+
username: payment_method.username
|
210
|
+
}
|
211
|
+
when ::Braintree::UsBankAccount
|
212
|
+
{
|
213
|
+
payment_method_type: "us_bank_account",
|
214
|
+
bank: payment_method.bank_name,
|
215
|
+
last4: payment_method.last_4
|
216
|
+
}
|
217
|
+
else
|
218
|
+
{
|
219
|
+
payment_method_type: payment_method.class.name.demodulize.underscore,
|
220
|
+
brand: payment_method.try(:card_type),
|
221
|
+
last4: payment_method.try(:last_4),
|
222
|
+
exp_month: payment_method.try(:expiration_month),
|
223
|
+
exp_year: payment_method.try(:expiration_year),
|
224
|
+
bank: payment_method.try(:bank_name),
|
225
|
+
username: payment_method.try(:username),
|
226
|
+
email: payment_method.try(:email)
|
227
|
+
}
|
215
228
|
end
|
216
229
|
|
217
230
|
pay_payment_method = pay_customer.payment_methods.where(processor_id: payment_method.token).first_or_initialize
|
@@ -27,7 +27,7 @@ module Pay
|
|
27
27
|
gateway.subscription.find(processor_id)
|
28
28
|
end
|
29
29
|
|
30
|
-
def cancel
|
30
|
+
def cancel(**options)
|
31
31
|
subscription = processor_subscription
|
32
32
|
|
33
33
|
if on_trial?
|
@@ -43,9 +43,14 @@ module Pay
|
|
43
43
|
raise Pay::Braintree::Error, e
|
44
44
|
end
|
45
45
|
|
46
|
-
def cancel_now!
|
46
|
+
def cancel_now!(**options)
|
47
47
|
gateway.subscription.cancel(processor_subscription.id)
|
48
|
-
|
48
|
+
ends_at = Time.current
|
49
|
+
pay_subscription.update!(
|
50
|
+
status: :canceled,
|
51
|
+
trial_ends_at: (ends_at if pay_subscription.trial_ends_at?),
|
52
|
+
ends_at: ends_at
|
53
|
+
)
|
49
54
|
rescue ::Braintree::BraintreeError => e
|
50
55
|
raise Pay::Braintree::Error, e
|
51
56
|
end
|
@@ -11,7 +11,12 @@ module Pay
|
|
11
11
|
pay_subscription = Pay::Subscription.find_by_processor_and_id(:braintree, subscription.id)
|
12
12
|
return unless pay_subscription.present?
|
13
13
|
|
14
|
-
|
14
|
+
ends_at = Time.current
|
15
|
+
pay_subscription.update!(
|
16
|
+
status: :canceled,
|
17
|
+
trial_ends_at: (ends_at if pay_subscription.trial_ends_at?),
|
18
|
+
ends_at: ends_at
|
19
|
+
)
|
15
20
|
end
|
16
21
|
end
|
17
22
|
end
|
@@ -14,8 +14,8 @@ module Pay
|
|
14
14
|
pay_customer = pay_subscription.customer
|
15
15
|
pay_charge = Pay::Braintree::Billable.new(pay_customer).save_transaction(subscription.transactions.first)
|
16
16
|
|
17
|
-
if pay_charge && Pay.
|
18
|
-
Pay
|
17
|
+
if pay_charge && Pay.send_email?(:receipt, pay_charge)
|
18
|
+
Pay.mailer.with(pay_customer: pay_customer, pay_charge: pay_charge).receipt.deliver_later
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|