pay 2.6.8 → 2.7.1
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.
Potentially problematic release.
This version of pay might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +73 -50
- data/app/models/pay.rb +5 -0
- data/app/models/pay/charge.rb +2 -0
- data/app/models/pay/subscription.rb +9 -2
- data/db/migrate/20200603134434_add_data_to_pay_models.rb +2 -18
- data/db/migrate/20210309004259_add_data_to_pay_billable.rb +10 -0
- data/db/migrate/20210406215234_add_currency_to_pay_charges.rb +5 -0
- data/db/migrate/20210406215506_add_application_fee_to_pay_models.rb +7 -0
- data/lib/generators/active_record/billable_generator.rb +44 -0
- data/lib/generators/active_record/merchant_generator.rb +44 -0
- data/lib/generators/active_record/templates/billable_migration.rb +16 -0
- data/lib/generators/active_record/templates/merchant_migration.rb +12 -0
- data/lib/generators/pay/{pay_generator.rb → billable_generator.rb} +2 -3
- data/lib/generators/pay/merchant_generator.rb +17 -0
- data/lib/generators/pay/orm_helpers.rb +10 -6
- data/lib/pay.rb +22 -0
- data/lib/pay/adapter.rb +22 -0
- data/lib/pay/billable.rb +4 -0
- data/lib/pay/braintree/billable.rb +11 -2
- data/lib/pay/braintree/subscription.rb +4 -0
- data/lib/pay/env.rb +8 -0
- data/lib/pay/fake_processor/subscription.rb +4 -0
- data/lib/pay/merchant.rb +37 -0
- data/lib/pay/paddle/subscription.rb +7 -0
- data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +3 -1
- data/lib/pay/stripe.rb +11 -0
- data/lib/pay/stripe/billable.rb +38 -47
- data/lib/pay/stripe/charge.rb +38 -4
- data/lib/pay/stripe/merchant.rb +66 -0
- data/lib/pay/stripe/subscription.rb +64 -20
- data/lib/pay/stripe/webhooks/account_updated.rb +17 -0
- data/lib/pay/stripe/webhooks/charge_refunded.rb +2 -7
- data/lib/pay/stripe/webhooks/charge_succeeded.rb +2 -8
- data/lib/pay/stripe/webhooks/checkout_session_async_payment_succeeded.rb +13 -0
- data/lib/pay/stripe/webhooks/checkout_session_completed.rb +13 -0
- data/lib/pay/stripe/webhooks/payment_intent_succeeded.rb +2 -8
- data/lib/pay/stripe/webhooks/subscription_created.rb +1 -35
- data/lib/pay/stripe/webhooks/subscription_deleted.rb +1 -9
- data/lib/pay/stripe/webhooks/subscription_renewing.rb +4 -6
- data/lib/pay/stripe/webhooks/subscription_updated.rb +1 -28
- data/lib/pay/version.rb +1 -1
- metadata +19 -6
- data/lib/generators/active_record/pay_generator.rb +0 -58
- data/lib/generators/active_record/templates/migration.rb +0 -9
data/lib/pay/stripe/charge.rb
CHANGED
@@ -3,20 +3,54 @@ module Pay
|
|
3
3
|
class Charge
|
4
4
|
attr_reader :pay_charge
|
5
5
|
|
6
|
-
delegate :processor_id, :owner, to: :pay_charge
|
6
|
+
delegate :processor_id, :owner, :stripe_account, to: :pay_charge
|
7
|
+
|
8
|
+
def self.sync(charge_id, object: nil)
|
9
|
+
object ||= ::Stripe::Charge.retrieve(id: charge_id)
|
10
|
+
owner = Pay.find_billable(processor: :stripe, processor_id: object.customer)
|
11
|
+
return unless owner
|
12
|
+
|
13
|
+
attrs = {
|
14
|
+
amount: object.amount,
|
15
|
+
amount_refunded: object.amount_refunded,
|
16
|
+
application_fee_amount: object.application_fee_amount,
|
17
|
+
card_exp_month: object.payment_method_details.card.exp_month,
|
18
|
+
card_exp_year: object.payment_method_details.card.exp_year,
|
19
|
+
card_last4: object.payment_method_details.card.last4,
|
20
|
+
card_type: object.payment_method_details.card.brand,
|
21
|
+
created_at: Time.at(object.created),
|
22
|
+
currency: object.currency,
|
23
|
+
stripe_account: owner.stripe_account
|
24
|
+
}
|
25
|
+
|
26
|
+
# Associate charge with subscription if we can
|
27
|
+
if object.invoice
|
28
|
+
invoice = (object.invoice.is_a?(::Stripe::Invoice) ? object.invoice : ::Stripe::Invoice.retrieve(object.invoice))
|
29
|
+
attrs[:subscription] = Pay::Subscription.find_by(processor: :stripe, processor_id: invoice.subscription)
|
30
|
+
end
|
31
|
+
|
32
|
+
pay_charge = owner.charges.find_or_initialize_by(processor: :stripe, processor_id: object.id)
|
33
|
+
pay_charge.update(attrs)
|
34
|
+
pay_charge
|
35
|
+
end
|
7
36
|
|
8
37
|
def initialize(pay_charge)
|
9
38
|
@pay_charge = pay_charge
|
10
39
|
end
|
11
40
|
|
12
41
|
def charge
|
13
|
-
::Stripe::Charge.retrieve(processor_id)
|
42
|
+
::Stripe::Charge.retrieve({id: processor_id, expand: ["customer", "invoice.subscription"]}, {stripe_account: stripe_account})
|
14
43
|
rescue ::Stripe::StripeError => e
|
15
44
|
raise Pay::Stripe::Error, e
|
16
45
|
end
|
17
46
|
|
18
|
-
|
19
|
-
|
47
|
+
# https://stripe.com/docs/api/refunds/create
|
48
|
+
#
|
49
|
+
# refund!
|
50
|
+
# refund!(5_00)
|
51
|
+
# refund!(5_00, refund_application_fee: true)
|
52
|
+
def refund!(amount_to_refund, **options)
|
53
|
+
::Stripe::Refund.create(options.merge(charge: processor_id, amount: amount_to_refund), {stripe_account: stripe_account})
|
20
54
|
pay_charge.update(amount_refunded: amount_to_refund)
|
21
55
|
rescue ::Stripe::StripeError => e
|
22
56
|
raise Pay::Stripe::Error, e
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Pay
|
2
|
+
module Stripe
|
3
|
+
class Merchant
|
4
|
+
attr_reader :merchant
|
5
|
+
|
6
|
+
delegate :stripe_connect_account_id,
|
7
|
+
to: :merchant
|
8
|
+
|
9
|
+
def initialize(merchant)
|
10
|
+
@merchant = merchant
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_account(**options)
|
14
|
+
defaults = {
|
15
|
+
type: "express",
|
16
|
+
capabilities: {
|
17
|
+
card_payments: {requested: true},
|
18
|
+
transfers: {requested: true}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
stripe_account = ::Stripe::Account.create(defaults.merge(options))
|
23
|
+
merchant.update(stripe_connect_account_id: stripe_account.id)
|
24
|
+
stripe_account
|
25
|
+
rescue ::Stripe::StripeError => e
|
26
|
+
raise Pay::Stripe::Error, e
|
27
|
+
end
|
28
|
+
|
29
|
+
def account
|
30
|
+
::Stripe::Account.retrieve(stripe_connect_account_id)
|
31
|
+
rescue ::Stripe::StripeError => e
|
32
|
+
raise Pay::Stripe::Error, e
|
33
|
+
end
|
34
|
+
|
35
|
+
def account_link(refresh_url:, return_url:, type: "account_onboarding", **options)
|
36
|
+
::Stripe::AccountLink.create({
|
37
|
+
account: stripe_connect_account_id,
|
38
|
+
refresh_url: refresh_url,
|
39
|
+
return_url: return_url,
|
40
|
+
type: type
|
41
|
+
})
|
42
|
+
rescue ::Stripe::StripeError => e
|
43
|
+
raise Pay::Stripe::Error, e
|
44
|
+
end
|
45
|
+
|
46
|
+
# A single-use login link for Express accounts to access their Stripe dashboard
|
47
|
+
def login_link(**options)
|
48
|
+
::Stripe::Account.create_login_link(stripe_connect_account_id)
|
49
|
+
rescue ::Stripe::StripeError => e
|
50
|
+
raise Pay::Stripe::Error, e
|
51
|
+
end
|
52
|
+
|
53
|
+
# Transfer money from the platform to this connected account
|
54
|
+
# https://stripe.com/docs/connect/charges-transfers#transfer-availability
|
55
|
+
def transfer(amount:, currency: "usd", **options)
|
56
|
+
::Stripe::Transfer.create({
|
57
|
+
amount: amount,
|
58
|
+
currency: currency,
|
59
|
+
destination: stripe_connect_account_id
|
60
|
+
}.merge(options))
|
61
|
+
rescue ::Stripe::StripeError => e
|
62
|
+
raise Pay::Stripe::Error, e
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -16,27 +16,63 @@ module Pay
|
|
16
16
|
:prorate?,
|
17
17
|
:quantity,
|
18
18
|
:quantity?,
|
19
|
+
:stripe_account,
|
19
20
|
:trial_ends_at,
|
20
21
|
to: :pay_subscription
|
21
22
|
|
23
|
+
def self.sync(subscription_id, object: nil, name: Pay.default_product_name)
|
24
|
+
# Skip loading the latest subscription details from the API if we already have it
|
25
|
+
object ||= ::Stripe::Subscription.retrieve(id: subscription_id, expand: ["pending_setup_intent", "latest_invoice.payment_intent"])
|
26
|
+
|
27
|
+
owner = Pay.find_billable(processor: :stripe, processor_id: object.customer)
|
28
|
+
return unless owner
|
29
|
+
|
30
|
+
attributes = {
|
31
|
+
application_fee_percent: object.application_fee_percent,
|
32
|
+
processor_plan: object.plan.id,
|
33
|
+
quantity: object.quantity,
|
34
|
+
name: name,
|
35
|
+
status: object.status,
|
36
|
+
stripe_account: owner.stripe_account,
|
37
|
+
trial_ends_at: (object.trial_end ? Time.at(object.trial_end) : nil)
|
38
|
+
}
|
39
|
+
|
40
|
+
# Subscriptions cancelling in the future
|
41
|
+
attributes[:ends_at] = Time.at(object.current_period_end) if object.cancel_at_period_end
|
42
|
+
|
43
|
+
# Fully cancelled subscription
|
44
|
+
attributes[:ends_at] = Time.at(object.ended_at) if object.ended_at
|
45
|
+
|
46
|
+
# Update or create the subscription
|
47
|
+
pay_subscription = owner.subscriptions.find_or_initialize_by(processor: :stripe, processor_id: object.id)
|
48
|
+
pay_subscription.update(attributes)
|
49
|
+
pay_subscription
|
50
|
+
end
|
51
|
+
|
22
52
|
def initialize(pay_subscription)
|
23
53
|
@pay_subscription = pay_subscription
|
24
54
|
end
|
25
55
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
subscription.save
|
56
|
+
def subscription(**options)
|
57
|
+
::Stripe::Subscription.retrieve(options.merge(id: processor_id))
|
58
|
+
end
|
30
59
|
|
31
|
-
|
32
|
-
|
60
|
+
def cancel
|
61
|
+
stripe_sub = ::Stripe::Subscription.update(processor_id, {cancel_at_period_end: true}, {stripe_account: stripe_account})
|
62
|
+
pay_subscription.update(ends_at: (on_trial? ? trial_ends_at : Time.at(stripe_sub.current_period_end)))
|
33
63
|
rescue ::Stripe::StripeError => e
|
34
64
|
raise Pay::Stripe::Error, e
|
35
65
|
end
|
36
66
|
|
37
67
|
def cancel_now!
|
38
|
-
|
39
|
-
pay_subscription.update(ends_at: Time.
|
68
|
+
::Stripe::Subscription.delete(processor_id, {stripe_account: stripe_account})
|
69
|
+
pay_subscription.update(ends_at: Time.current, status: :canceled)
|
70
|
+
rescue ::Stripe::StripeError => e
|
71
|
+
raise Pay::Stripe::Error, e
|
72
|
+
end
|
73
|
+
|
74
|
+
def change_quantity(quantity)
|
75
|
+
::Stripe::Subscription.update(processor_id, quantity: quantity)
|
40
76
|
rescue ::Stripe::StripeError => e
|
41
77
|
raise Pay::Stripe::Error, e
|
42
78
|
end
|
@@ -58,23 +94,31 @@ module Pay
|
|
58
94
|
raise StandardError, "You can only resume subscriptions within their grace period."
|
59
95
|
end
|
60
96
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
97
|
+
::Stripe::Subscription.update(
|
98
|
+
processor_id,
|
99
|
+
{
|
100
|
+
plan: processor_plan,
|
101
|
+
trial_end: (on_trial? ? trial_ends_at.to_i : "now"),
|
102
|
+
cancel_at_period_end: false
|
103
|
+
},
|
104
|
+
{stripe_account: stripe_account}
|
105
|
+
)
|
66
106
|
rescue ::Stripe::StripeError => e
|
67
107
|
raise Pay::Stripe::Error, e
|
68
108
|
end
|
69
109
|
|
70
110
|
def swap(plan)
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
111
|
+
::Stripe::Subscription.update(
|
112
|
+
processor_id,
|
113
|
+
{
|
114
|
+
cancel_at_period_end: false,
|
115
|
+
plan: plan,
|
116
|
+
proration_behavior: (prorate ? "create_prorations" : "none"),
|
117
|
+
trial_end: (on_trial? ? trial_ends_at.to_i : "now"),
|
118
|
+
quantity: quantity
|
119
|
+
},
|
120
|
+
{stripe_account: stripe_account}
|
121
|
+
)
|
78
122
|
rescue ::Stripe::StripeError => e
|
79
123
|
raise Pay::Stripe::Error, e
|
80
124
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Pay
|
2
|
+
module Stripe
|
3
|
+
module Webhooks
|
4
|
+
class AccountUpdated
|
5
|
+
def call(event)
|
6
|
+
object = event.data.object
|
7
|
+
|
8
|
+
merchant = Pay.find_merchant("stripe_connect_account_id", object.id)
|
9
|
+
|
10
|
+
return unless merchant.present?
|
11
|
+
|
12
|
+
merchant.update(onboarding_complete: object.charges_enabled)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -3,13 +3,8 @@ module Pay
|
|
3
3
|
module Webhooks
|
4
4
|
class ChargeRefunded
|
5
5
|
def call(event)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
return unless charge.present?
|
10
|
-
|
11
|
-
charge.update(amount_refunded: object.amount_refunded)
|
12
|
-
notify_user(charge.owner, charge)
|
6
|
+
pay_charge = Pay::Stripe::Charge.sync(event.data.object.id)
|
7
|
+
notify_user(pay_charge.owner, pay_charge) if pay_charge
|
13
8
|
end
|
14
9
|
|
15
10
|
def notify_user(billable, charge)
|
@@ -3,14 +3,8 @@ module Pay
|
|
3
3
|
module Webhooks
|
4
4
|
class ChargeSucceeded
|
5
5
|
def call(event)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
return unless billable.present?
|
10
|
-
return if billable.charges.where(processor_id: object.id).any?
|
11
|
-
|
12
|
-
charge = Pay::Stripe::Billable.new(billable).save_pay_charge(object)
|
13
|
-
notify_user(billable, charge)
|
6
|
+
pay_charge = Pay::Stripe::Charge.sync(event.data.object.id)
|
7
|
+
notify_user(pay_charge.owner, pay_charge) if pay_charge
|
14
8
|
end
|
15
9
|
|
16
10
|
def notify_user(billable, charge)
|
@@ -4,15 +4,9 @@ module Pay
|
|
4
4
|
class PaymentIntentSucceeded
|
5
5
|
def call(event)
|
6
6
|
object = event.data.object
|
7
|
-
billable = Pay.find_billable(processor: :stripe, processor_id: object.customer)
|
8
|
-
|
9
|
-
return unless billable.present?
|
10
|
-
|
11
7
|
object.charges.data.each do |charge|
|
12
|
-
|
13
|
-
|
14
|
-
charge = Pay::Stripe::Billable.new(billable).save_pay_charge(charge)
|
15
|
-
notify_user(billable, charge)
|
8
|
+
pay_charge = Pay::Stripe::Charge.sync(charge.id)
|
9
|
+
notify_user(pay_charge.owner, pay_charge) if pay_charge
|
16
10
|
end
|
17
11
|
end
|
18
12
|
|
@@ -3,41 +3,7 @@ module Pay
|
|
3
3
|
module Webhooks
|
4
4
|
class SubscriptionCreated
|
5
5
|
def call(event)
|
6
|
-
|
7
|
-
|
8
|
-
# We may already have the subscription in the database, so we can update that record
|
9
|
-
subscription = Pay.subscription_model.find_by(processor: :stripe, processor_id: object.id)
|
10
|
-
|
11
|
-
# Create the subscription in the database if we don't have it already
|
12
|
-
if subscription.nil?
|
13
|
-
# The customer should already be in the database
|
14
|
-
owner = Pay.find_billable(processor: :stripe, processor_id: object.customer)
|
15
|
-
|
16
|
-
if owner.nil?
|
17
|
-
Rails.logger.error("[Pay] Unable to find Pay::Billable with processor: :stripe and processor_id: '#{object.customer}'. Searched these models: #{Pay.billable_models.join(", ")}")
|
18
|
-
return
|
19
|
-
end
|
20
|
-
|
21
|
-
subscription = Pay.subscription_model.new(name: Pay.default_product_name, owner: owner, processor: :stripe, processor_id: object.id)
|
22
|
-
end
|
23
|
-
|
24
|
-
subscription.quantity = object.quantity
|
25
|
-
subscription.status = object.status
|
26
|
-
subscription.processor_plan = object.plan.id
|
27
|
-
subscription.trial_ends_at = Time.at(object.trial_end) if object.trial_end.present?
|
28
|
-
|
29
|
-
# If user was on trial, their subscription ends at the end of the trial
|
30
|
-
subscription.ends_at = if object.cancel_at_period_end && subscription.on_trial?
|
31
|
-
subscription.trial_ends_at
|
32
|
-
|
33
|
-
# User wasn't on trial, so subscription ends at period end
|
34
|
-
elsif object.cancel_at_period_end
|
35
|
-
Time.at(object.current_period_end)
|
36
|
-
|
37
|
-
# Subscription isn't marked to cancel at period end
|
38
|
-
end
|
39
|
-
|
40
|
-
subscription.save!
|
6
|
+
Pay::Stripe::Subscription.sync(event.data.object.id)
|
41
7
|
end
|
42
8
|
end
|
43
9
|
end
|
@@ -3,15 +3,7 @@ module Pay
|
|
3
3
|
module Webhooks
|
4
4
|
class SubscriptionDeleted
|
5
5
|
def call(event)
|
6
|
-
|
7
|
-
subscription = Pay.subscription_model.find_by(processor: :stripe, processor_id: object.id)
|
8
|
-
|
9
|
-
# We couldn't find the subscription for some reason, maybe it's from another service
|
10
|
-
return if subscription.nil?
|
11
|
-
|
12
|
-
# User canceled subscriptions have an ends_at
|
13
|
-
# Automatically canceled subscriptions need this value set
|
14
|
-
subscription.update!(ends_at: Time.at(object.ended_at)) if subscription.ends_at.blank? && object.ended_at.present?
|
6
|
+
Pay::Stripe::Subscription.sync(event.data.object.id)
|
15
7
|
end
|
16
8
|
end
|
17
9
|
end
|
@@ -5,12 +5,10 @@ module Pay
|
|
5
5
|
def call(event)
|
6
6
|
# Event is of type "invoice" see:
|
7
7
|
# https://stripe.com/docs/api/invoices/object
|
8
|
-
subscription = Pay.subscription_model.find_by(
|
9
|
-
|
10
|
-
|
11
|
-
)
|
12
|
-
date = Time.zone.at(event.data.object.next_payment_attempt)
|
13
|
-
notify_user(subscription.owner, subscription, date) if subscription.present?
|
8
|
+
subscription = Pay.subscription_model.find_by(processor: :stripe, processor_id: event.data.object.subscription)
|
9
|
+
return unless subscription
|
10
|
+
|
11
|
+
notify_user(subscription.owner, subscription, Time.zone.at(event.data.object.next_payment_attempt))
|
14
12
|
end
|
15
13
|
|
16
14
|
def notify_user(billable, subscription, date)
|
@@ -3,34 +3,7 @@ module Pay
|
|
3
3
|
module Webhooks
|
4
4
|
class SubscriptionUpdated
|
5
5
|
def call(event)
|
6
|
-
|
7
|
-
subscription = Pay.subscription_model.find_by(processor: :stripe, processor_id: object.id)
|
8
|
-
|
9
|
-
return if subscription.nil?
|
10
|
-
|
11
|
-
# Delete any subscription attempts that have expired
|
12
|
-
if object.status == "incomplete_expired"
|
13
|
-
subscription.destroy
|
14
|
-
return
|
15
|
-
end
|
16
|
-
|
17
|
-
subscription.status = object.status
|
18
|
-
subscription.quantity = object.quantity
|
19
|
-
subscription.processor_plan = object.plan.id
|
20
|
-
subscription.trial_ends_at = Time.at(object.trial_end) if object.trial_end.present?
|
21
|
-
|
22
|
-
# If user was on trial, their subscription ends at the end of the trial
|
23
|
-
subscription.ends_at = if object.cancel_at_period_end && subscription.on_trial?
|
24
|
-
subscription.trial_ends_at
|
25
|
-
|
26
|
-
# User wasn't on trial, so subscription ends at period end
|
27
|
-
elsif object.cancel_at_period_end
|
28
|
-
Time.at(object.current_period_end)
|
29
|
-
|
30
|
-
# Subscription isn't marked to cancel at period end
|
31
|
-
end
|
32
|
-
|
33
|
-
subscription.save!
|
6
|
+
Pay::Stripe::Subscription.sync(event.data.object.id)
|
34
7
|
end
|
35
8
|
end
|
36
9
|
end
|