core_merchant 0.10.3 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +30 -0
- data/lib/core_merchant/subscription.rb +4 -28
- data/lib/core_merchant/subscription_manager.rb +57 -0
- data/lib/core_merchant/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 22e2c74252e4e4b93cfcda94ce010339441225e0214b5feb3ae595adc539e421
|
4
|
+
data.tar.gz: a7bfb337cb13efebae8aba09f9e7eb0df7186b815fd489faca0d25709a195cc8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 891a264116088a5fbbf92b0878b7d704a17d8f92052332d498816256ee2414e0b55c765d8994ca4c4731aa8cccf99926099ef00006022c7c202fced43e3e1932
|
7
|
+
data.tar.gz: 6fe89dc891685e3f124b75c01f10ead88effdcbfbdf6d1a6d12d2894f0eaff8c92c74328e9ef69367ce3b169b9f7bff0e46f9b28a41e0ee415a338c053c30ff4
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -57,6 +57,36 @@ $ gem install core_merchant
|
|
57
57
|
```
|
58
58
|
|
59
59
|
# Usage
|
60
|
+
The steps below will guide you through setting up CoreMerchant in your Rails application. After completing these steps, you will be able to create subscription plans, manage subscriptions, and handle subscription events.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
# Create a subscription plan
|
64
|
+
plan = CoreMerchant::SubscriptionPlan.create(name_key: 'basic', price_cents: 9_99, duration: '1m')
|
65
|
+
|
66
|
+
# Subscribe a customer to the plan
|
67
|
+
customer = current_user
|
68
|
+
subscription = CoreMerchant::Subscription.create(customer: customer, plan: plan)
|
69
|
+
|
70
|
+
# Start the subscription
|
71
|
+
CoreMerchant.subscription_manager.start_subscription(subscription)
|
72
|
+
|
73
|
+
# Use a listener to handle subscription events
|
74
|
+
class MySubscriptionListener
|
75
|
+
include CoreMerchant::SubscriptionListener
|
76
|
+
|
77
|
+
def on_subscription_started(subscription)
|
78
|
+
puts "Subscription started for #{subscription.customer.email}, subscribed to #{subscription.plan.name}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def on_subscription_due_for_renewal(subscription)
|
82
|
+
puts "Subscription due for renewal for #{subscription.customer.email}, renewing until #{subscription.current_period_end_date}"
|
83
|
+
|
84
|
+
# After payment is processed
|
85
|
+
CoreMerchant.subscription_manager.payment_successful_for_renewal(subscription)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
60
90
|
## Initialization
|
61
91
|
Run the generator to create the initializer file and the migrations:
|
62
92
|
```
|
@@ -51,7 +51,7 @@ module CoreMerchant
|
|
51
51
|
# subscription.start
|
52
52
|
# subscription.cancel(reason: "Too expensive", at_period_end: true)
|
53
53
|
# ```
|
54
|
-
class Subscription < ActiveRecord::Base
|
54
|
+
class Subscription < ActiveRecord::Base
|
55
55
|
include CoreMerchant::Concerns::SubscriptionStateMachine
|
56
56
|
include CoreMerchant::Concerns::SubscriptionNotifications
|
57
57
|
include CoreMerchant::Concerns::SubscriptionEventAssociation
|
@@ -89,18 +89,7 @@ module CoreMerchant
|
|
89
89
|
# Starts the subscription.
|
90
90
|
# Sets the current period start and end dates based on the plan's duration.
|
91
91
|
def start
|
92
|
-
|
93
|
-
new_period_end = new_period_start + subscription_plan.duration_in_date
|
94
|
-
|
95
|
-
transaction do
|
96
|
-
transition_to_active!
|
97
|
-
update!(
|
98
|
-
current_period_start: new_period_start.to_date,
|
99
|
-
current_period_end: new_period_end.to_date
|
100
|
-
)
|
101
|
-
end
|
102
|
-
|
103
|
-
notify_subscription_manager(:started)
|
92
|
+
CoreMerchant.subscription_manager.start_subscription(self)
|
104
93
|
end
|
105
94
|
|
106
95
|
# Cancels the subscription.
|
@@ -110,26 +99,13 @@ module CoreMerchant
|
|
110
99
|
# Otherwise, the subscription will be canceled immediately.
|
111
100
|
# Default is `true`.
|
112
101
|
def cancel(reason:, at_period_end: true)
|
113
|
-
|
114
|
-
if at_period_end
|
115
|
-
transition_to_pending_cancellation!
|
116
|
-
else
|
117
|
-
transition_to_canceled!
|
118
|
-
end
|
119
|
-
update!(
|
120
|
-
canceled_at: at_period_end ? current_period_end : Time.current,
|
121
|
-
cancellation_reason: reason
|
122
|
-
)
|
123
|
-
end
|
124
|
-
|
125
|
-
notify_subscription_manager(:canceled, reason: reason, immediate: !at_period_end)
|
126
|
-
cancellation_events.create!(reason: reason, at_period_end: at_period_end)
|
102
|
+
CoreMerchant.subscription_manager.cancel_subscription(self, reason: reason, at_period_end: at_period_end)
|
127
103
|
end
|
128
104
|
|
129
105
|
# Starts a new period for the subscription.
|
130
106
|
# This is called by SubscriptionManager when a subscription renewal is successful.
|
131
107
|
def start_new_period
|
132
|
-
new_period_start = current_period_end
|
108
|
+
new_period_start = current_period_end || start_date
|
133
109
|
new_period_end = new_period_start + subscription_plan.duration_in_date
|
134
110
|
|
135
111
|
update!(
|
@@ -40,41 +40,95 @@ module CoreMerchant
|
|
40
40
|
@listeners = []
|
41
41
|
end
|
42
42
|
|
43
|
+
# Checks all subscriptions for renewals and cancellations.
|
44
|
+
# Call this method periodically (probably daily) to check for subscriptions that need attention.
|
45
|
+
# This method will notify listeners when subscriptions are due for renewal or cancellation.
|
43
46
|
def check_subscriptions
|
44
47
|
check_renewals
|
45
48
|
check_cancellations
|
46
49
|
end
|
47
50
|
|
51
|
+
# Adds a listener to the list of listeners.
|
52
|
+
# You probably don't need to call this method directly. Instead, set the listener in the configuration.
|
53
|
+
# config.subscription_listener_class = "MySubscriptionListener"
|
48
54
|
def add_listener(listener)
|
49
55
|
@listeners << listener
|
50
56
|
end
|
51
57
|
|
58
|
+
# Checks all subscriptions for renewals. Called by `check_subscriptions`.
|
52
59
|
def check_renewals
|
53
60
|
Subscription.find_each do |subscription|
|
54
61
|
process_for_renewal(subscription) if subscription.due_for_renewal?
|
55
62
|
end
|
56
63
|
end
|
57
64
|
|
65
|
+
# Starts the subscription.
|
66
|
+
# Sets the current period start and end dates based on the plan's duration.
|
67
|
+
def start_subscription(subscription)
|
68
|
+
subscription.transaction do
|
69
|
+
subscription.start_new_period
|
70
|
+
subscription.transition_to_active!
|
71
|
+
end
|
72
|
+
|
73
|
+
notify(subscription, :started)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Cancels the subscription.
|
77
|
+
# Parameters:
|
78
|
+
# - `reason`: Reason for cancellation
|
79
|
+
# - `at_period_end`: If true, the subscription will be canceled at the end of the current period.
|
80
|
+
# Otherwise, the subscription will be canceled immediately.
|
81
|
+
# Default is `true`.
|
82
|
+
def cancel_subscription(subscription, reason:, at_period_end: true)
|
83
|
+
subscription.transaction do
|
84
|
+
if at_period_end
|
85
|
+
subscription.transition_to_pending_cancellation!
|
86
|
+
else
|
87
|
+
subscription.transition_to_canceled!
|
88
|
+
end
|
89
|
+
subscription.update!(
|
90
|
+
canceled_at: at_period_end ? subscription.current_period_end : Time.current,
|
91
|
+
cancellation_reason: reason
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
notify(subscription, :canceled, reason: reason, immediate: !at_period_end)
|
96
|
+
subscription.cancellation_events.create!(reason: reason, at_period_end: at_period_end)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Starts the subscription renewal process.
|
100
|
+
# This method is called when a subscription is due for renewal. It will in turn call notify the listener.
|
101
|
+
# When you receive a notification that a subscription is due for renewal, you should either call
|
102
|
+
# `no_payment_needed_for_renewal`, `processing_payment_for_renewal`, `payment_successful_for_renewal`, or
|
103
|
+
# `payment_failed_for_renewal` to continue the renewal process.
|
58
104
|
def process_for_renewal(subscription)
|
59
105
|
return unless subscription.transition_to_processing_renewal
|
60
106
|
|
61
107
|
notify(subscription, :due_for_renewal)
|
62
108
|
end
|
63
109
|
|
110
|
+
# Call this method when a subscription is renewed without payment.
|
111
|
+
# It will renew the subscription and notify listeners.
|
64
112
|
def no_payment_needed_for_renewal(subscription)
|
65
113
|
renew_subscription(subscription)
|
66
114
|
end
|
67
115
|
|
116
|
+
# Call this method when payment is being processed for a renewal. This needs to be followed by either
|
117
|
+
# `payment_successful_for_renewal` or `payment_failed_for_renewal`.
|
68
118
|
def processing_payment_for_renewal(subscription)
|
69
119
|
return unless subscription.transition_to_processing_payment
|
70
120
|
|
71
121
|
notify(subscription, :renewal_payment_processing)
|
72
122
|
end
|
73
123
|
|
124
|
+
# Call this method when payment was successful for a renewal.
|
125
|
+
# It will renew the subscription and notify listeners.
|
74
126
|
def payment_successful_for_renewal(subscription)
|
75
127
|
renew_subscription(subscription)
|
76
128
|
end
|
77
129
|
|
130
|
+
# Call this method when payment failed for a renewal.
|
131
|
+
# It will transition the subscription to past due when in grace period, or expired if not.
|
78
132
|
def payment_failed_for_renewal(subscription)
|
79
133
|
is_in_grace_period = subscription.in_grace_period?
|
80
134
|
if is_in_grace_period
|
@@ -86,12 +140,15 @@ module CoreMerchant
|
|
86
140
|
end
|
87
141
|
end
|
88
142
|
|
143
|
+
# Checks all subscriptions for cancellations. Called by `check_subscriptions`.
|
89
144
|
def check_cancellations
|
90
145
|
Subscription.find_each do |subscription|
|
91
146
|
process_for_cancellation(subscription) if subscription.pending_cancellation?
|
92
147
|
end
|
93
148
|
end
|
94
149
|
|
150
|
+
# Processes a subscription for cancellation.
|
151
|
+
# This method is called when a subscription is pending cancellation.
|
95
152
|
def process_for_cancellation(subscription)
|
96
153
|
return unless subscription.transition_to_expired
|
97
154
|
|