pay 2.6.10 → 2.7.2
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 +93 -54
- data/app/models/pay/application_record.rb +1 -0
- data/app/models/pay/charge.rb +3 -1
- data/app/models/pay/subscription.rb +5 -3
- 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/db/migrate/20210714175351_add_uniqueness_to_pay_models.rb +6 -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 +17 -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 +8 -1
- data/lib/pay/braintree/subscription.rb +6 -0
- data/lib/pay/env.rb +8 -0
- data/lib/pay/fake_processor/subscription.rb +6 -0
- data/lib/pay/merchant.rb +37 -0
- data/lib/pay/paddle/subscription.rb +9 -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 +39 -36
- data/lib/pay/stripe/charge.rb +62 -4
- data/lib/pay/stripe/merchant.rb +66 -0
- data/lib/pay/stripe/subscription.rb +87 -21
- 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/payment_method_attached.rb +17 -0
- data/lib/pay/stripe/webhooks/payment_method_automatically_updated.rb +17 -0
- data/lib/pay/stripe/webhooks/payment_method_detached.rb +17 -0
- 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 +22 -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,24 +3,82 @@ 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, try: 0, retries: 1)
|
9
|
+
# Skip loading the latest charge details from the API if we already have it
|
10
|
+
object ||= ::Stripe::Charge.retrieve(id: charge_id)
|
11
|
+
|
12
|
+
owner = Pay.find_billable(processor: :stripe, processor_id: object.customer)
|
13
|
+
return unless owner
|
14
|
+
|
15
|
+
attrs = {
|
16
|
+
amount: object.amount,
|
17
|
+
amount_refunded: object.amount_refunded,
|
18
|
+
application_fee_amount: object.application_fee_amount,
|
19
|
+
card_exp_month: object.payment_method_details.card.exp_month,
|
20
|
+
card_exp_year: object.payment_method_details.card.exp_year,
|
21
|
+
card_last4: object.payment_method_details.card.last4,
|
22
|
+
card_type: object.payment_method_details.card.brand,
|
23
|
+
created_at: Time.at(object.created),
|
24
|
+
currency: object.currency,
|
25
|
+
stripe_account: owner.stripe_account
|
26
|
+
}
|
27
|
+
|
28
|
+
# Associate charge with subscription if we can
|
29
|
+
if object.invoice
|
30
|
+
invoice = (object.invoice.is_a?(::Stripe::Invoice) ? object.invoice : ::Stripe::Invoice.retrieve(object.invoice))
|
31
|
+
attrs[:subscription] = Pay::Subscription.find_by(processor: :stripe, processor_id: invoice.subscription)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Update or create the charge
|
35
|
+
processor_details = {processor: :stripe, processor_id: object.id}
|
36
|
+
if (pay_charge = owner.charges.find_by(processor_details))
|
37
|
+
pay_charge.with_lock do
|
38
|
+
pay_charge.update!(attrs)
|
39
|
+
end
|
40
|
+
pay_charge
|
41
|
+
else
|
42
|
+
owner.charges.create!(attrs.merge(processor_details))
|
43
|
+
end
|
44
|
+
rescue ActiveRecord::RecordInvalid
|
45
|
+
try += 1
|
46
|
+
if try <= retries
|
47
|
+
sleep 0.1
|
48
|
+
retry
|
49
|
+
else
|
50
|
+
raise
|
51
|
+
end
|
52
|
+
end
|
7
53
|
|
8
54
|
def initialize(pay_charge)
|
9
55
|
@pay_charge = pay_charge
|
10
56
|
end
|
11
57
|
|
12
58
|
def charge
|
13
|
-
::Stripe::Charge.retrieve(id: processor_id, expand: ["customer", "invoice.subscription"])
|
59
|
+
::Stripe::Charge.retrieve({id: processor_id, expand: ["customer", "invoice.subscription"]}, stripe_options)
|
14
60
|
rescue ::Stripe::StripeError => e
|
15
61
|
raise Pay::Stripe::Error, e
|
16
62
|
end
|
17
63
|
|
18
|
-
|
19
|
-
|
64
|
+
# https://stripe.com/docs/api/refunds/create
|
65
|
+
#
|
66
|
+
# refund!
|
67
|
+
# refund!(5_00)
|
68
|
+
# refund!(5_00, refund_application_fee: true)
|
69
|
+
def refund!(amount_to_refund, **options)
|
70
|
+
::Stripe::Refund.create(options.merge(charge: processor_id, amount: amount_to_refund), stripe_options)
|
20
71
|
pay_charge.update(amount_refunded: amount_to_refund)
|
21
72
|
rescue ::Stripe::StripeError => e
|
22
73
|
raise Pay::Stripe::Error, e
|
23
74
|
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# Options for Stripe requests
|
79
|
+
def stripe_options
|
80
|
+
{stripe_account: stripe_account}.compact
|
81
|
+
end
|
24
82
|
end
|
25
83
|
end
|
26
84
|
end
|
@@ -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,33 +16,82 @@ 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, try: 0, retries: 1)
|
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
|
+
attributes[:ends_at] = if object.ended_at
|
41
|
+
# Fully cancelled subscription
|
42
|
+
Time.at(object.ended_at)
|
43
|
+
elsif object.cancel_at
|
44
|
+
# subscription cancelling in the future
|
45
|
+
Time.at(object.cancel_at)
|
46
|
+
elsif object.cancel_at_period_end
|
47
|
+
# Subscriptions cancelling in the future
|
48
|
+
Time.at(object.current_period_end)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Update or create the subscription
|
52
|
+
processor_details = {processor: :stripe, processor_id: object.id}
|
53
|
+
if (pay_subscription = owner.subscriptions.find_by(processor_details))
|
54
|
+
pay_subscription.with_lock do
|
55
|
+
pay_subscription.update!(attributes)
|
56
|
+
end
|
57
|
+
pay_subscription
|
58
|
+
else
|
59
|
+
owner.subscriptions.create!(attributes.merge(processor_details))
|
60
|
+
end
|
61
|
+
rescue ActiveRecord::RecordInvalid
|
62
|
+
try += 1
|
63
|
+
if try <= retries
|
64
|
+
sleep 0.1
|
65
|
+
retry
|
66
|
+
else
|
67
|
+
raise
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
22
71
|
def initialize(pay_subscription)
|
23
72
|
@pay_subscription = pay_subscription
|
24
73
|
end
|
25
74
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
subscription.save
|
75
|
+
def subscription(**options)
|
76
|
+
::Stripe::Subscription.retrieve(options.merge(id: processor_id))
|
77
|
+
end
|
30
78
|
|
31
|
-
|
32
|
-
|
79
|
+
def cancel
|
80
|
+
stripe_sub = ::Stripe::Subscription.update(processor_id, {cancel_at_period_end: true}, stripe_options)
|
81
|
+
pay_subscription.update(ends_at: (on_trial? ? trial_ends_at : Time.at(stripe_sub.current_period_end)))
|
33
82
|
rescue ::Stripe::StripeError => e
|
34
83
|
raise Pay::Stripe::Error, e
|
35
84
|
end
|
36
85
|
|
37
86
|
def cancel_now!
|
38
|
-
|
39
|
-
pay_subscription.update(ends_at: Time.
|
87
|
+
::Stripe::Subscription.delete(processor_id, {}, stripe_options)
|
88
|
+
pay_subscription.update(ends_at: Time.current, status: :canceled)
|
40
89
|
rescue ::Stripe::StripeError => e
|
41
90
|
raise Pay::Stripe::Error, e
|
42
91
|
end
|
43
92
|
|
44
93
|
def change_quantity(quantity)
|
45
|
-
::Stripe::Subscription.update(processor_id, quantity: quantity)
|
94
|
+
::Stripe::Subscription.update(processor_id, {quantity: quantity}, stripe_options)
|
46
95
|
rescue ::Stripe::StripeError => e
|
47
96
|
raise Pay::Stripe::Error, e
|
48
97
|
end
|
@@ -64,26 +113,43 @@ module Pay
|
|
64
113
|
raise StandardError, "You can only resume subscriptions within their grace period."
|
65
114
|
end
|
66
115
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
116
|
+
::Stripe::Subscription.update(
|
117
|
+
processor_id,
|
118
|
+
{
|
119
|
+
plan: processor_plan,
|
120
|
+
trial_end: (on_trial? ? trial_ends_at.to_i : "now"),
|
121
|
+
cancel_at_period_end: false
|
122
|
+
},
|
123
|
+
stripe_options
|
124
|
+
)
|
72
125
|
rescue ::Stripe::StripeError => e
|
73
126
|
raise Pay::Stripe::Error, e
|
74
127
|
end
|
75
128
|
|
76
129
|
def swap(plan)
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
130
|
+
raise ArgumentError, "plan must be a string" unless plan.is_a?(String)
|
131
|
+
|
132
|
+
::Stripe::Subscription.update(
|
133
|
+
processor_id,
|
134
|
+
{
|
135
|
+
cancel_at_period_end: false,
|
136
|
+
plan: plan,
|
137
|
+
proration_behavior: (prorate ? "create_prorations" : "none"),
|
138
|
+
trial_end: (on_trial? ? trial_ends_at.to_i : "now"),
|
139
|
+
quantity: quantity
|
140
|
+
},
|
141
|
+
stripe_options
|
142
|
+
)
|
84
143
|
rescue ::Stripe::StripeError => e
|
85
144
|
raise Pay::Stripe::Error, e
|
86
145
|
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
# Options for Stripe requests
|
150
|
+
def stripe_options
|
151
|
+
{stripe_account: stripe_account}.compact
|
152
|
+
end
|
87
153
|
end
|
88
154
|
end
|
89
155
|
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
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Pay
|
2
|
+
module Stripe
|
3
|
+
module Webhooks
|
4
|
+
class PaymentMethodAttached
|
5
|
+
def call(event)
|
6
|
+
object = event.data.object
|
7
|
+
pay_customer = Pay::Customer.find_by(processor: :stripe, processor_id: object.customer)
|
8
|
+
|
9
|
+
# Couldn't find user, we can skip
|
10
|
+
return unless pay_customer.present?
|
11
|
+
|
12
|
+
Pay::Stripe::Billable.new(pay_customer).sync_payment_method(payment_method_id: object.id)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Pay
|
2
|
+
module Stripe
|
3
|
+
module Webhooks
|
4
|
+
class PaymentMethodAutomaticallyUpdated
|
5
|
+
def call(event)
|
6
|
+
object = event.data.object
|
7
|
+
pay_customer = Pay::Customer.find_by(processor: :stripe, processor_id: object.customer)
|
8
|
+
|
9
|
+
# Couldn't find user, we can skip
|
10
|
+
return unless pay_customer.present?
|
11
|
+
|
12
|
+
Pay::Stripe::Billable.new(pay_customer).sync_payment_method(payment_method_id: object.id)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Pay
|
2
|
+
module Stripe
|
3
|
+
module Webhooks
|
4
|
+
class PaymentMethodDetached
|
5
|
+
def call(event)
|
6
|
+
object = event.data.object
|
7
|
+
pay_customer = Pay::Customer.find_by(processor: :stripe, processor_id: object.customer)
|
8
|
+
|
9
|
+
# Couldn't find user, we can skip
|
10
|
+
return unless pay_customer.present?
|
11
|
+
|
12
|
+
Pay::Stripe::Billable.new(pay_customer).sync_payment_method(payment_method_id: object.id)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|