app_manager 2.5.0 → 2.7.0
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +23 -22
- data/app/controllers/app_manager/charges_controller.rb +21 -6
- data/app/controllers/app_manager/plans_controller.rb +41 -2
- data/lib/app_manager/fail_safe.rb +172 -45
- data/lib/app_manager/graphql_helper.rb +47 -49
- data/lib/app_manager/version.rb +1 -1
- data/lib/app_manager.rb +6 -0
- data/lib/generators/app_manager/install/install_generator.rb +8 -0
- data/lib/generators/app_manager/install/templates/add_charge_discount_snapshot_and_used.erb +12 -0
- data/lib/generators/app_manager/install/templates/failsafe_tables.erb +7 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b07aa041091f2f5c6acc9fc85b9ffbf6c73d7d97f0694981fb3ee261fd4d167c
|
|
4
|
+
data.tar.gz: 3ef460faa396f6dbb9df6cb66010045975cd76b28a29e97d00b4542bb846a0c2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 22f3bb93b50b5064d8db803023882e48539e9c212703b922379378e3cff63d017020b96c7cf3d2bdf3402768e4011f1ffbbd7ba823d513c3d81c9881a6f91a69
|
|
7
|
+
data.tar.gz: 423fbd2cea4724aaeeacfc86fc6f90d15df211fc8a74a23c8e6ac2c93ff3744d27c502873d55b3beb03d3783db3492d74b26c63f2d0d940209668715bccbc8d1
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
app_manager (2.
|
|
4
|
+
app_manager (2.7.0)
|
|
5
5
|
activerecord-import (~> 1.4)
|
|
6
6
|
httparty
|
|
7
7
|
kaminari (>= 0.16.3)
|
|
@@ -92,27 +92,27 @@ GEM
|
|
|
92
92
|
bigdecimal (4.0.1)
|
|
93
93
|
builder (3.3.0)
|
|
94
94
|
coderay (1.1.3)
|
|
95
|
-
concurrent-ruby (1.3.
|
|
95
|
+
concurrent-ruby (1.3.8)
|
|
96
96
|
connection_pool (3.0.2)
|
|
97
97
|
crack (1.0.1)
|
|
98
98
|
bigdecimal
|
|
99
99
|
rexml
|
|
100
|
-
crass (1.0.
|
|
100
|
+
crass (1.0.7)
|
|
101
101
|
csv (3.3.5)
|
|
102
102
|
date (3.5.1)
|
|
103
103
|
diff-lcs (1.6.2)
|
|
104
104
|
dotenv (2.1.2)
|
|
105
105
|
drb (2.2.3)
|
|
106
|
-
erb (6.0.
|
|
106
|
+
erb (6.0.6)
|
|
107
107
|
erubi (1.13.1)
|
|
108
|
-
globalid (1.
|
|
108
|
+
globalid (1.4.0)
|
|
109
109
|
activesupport (>= 6.1)
|
|
110
110
|
hashdiff (1.2.1)
|
|
111
111
|
httparty (0.24.2)
|
|
112
112
|
csv
|
|
113
113
|
mini_mime (>= 1.0.0)
|
|
114
114
|
multi_xml (>= 0.5.2)
|
|
115
|
-
i18n (1.
|
|
115
|
+
i18n (1.15.2)
|
|
116
116
|
concurrent-ruby (~> 1.0)
|
|
117
117
|
io-console (0.8.2)
|
|
118
118
|
irb (1.18.0)
|
|
@@ -120,7 +120,7 @@ GEM
|
|
|
120
120
|
prism (>= 1.3.0)
|
|
121
121
|
rdoc (>= 4.0.0)
|
|
122
122
|
reline (>= 0.4.2)
|
|
123
|
-
json (2.
|
|
123
|
+
json (2.21.1)
|
|
124
124
|
kaminari (1.2.2)
|
|
125
125
|
activesupport (>= 4.1.0)
|
|
126
126
|
kaminari-actionview (= 1.2.2)
|
|
@@ -134,10 +134,10 @@ GEM
|
|
|
134
134
|
kaminari-core (= 1.2.2)
|
|
135
135
|
kaminari-core (1.2.2)
|
|
136
136
|
logger (1.7.0)
|
|
137
|
-
loofah (2.25.
|
|
137
|
+
loofah (2.25.2)
|
|
138
138
|
crass (~> 1.0.2)
|
|
139
139
|
nokogiri (>= 1.12.0)
|
|
140
|
-
mail (2.9.
|
|
140
|
+
mail (2.9.1)
|
|
141
141
|
logger
|
|
142
142
|
mini_mime (>= 0.1.1)
|
|
143
143
|
net-imap
|
|
@@ -152,7 +152,7 @@ GEM
|
|
|
152
152
|
prism (~> 1.5)
|
|
153
153
|
multi_xml (0.9.1)
|
|
154
154
|
bigdecimal (>= 3.1, < 5)
|
|
155
|
-
net-imap (0.6.4)
|
|
155
|
+
net-imap (0.6.4.1)
|
|
156
156
|
date
|
|
157
157
|
net-protocol
|
|
158
158
|
net-pop (0.1.2)
|
|
@@ -162,12 +162,12 @@ GEM
|
|
|
162
162
|
net-smtp (0.5.1)
|
|
163
163
|
net-protocol
|
|
164
164
|
nio4r (2.7.5)
|
|
165
|
-
nokogiri (1.19.
|
|
165
|
+
nokogiri (1.19.4)
|
|
166
166
|
mini_portile2 (~> 2.8.2)
|
|
167
167
|
racc (~> 1.4)
|
|
168
|
-
nokogiri (1.19.
|
|
168
|
+
nokogiri (1.19.4-x86_64-linux-gnu)
|
|
169
169
|
racc (~> 1.4)
|
|
170
|
-
pp (0.6.
|
|
170
|
+
pp (0.6.4)
|
|
171
171
|
prettyprint
|
|
172
172
|
prettyprint (0.2.0)
|
|
173
173
|
prism (1.9.0)
|
|
@@ -175,9 +175,6 @@ GEM
|
|
|
175
175
|
coderay (~> 1.1)
|
|
176
176
|
method_source (~> 1.0)
|
|
177
177
|
reline (>= 0.6.0)
|
|
178
|
-
psych (5.3.1)
|
|
179
|
-
date
|
|
180
|
-
stringio
|
|
181
178
|
public_suffix (7.0.2)
|
|
182
179
|
racc (1.8.1)
|
|
183
180
|
rack (3.2.6)
|
|
@@ -206,8 +203,8 @@ GEM
|
|
|
206
203
|
activesupport (>= 5.0.0)
|
|
207
204
|
minitest
|
|
208
205
|
nokogiri (>= 1.6)
|
|
209
|
-
rails-html-sanitizer (1.7.
|
|
210
|
-
loofah (~> 2.25)
|
|
206
|
+
rails-html-sanitizer (1.7.1)
|
|
207
|
+
loofah (~> 2.25, >= 2.25.2)
|
|
211
208
|
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
|
212
209
|
railties (8.1.3)
|
|
213
210
|
actionpack (= 8.1.3)
|
|
@@ -219,9 +216,14 @@ GEM
|
|
|
219
216
|
tsort (>= 0.2)
|
|
220
217
|
zeitwerk (~> 2.6)
|
|
221
218
|
rake (13.3.1)
|
|
222
|
-
|
|
219
|
+
rbs (4.0.3)
|
|
220
|
+
logger
|
|
221
|
+
prism (>= 1.6.0)
|
|
222
|
+
tsort
|
|
223
|
+
rdoc (8.0.0)
|
|
223
224
|
erb
|
|
224
|
-
|
|
225
|
+
prism (>= 1.6.0)
|
|
226
|
+
rbs (>= 4.0.0)
|
|
225
227
|
tsort
|
|
226
228
|
reline (0.6.3)
|
|
227
229
|
io-console (~> 0.5)
|
|
@@ -242,7 +244,6 @@ GEM
|
|
|
242
244
|
rspec-support (~> 3.13.0)
|
|
243
245
|
rspec-support (3.13.7)
|
|
244
246
|
securerandom (0.4.1)
|
|
245
|
-
stringio (3.2.0)
|
|
246
247
|
thor (1.5.0)
|
|
247
248
|
timeout (0.6.1)
|
|
248
249
|
tsort (0.2.0)
|
|
@@ -254,7 +255,7 @@ GEM
|
|
|
254
255
|
addressable (>= 2.8.0)
|
|
255
256
|
crack (>= 0.3.2)
|
|
256
257
|
hashdiff (>= 0.4.0, < 2.0.0)
|
|
257
|
-
websocket-driver (0.8.
|
|
258
|
+
websocket-driver (0.8.2)
|
|
258
259
|
base64
|
|
259
260
|
websocket-extensions (>= 0.1.0)
|
|
260
261
|
websocket-extensions (0.1.5)
|
|
@@ -34,7 +34,7 @@ module AppManager
|
|
|
34
34
|
Rollbar.error("APP MANAGER Process Plan Failed #{e.inspect}-#{e.backtrace}")
|
|
35
35
|
end
|
|
36
36
|
@trial_activated_field = AppManager.configuration.field_names['trial_activated_at']
|
|
37
|
-
update_info = {@plan_field => params[:plan_id],
|
|
37
|
+
update_info = {@plan_field => params[:plan_id], grandfathered_field => 0}
|
|
38
38
|
if !config_trial_days.nil? && !plan_data.nil?
|
|
39
39
|
trial_days = plan_data['trial_days'] || 0
|
|
40
40
|
update_info[config_trial_days] = trial_days
|
|
@@ -58,7 +58,8 @@ module AppManager
|
|
|
58
58
|
raise Error, "Invalid charge"
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
|
-
|
|
61
|
+
charge_timestamp = Time.now.to_i
|
|
62
|
+
request_data = {'shop' => @shop.shopify_domain, 'timestamp' => charge_timestamp, 'plan' => params[:plan_id]}
|
|
62
63
|
request_data.merge!('host' => params['host']) if params['host'].present?
|
|
63
64
|
request_data.merge!('old_plan' => params['old_plan']) if params['old_plan'].present?
|
|
64
65
|
|
|
@@ -66,6 +67,14 @@ module AppManager
|
|
|
66
67
|
gq_obj = AppManager::GraphqlHelper.new(@shop.shopify_domain, @shop.shopify_token)
|
|
67
68
|
discount_local_storage = params[:discount_code].present? && !params[:discount_code].nil? ? params[:discount_code] : nil
|
|
68
69
|
data = gq_obj.recurring_charge_api_call(plan_data, return_url, @shop,discount_local_storage)
|
|
70
|
+
# Stash the discount snapshot so the callback can persist it on the charge row.
|
|
71
|
+
if gq_obj.discount_snapshot.present?
|
|
72
|
+
begin
|
|
73
|
+
Rails.cache.write(AppManager.charge_snapshot_cache_key(@shop.shopify_domain, charge_timestamp), gq_obj.discount_snapshot, expires_in: 1.hour)
|
|
74
|
+
rescue Exception => e
|
|
75
|
+
Rollbar.error("APP MANAGER Failed to cache discount snapshot #{e.inspect}")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
69
78
|
if data.present? && !data["errors"].present? && (data["data"].present? && data["data"]["appSubscriptionCreate"].present? && (!data["data"]["appSubscriptionCreate"]["userErrors"].any? && data["data"]["appSubscriptionCreate"]["confirmationUrl"]))
|
|
70
79
|
redirect_charge = data["data"]["appSubscriptionCreate"]["confirmationUrl"]
|
|
71
80
|
render json: {'redirect_url' => redirect_charge}
|
|
@@ -111,6 +120,14 @@ module AppManager
|
|
|
111
120
|
|
|
112
121
|
['api_client_id', 'return_url', 'decorated_return_url','id','id','currency'].each { |k| charge.delete k }
|
|
113
122
|
|
|
123
|
+
# Merge the discount snapshot captured at charge creation onto the charge row.
|
|
124
|
+
snapshot_key = AppManager.charge_snapshot_cache_key(params[:shop], params[:timestamp])
|
|
125
|
+
discount_snapshot = Rails.cache.read(snapshot_key) rescue nil
|
|
126
|
+
if discount_snapshot.is_a?(Hash)
|
|
127
|
+
discount_snapshot.each { |field, value| charge[field] = value unless value.nil? || value == '' }
|
|
128
|
+
Rails.cache.delete(snapshot_key) rescue nil
|
|
129
|
+
end
|
|
130
|
+
|
|
114
131
|
charge_ob = AppManager::Client.new(nil, json_req = true)
|
|
115
132
|
response = charge_ob.store_charge(charge.to_json)
|
|
116
133
|
|
|
@@ -209,11 +226,10 @@ module AppManager
|
|
|
209
226
|
if params[:shop].present? && params[:plan_id].present?
|
|
210
227
|
@shop = shop_data
|
|
211
228
|
grandfathered_field = @field_names['grandfathered']
|
|
212
|
-
trial_activated_field = @field_names['trial_activated_at']
|
|
213
229
|
if !@shop.nil?
|
|
214
230
|
plan_obj = AppManager::Client.new
|
|
215
231
|
plan_data = plan_obj.get_plan(params[:plan_id], params[:shop])
|
|
216
|
-
update_info = {@plan_field => params[:plan_id],
|
|
232
|
+
update_info = {@plan_field => params[:plan_id], grandfathered_field => 0}
|
|
217
233
|
if !config_trial_days.nil? && !plan_data.nil?
|
|
218
234
|
trial_days = plan_data['trial_days'] || 0
|
|
219
235
|
update_info[config_trial_days] = trial_days
|
|
@@ -242,9 +258,8 @@ module AppManager
|
|
|
242
258
|
if params[:shop].present?
|
|
243
259
|
@shop = shop_data
|
|
244
260
|
grandfathered_field = @field_names['grandfathered']
|
|
245
|
-
trial_activated_field = @field_names['trial_activated_at']
|
|
246
261
|
if !@shop.nil?
|
|
247
|
-
update_info = {@plan_field => nil,
|
|
262
|
+
update_info = {@plan_field => nil, grandfathered_field => 0}
|
|
248
263
|
if !config_trial_days.nil?
|
|
249
264
|
update_info[config_trial_days] = 0
|
|
250
265
|
end
|
|
@@ -98,6 +98,30 @@ module AppManager
|
|
|
98
98
|
app_faqs_obj = AppManager::Client.new
|
|
99
99
|
app_faqs = app_faqs_obj.get_app_faqs || []
|
|
100
100
|
|
|
101
|
+
# Embed the active charge's effective pricing onto the matching plan(s), so it
|
|
102
|
+
# travels with the plan payload the frontend already consumes.
|
|
103
|
+
active_charge_pricing = nil
|
|
104
|
+
if active_charge.is_a?(Hash) && active_charge['active_charge'].is_a?(Hash) && active_charge['active_charge']['effective_price']
|
|
105
|
+
ac = active_charge['active_charge']
|
|
106
|
+
active_charge_pricing = {
|
|
107
|
+
'plan_id' => ac['plan_id'],
|
|
108
|
+
'interval' => ac['interval'],
|
|
109
|
+
'effective_price' => ac['effective_price'],
|
|
110
|
+
'strike_price' => ac['strike_price'],
|
|
111
|
+
'is_discount_active' => ac['is_discount_active'] || false,
|
|
112
|
+
'discount_ends_on' => ac['discount_ends_on'],
|
|
113
|
+
'remaining_intervals' => ac['remaining_intervals'],
|
|
114
|
+
'discount_value' => ac['discount_value'],
|
|
115
|
+
'discount_type' => ac['discount_type'],
|
|
116
|
+
'discount_duration_intervals' => ac['discount_duration_intervals']
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
if active_charge_pricing.present?
|
|
121
|
+
plans = plans.map { |p| with_active_charge_pricing(p, active_charge_pricing) } if plans.is_a?(Array)
|
|
122
|
+
plan = with_active_charge_pricing(plan, active_charge_pricing)
|
|
123
|
+
end
|
|
124
|
+
|
|
101
125
|
response = {
|
|
102
126
|
'plans' => plans,
|
|
103
127
|
'promotional_discount' => promotional_discount,
|
|
@@ -108,13 +132,24 @@ module AppManager
|
|
|
108
132
|
'default_plan_id' => default_plan_id,
|
|
109
133
|
'most_popular_plan_ids' => most_popular_plan_ids,
|
|
110
134
|
'choose_later' => choose_later,
|
|
111
|
-
'has_active_charge' => (((active_charge && active_charge.any? && !active_charge['active_charge'].nil?)
|
|
135
|
+
'has_active_charge' => (((active_charge && active_charge.any? && !active_charge['active_charge'].nil?) || (plan.present? && plan['price'].to_f == 0) || !trial_activated_at) ? true : false),
|
|
112
136
|
'global_plan_charge' => plan.present? && plan['is_global'] && active_charge.present? && active_charge.any? && !active_charge['active_charge'].nil? && !active_charge['bundle_charge'].nil?,
|
|
113
137
|
'app_faqs' => app_faqs
|
|
114
138
|
}
|
|
115
139
|
render json: response
|
|
116
140
|
end
|
|
117
141
|
|
|
142
|
+
# Attaches active_charge_pricing to a plan only when it is the plan the active
|
|
143
|
+
# charge belongs to (same id and, when known, same interval).
|
|
144
|
+
def with_active_charge_pricing(plan, pricing)
|
|
145
|
+
return plan unless plan.is_a?(Hash) && plan.present?
|
|
146
|
+
matches_plan = plan['id'].to_s == pricing['plan_id'].to_s
|
|
147
|
+
plan_interval = plan['interval'].is_a?(Hash) ? plan['interval']['value'] : plan['interval']
|
|
148
|
+
matches_interval = pricing['interval'].blank? || plan_interval == pricing['interval']
|
|
149
|
+
plan = plan.merge('active_charge_pricing' => pricing) if matches_plan && matches_interval
|
|
150
|
+
plan
|
|
151
|
+
end
|
|
152
|
+
|
|
118
153
|
def users
|
|
119
154
|
if model
|
|
120
155
|
|
|
@@ -170,7 +205,11 @@ module AppManager
|
|
|
170
205
|
@trial_activated_field = AppManager.configuration.field_names['trial_activated_at']
|
|
171
206
|
plan_obj = AppManager::Client.new
|
|
172
207
|
plan_data = plan_obj.get_plan(params[:plan_id], params[:shop_domain]) rescue nil
|
|
173
|
-
|
|
208
|
+
# Only stamp the trial start the first time; re-assigning a no-charge plan must
|
|
209
|
+
# not restart the trial clock (that would hand back trial days already consumed).
|
|
210
|
+
existing_trial_activated_at = @shop[@trial_activated_field] rescue nil
|
|
211
|
+
update_info = {@plan_field => params[:plan_id]}
|
|
212
|
+
update_info[@trial_activated_field] = DateTime.now if existing_trial_activated_at.blank?
|
|
174
213
|
if !config_trial_days.nil? && !plan_data.nil?
|
|
175
214
|
trial_days = plan_data['trial_days'] || 0
|
|
176
215
|
update_info[config_trial_days] = trial_days
|
|
@@ -159,13 +159,26 @@ module AppManager
|
|
|
159
159
|
charges.each do |charge|
|
|
160
160
|
charge_test = charge['test'] ? true : false
|
|
161
161
|
if !charge["id"].nil?
|
|
162
|
-
|
|
162
|
+
attrs = { c_id: charge["id"], charge_id: charge["charge_id"], test: charge_test, status: charge["status"], name: charge["name"], type: charge["type"], price: charge["price"], interval: charge["interval"], trial_days: charge["trial_days"], billing_on: charge["billing_on"], activated_on: charge["activated_on"], trial_ends_on: charge["trial_ends_on"], cancelled_on: charge["cancelled_on"], expires_on: charge["expires_on"], plan_id: charge["plan_id"], description: charge["description"], shop_domain: charge["shop_domain"], created_at: charge["created_at"], updated_at: charge["updated_at"], app_id: charge["app_id"], sync: true }
|
|
163
|
+
add_charge_discount_snapshot(attrs, charge)
|
|
164
|
+
charge_data << AppManager::Charge.new(attrs)
|
|
163
165
|
end
|
|
164
166
|
end
|
|
165
167
|
AppManager::Charge.bulk_import charge_data
|
|
166
168
|
end
|
|
167
169
|
end
|
|
168
170
|
|
|
171
|
+
# Copies the discount snapshot fields (value/type/duration/source/promo id) from a
|
|
172
|
+
# charge payload onto the failsafe attrs, but only for columns that actually exist
|
|
173
|
+
# so older (un-migrated) installs keep working.
|
|
174
|
+
def add_charge_discount_snapshot(attrs, charge)
|
|
175
|
+
columns = AppManager::Charge.column_names
|
|
176
|
+
%w[discount_value discount_type discount_duration_intervals discount_source promotional_discount_id].each do |field|
|
|
177
|
+
attrs[field.to_sym] = charge[field] if columns.include?(field) && !charge[field].nil?
|
|
178
|
+
end
|
|
179
|
+
attrs
|
|
180
|
+
end
|
|
181
|
+
|
|
169
182
|
|
|
170
183
|
def save_api_apps(apps)
|
|
171
184
|
begin
|
|
@@ -204,8 +217,11 @@ module AppManager
|
|
|
204
217
|
end
|
|
205
218
|
if discount_plans.any?
|
|
206
219
|
discount_plans_data = []
|
|
220
|
+
has_used_column = AppManager::DiscountPlan.column_names.include?('used')
|
|
207
221
|
discount_plans.each do |discount_plan|
|
|
208
|
-
|
|
222
|
+
attrs = { discount_plan_id: discount_plan['id'], discount: discount_plan['discount'], shop_domain: discount_plan['shop_domain'], cycle_count: discount_plan['cycle_count'], plan_id: discount_plan['plan_id'], created_by: discount_plan['created_by'], created_at: discount_plan['created_at'], updated_at: discount_plan['updated_at'], app_id: discount_plan['app_id'], discount_type: discount_plan['discount_type'] }
|
|
223
|
+
attrs[:used] = discount_plan['used'] ? true : false if has_used_column
|
|
224
|
+
discount_plans_data << AppManager::DiscountPlan.new(attrs)
|
|
209
225
|
# discount_plans_data << AppManager::DiscountPlan.new(id: discount_plan['id'], discount: discount_plan['discount'], shop_domain: discount_plan['shop_domain'], cycle_count: discount_plan['cycle_count'], plan_id: discount_plan['plan_id'], created_by: discount_plan['created_by'], created_at: discount_plan['created_at'], updated_at: discount_plan['updated_at'], app_id: discount_plan['app_id'], discount_type: discount_plan['discount_type'])
|
|
210
226
|
end
|
|
211
227
|
AppManager::DiscountPlan.bulk_import discount_plans_data
|
|
@@ -238,8 +254,11 @@ module AppManager
|
|
|
238
254
|
end
|
|
239
255
|
if plan_users.any?
|
|
240
256
|
extend_plan_users = []
|
|
257
|
+
has_used_column = AppManager::PlanUser.column_names.include?('used')
|
|
241
258
|
plan_users.each do |plan_user|
|
|
242
|
-
|
|
259
|
+
attrs = { plan_user_id: plan_user['id'], shop_domain: plan_user['shop_domain'], plan_id: plan_user['plan_id'], app_id: plan_user['app_id'], created_by: plan_user['created_by'], created_at: plan_user['created_at'], updated_at: plan_user['updated_at'] }
|
|
260
|
+
attrs[:used] = plan_user['used'] ? true : false if has_used_column
|
|
261
|
+
extend_plan_users << AppManager::PlanUser.new(attrs)
|
|
243
262
|
# extend_plan_users << AppManager::PlanUser.new(id: plan_user['id'], shop_domain: plan_user['shop_domain'], plan_id: plan_user['plan_id'], created_by: plan_user['created_by'], created_at: plan_user['created_at'], updated_at: plan_user['updated_at'])
|
|
244
263
|
end
|
|
245
264
|
AppManager::PlanUser.bulk_import extend_plan_users
|
|
@@ -396,6 +415,7 @@ module AppManager
|
|
|
396
415
|
|
|
397
416
|
custom_plan_ids = []
|
|
398
417
|
plan_users = AppManager::PlanUser.where(shop_domain: params['shop_domain'])
|
|
418
|
+
plan_users = plan_users.where(used: false) if AppManager::PlanUser.column_names.include?('used')
|
|
399
419
|
custom_plan_ids = plan_users.pluck(:plan_id) if plan_users.present?
|
|
400
420
|
|
|
401
421
|
custom_plan_base_ids = []
|
|
@@ -474,6 +494,7 @@ module AppManager
|
|
|
474
494
|
custom_discounts_data = []
|
|
475
495
|
if params["shop_domain"].present? && plan_data
|
|
476
496
|
custom_discounts = AppManager::DiscountPlan.where(shop_domain: params['shop_domain']).order(created_at: :desc)
|
|
497
|
+
custom_discounts = custom_discounts.where(used: false) if AppManager::DiscountPlan.column_names.include?('used')
|
|
477
498
|
if custom_discounts.present?
|
|
478
499
|
custom_discounts.each do |custom_discount|
|
|
479
500
|
new_custom_discount = {}
|
|
@@ -496,6 +517,7 @@ module AppManager
|
|
|
496
517
|
plan['discount'] = cd['discount']
|
|
497
518
|
plan['discount_type'] = cd['discount_type']
|
|
498
519
|
plan['cycle_count'] = cd['cycle_count']
|
|
520
|
+
plan['discount_is_custom'] = true
|
|
499
521
|
end
|
|
500
522
|
end
|
|
501
523
|
|
|
@@ -556,10 +578,12 @@ module AppManager
|
|
|
556
578
|
plan_data['old_plan_id'] = nil # Temporary for migration puspose only
|
|
557
579
|
if params["shop_domain"].present? && plan_data
|
|
558
580
|
discount_plans = AppManager::DiscountPlan.where(plan_id: params["plan_id"], shop_domain: params["shop_domain"])
|
|
581
|
+
discount_plans = discount_plans.where(used: false) if AppManager::DiscountPlan.column_names.include?('used')
|
|
559
582
|
discount_plans.each do |cd|
|
|
560
583
|
plan_data['discount'] = cd['discount'] if cd rescue plan_data['discount']
|
|
561
584
|
plan_data['discount_type'] = cd['discount_type'] if cd rescue plan_data['discount_type']
|
|
562
585
|
plan_data['cycle_count'] = cd['cycle_count'] if cd rescue plan_data['cycle_count']
|
|
586
|
+
plan_data['discount_is_custom'] = true if cd
|
|
563
587
|
end
|
|
564
588
|
end
|
|
565
589
|
|
|
@@ -572,54 +596,82 @@ module AppManager
|
|
|
572
596
|
end
|
|
573
597
|
|
|
574
598
|
def get_local_remaining_days(params, options)
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
599
|
+
shop_domain = params['shop_domain']
|
|
600
|
+
plan_id = params['plan_id']
|
|
601
|
+
trial_activated_at = params['trial_activated_at']
|
|
602
|
+
|
|
603
|
+
return nil if plan_id.blank?
|
|
604
|
+
|
|
605
|
+
plan_trial_days = (AppManager::Plan.where(plan_id: plan_id).limit(1).pluck(:trial_days).first || 0).to_i
|
|
606
|
+
|
|
607
|
+
# Extension stack - sum every record for this plan.
|
|
608
|
+
extension_days = AppManager::ExtendTrial.where(shop_domain: shop_domain, plan_id: plan_id).sum(:days).to_i
|
|
609
|
+
|
|
610
|
+
consumed = prepare_consumed_trial_days(shop_domain, trial_activated_at)
|
|
611
|
+
|
|
612
|
+
[0, (plan_trial_days + extension_days) - consumed].max
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
# Offline mirror of the portal's consumed-trial-days calculation. Total trial days
|
|
616
|
+
# already consumed, excluding time spent on a free plan (paused via
|
|
617
|
+
# charge.cancelled_on). Derived from existing failsafe tables only.
|
|
618
|
+
def prepare_consumed_trial_days(shop_domain, trial_activated_at = nil)
|
|
619
|
+
windows = []
|
|
620
|
+
earliest_charge_start = nil
|
|
621
|
+
|
|
622
|
+
charges = AppManager::Charge.where(shop_domain: shop_domain)
|
|
623
|
+
.where('trial_days > 0')
|
|
624
|
+
.where.not(trial_ends_on: nil)
|
|
625
|
+
|
|
626
|
+
charges.each do |charge|
|
|
627
|
+
trial_end = to_datetime_safe(charge['trial_ends_on'])
|
|
628
|
+
next if trial_end.nil?
|
|
629
|
+
start = trial_end - charge['trial_days'].to_i.days
|
|
630
|
+
|
|
631
|
+
earliest_charge_start = start if earliest_charge_start.nil? || start < earliest_charge_start
|
|
632
|
+
|
|
633
|
+
finish = DateTime.now
|
|
634
|
+
if charge['cancelled_on'].present?
|
|
635
|
+
cancelled_on = to_datetime_safe(charge['cancelled_on'])
|
|
636
|
+
finish = cancelled_on if cancelled_on && cancelled_on < finish
|
|
596
637
|
end
|
|
597
|
-
|
|
638
|
+
finish = trial_end if trial_end < finish
|
|
639
|
+
windows << [start, finish] if finish > start
|
|
598
640
|
end
|
|
599
|
-
@charges = AppManager::Charge.where(shop_domain: @shop_domain).order(created_at: :desc)
|
|
600
|
-
if @charges.any?
|
|
601
|
-
charge = @charges.first
|
|
602
641
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
642
|
+
if trial_activated_at.present?
|
|
643
|
+
choose_later_plan = AppManager::Plan.where(choose_later_plan: true).order(id: :desc).first
|
|
644
|
+
if choose_later_plan && choose_later_plan['trial_days'].to_i > 0
|
|
645
|
+
cl_extension = AppManager::ExtendTrial.where(shop_domain: shop_domain, plan_id: choose_later_plan['plan_id']).sum(:days).to_i
|
|
646
|
+
length = choose_later_plan['trial_days'].to_i + cl_extension
|
|
647
|
+
start = to_datetime_safe(trial_activated_at)
|
|
648
|
+
unless start.nil?
|
|
649
|
+
natural_end = start + length.days
|
|
650
|
+
finish = DateTime.now < natural_end ? DateTime.now : natural_end
|
|
651
|
+
# No-charge trial ends when the first charge starts.
|
|
652
|
+
finish = earliest_charge_start if earliest_charge_start && earliest_charge_start < finish
|
|
653
|
+
windows << [start, finish] if finish > start
|
|
606
654
|
end
|
|
655
|
+
end
|
|
656
|
+
end
|
|
607
657
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
658
|
+
return 0 if windows.empty?
|
|
659
|
+
|
|
660
|
+
windows.sort_by! { |w| w[0] }
|
|
661
|
+
|
|
662
|
+
total = 0
|
|
663
|
+
cur_start, cur_end = windows[0]
|
|
664
|
+
windows[1..-1].each do |start, finish|
|
|
665
|
+
if start <= cur_end
|
|
666
|
+
cur_end = finish if finish > cur_end
|
|
667
|
+
else
|
|
668
|
+
total += (cur_end - cur_start).to_i
|
|
669
|
+
cur_start, cur_end = start, finish
|
|
620
670
|
end
|
|
621
|
-
return @remaining_days
|
|
622
671
|
end
|
|
672
|
+
total += (cur_end - cur_start).to_i
|
|
673
|
+
|
|
674
|
+
total
|
|
623
675
|
end
|
|
624
676
|
|
|
625
677
|
|
|
@@ -633,6 +685,7 @@ module AppManager
|
|
|
633
685
|
charges = AppManager::Charge.where(status: 'active', shop_domain: params["shop_domain"])
|
|
634
686
|
if charges.any?
|
|
635
687
|
active_charge = charges.first.attributes
|
|
688
|
+
active_charge = active_charge.merge(effective_charge_pricing(active_charge))
|
|
636
689
|
end
|
|
637
690
|
cancelled_charges = AppManager::Charge.where(status: 'cancelled', shop_domain: params["shop_domain"]).order(created_at: :desc)
|
|
638
691
|
if cancelled_charges.any?
|
|
@@ -643,6 +696,78 @@ module AppManager
|
|
|
643
696
|
return charge_hash
|
|
644
697
|
end
|
|
645
698
|
|
|
699
|
+
# Offline mirror of the portal's effective-pricing calculation. Derives the true
|
|
700
|
+
# price the shop pays right now from the discount snapshot stored on the charge
|
|
701
|
+
# plus elapsed time, instead of recomputing from current discount config.
|
|
702
|
+
def effective_charge_pricing(charge)
|
|
703
|
+
base = (charge['price'] || 0).to_f.round(2)
|
|
704
|
+
no_discount = {
|
|
705
|
+
'effective_price' => base,
|
|
706
|
+
'strike_price' => nil,
|
|
707
|
+
'is_discount_active' => false,
|
|
708
|
+
'remaining_intervals' => nil,
|
|
709
|
+
'discount_ends_on' => nil
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
begin
|
|
713
|
+
now = DateTime.now
|
|
714
|
+
|
|
715
|
+
value = charge['discount_value']
|
|
716
|
+
return no_discount if value.nil? || value.to_f <= 0
|
|
717
|
+
|
|
718
|
+
type = charge['discount_type'] || 'percentage'
|
|
719
|
+
discounted = type == 'percentage' ? base - (base * value.to_f / 100) : base - value.to_f
|
|
720
|
+
discounted = [discounted, 0].max.round(2)
|
|
721
|
+
|
|
722
|
+
duration = (charge['discount_duration_intervals'] || 0).to_i
|
|
723
|
+
if duration <= 0
|
|
724
|
+
return {
|
|
725
|
+
'effective_price' => discounted,
|
|
726
|
+
'strike_price' => base,
|
|
727
|
+
'is_discount_active' => true,
|
|
728
|
+
'remaining_intervals' => nil,
|
|
729
|
+
'discount_ends_on' => nil
|
|
730
|
+
}
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
anchor = to_datetime_safe(charge['trial_ends_on'] || charge['activated_on'] || charge['created_at']) || now
|
|
734
|
+
|
|
735
|
+
interval_days = charge['interval'] == 'ANNUAL' ? 365 : 30
|
|
736
|
+
days_since_anchor = [(now - anchor).to_f, 0].max
|
|
737
|
+
elapsed = (days_since_anchor / interval_days).floor
|
|
738
|
+
ends_on = (anchor + (duration * interval_days)).to_date.to_s
|
|
739
|
+
|
|
740
|
+
if elapsed >= duration
|
|
741
|
+
return {
|
|
742
|
+
'effective_price' => base,
|
|
743
|
+
'strike_price' => nil,
|
|
744
|
+
'is_discount_active' => false,
|
|
745
|
+
'remaining_intervals' => 0,
|
|
746
|
+
'discount_ends_on' => ends_on
|
|
747
|
+
}
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
{
|
|
751
|
+
'effective_price' => discounted,
|
|
752
|
+
'strike_price' => base,
|
|
753
|
+
'is_discount_active' => true,
|
|
754
|
+
'remaining_intervals' => duration - elapsed,
|
|
755
|
+
'discount_ends_on' => ends_on
|
|
756
|
+
}
|
|
757
|
+
rescue Exception => e
|
|
758
|
+
Rollbar.error("APP MANAGER effective_charge_pricing failed #{e.inspect}") rescue nil
|
|
759
|
+
no_discount
|
|
760
|
+
end
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
def to_datetime_safe(value)
|
|
764
|
+
return nil if value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
|
765
|
+
return value.to_datetime if value.respond_to?(:to_datetime)
|
|
766
|
+
DateTime.parse(value.to_s)
|
|
767
|
+
rescue Exception
|
|
768
|
+
nil
|
|
769
|
+
end
|
|
770
|
+
|
|
646
771
|
def get_local_has_plan(params, options)
|
|
647
772
|
if params["grandfathered"].present? && params["grandfathered"] == 1
|
|
648
773
|
return {"has_plan" => true}
|
|
@@ -739,7 +864,9 @@ module AppManager
|
|
|
739
864
|
test_value = charge["test"]
|
|
740
865
|
plan_id = charge["plan_id"].to_i
|
|
741
866
|
begin
|
|
742
|
-
|
|
867
|
+
attrs = { charge_id: charge["charge_id"], test: test_value, status: charge["status"], name: charge["name"], type: charge["type"], price: charge["price"], interval: charge["interval"], trial_days: charge["trial_days"], billing_on: charge["billing_on"], activated_on: charge["activated_on"], trial_ends_on: charge["trial_ends_on"], cancelled_on: charge["cancelled_on"], expires_on: charge["expires_on"], plan_id: plan_id, description: charge["description"], shop_domain: charge["shop_domain"], created_at: charge["created_at"], updated_at: charge["updated_at"], sync: 0, process_type: 'store-charge' }
|
|
868
|
+
add_charge_discount_snapshot(attrs, charge)
|
|
869
|
+
AppManager::Charge.create(attrs)
|
|
743
870
|
message = {"message" => 'success'}
|
|
744
871
|
rescue Exception => e
|
|
745
872
|
Rollbar.error("Charge not saved on local DB due to #{e.inspect}")
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
module AppManager
|
|
2
2
|
class GraphqlHelper
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
# Discount snapshot captured while building the last recurring charge, so the
|
|
5
|
+
# charge callback can persist it (value/type/duration/source) on the charge row.
|
|
6
|
+
attr_reader :discount_snapshot
|
|
7
|
+
|
|
4
8
|
def initialize(shopify_domain,shopify_token)
|
|
5
9
|
@api_key = AppManager.configuration.shopify_api_key || nil
|
|
6
10
|
@api_version = AppManager.configuration.shopify_api_version || nil
|
|
@@ -61,26 +65,13 @@ module AppManager
|
|
|
61
65
|
if !plan['affiliate'].nil? && plan['affiliate'].any? && !shop_plan_field.nil? && (plan['affiliate'].map{|e| e['value']}.include?(shop[shop_plan_field]) || shop[partner_development_field])
|
|
62
66
|
plan_test = true
|
|
63
67
|
end
|
|
64
|
-
trial_days = plan['trial_days'] || 0
|
|
65
|
-
if shop && shop.shopify_domain
|
|
68
|
+
trial_days = (plan['trial_days'] || 0).to_i
|
|
69
|
+
if shop && shop.shopify_domain
|
|
66
70
|
trial_activated_at = shop[AppManager.configuration.field_names['trial_activated_at']] rescue nil
|
|
67
|
-
plan_id_field = shop[AppManager.configuration.plan_id_or_name_field] rescue nil
|
|
68
71
|
remaining_obj = AppManager::Client.new
|
|
69
|
-
remaining = remaining_obj.get_remaining_days(shop.shopify_domain,trial_activated_at,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if !remaining.nil?
|
|
73
|
-
if !plan_id_field.nil?
|
|
74
|
-
plan_obj = AppManager::Client.new
|
|
75
|
-
current_plan = plan_obj.get_plan(plan_id_field) rescue nil
|
|
76
|
-
used_days = (current_plan['trial_days'].to_i - remaining.to_i) if current_plan rescue 0
|
|
77
|
-
if used_days > 0
|
|
78
|
-
days = trial_days - used_days
|
|
79
|
-
trial_days = days > 0 ? days : 0
|
|
80
|
-
end
|
|
81
|
-
else
|
|
82
|
-
trial_days = remaining
|
|
83
|
-
end
|
|
72
|
+
remaining = remaining_obj.get_remaining_days(shop.shopify_domain, trial_activated_at, plan['id'])
|
|
73
|
+
if remaining.is_a?(Numeric) || (remaining.is_a?(String) && remaining.match?(/\A-?\d+\z/))
|
|
74
|
+
trial_days = [0, remaining.to_i].max
|
|
84
75
|
end
|
|
85
76
|
end
|
|
86
77
|
|
|
@@ -91,54 +82,61 @@ module AppManager
|
|
|
91
82
|
promotional_discount = [] if promotional_discount.class.to_s == "Hash" && promotional_discount.has_key?('status') && promotional_discount['status'] == 404
|
|
92
83
|
end
|
|
93
84
|
|
|
94
|
-
|
|
85
|
+
# A promotional discount now takes priority over the plan's own discount when it
|
|
86
|
+
# applies to this (non-global) plan; otherwise fall back to the plan discount.
|
|
87
|
+
promo_applies = promotional_discount.present? && promotional_discount.respond_to?(:any?) && promotional_discount.any? &&
|
|
88
|
+
promotional_discount['value'].to_f > 0 &&
|
|
89
|
+
!(plan['is_global'] == 1 || plan['is_global'] == true) &&
|
|
90
|
+
(promotional_discount['plan_relation'].blank? || promotional_discount['plan_relation'].include?(plan['id']))
|
|
91
|
+
|
|
92
|
+
plan_discount = {}
|
|
93
|
+
@discount_snapshot = nil
|
|
94
|
+
|
|
95
|
+
if promo_applies
|
|
96
|
+
discount_type = promotional_discount['type'] || 'percentage'
|
|
97
|
+
discount_value = {
|
|
98
|
+
discount_type => discount_type == 'percentage' ? promotional_discount['value'].to_f / 100 : [promotional_discount['value'].to_f, plan['price']].min
|
|
99
|
+
}
|
|
100
|
+
discount = { 'value' => discount_value }
|
|
101
|
+
discount['durationLimitInIntervals'] = promotional_discount['duration_intervals'].to_i if promotional_discount['duration_intervals'].to_i.positive?
|
|
102
|
+
plan_discount = { "discount" => discount }
|
|
103
|
+
|
|
104
|
+
@discount_snapshot = {
|
|
105
|
+
'discount_value' => promotional_discount['value'].to_f,
|
|
106
|
+
'discount_type' => promotional_discount['type'] || 'percentage',
|
|
107
|
+
'discount_duration_intervals' => promotional_discount['duration_intervals'].to_i,
|
|
108
|
+
'discount_source' => 'promotional',
|
|
109
|
+
'promotional_discount_id' => (promotional_discount['id'] || 0).to_i
|
|
110
|
+
}
|
|
111
|
+
elsif !plan['discount'].nil? && plan['discount'] != 0
|
|
95
112
|
discount_type = plan['discount_type'] || "percentage"
|
|
96
113
|
discount_val = discount_type == "percentage" ? (plan['discount'].to_f/ 100) : "#{plan['discount']}"
|
|
97
114
|
if !@api_version.nil? && @api_version.to_s[/^\d{4}/].to_i >= 2024
|
|
98
|
-
plan_discount = if plan['
|
|
115
|
+
plan_discount = if plan['cycle_count'] && plan['cycle_count'].to_i != 0
|
|
99
116
|
{
|
|
100
117
|
"discount" => { "durationLimitInIntervals" => (plan['cycle_count'].to_i),
|
|
101
118
|
"value" => {"#{discount_type}" => discount_val}
|
|
102
119
|
}
|
|
103
120
|
}
|
|
104
|
-
|
|
121
|
+
else
|
|
105
122
|
{
|
|
106
123
|
"discount" => { "value" => {"#{discount_type}" => discount_val}
|
|
107
124
|
}
|
|
108
125
|
}
|
|
109
|
-
else
|
|
110
|
-
{}
|
|
111
126
|
end
|
|
112
127
|
else
|
|
113
|
-
plan_discount =
|
|
114
|
-
end
|
|
115
|
-
else
|
|
116
|
-
|
|
117
|
-
if promotional_discount.any?
|
|
118
|
-
|
|
119
|
-
if promotional_discount['plan_relation'].any? && !promotional_discount['plan_relation'].include?(plan['id']) && plan['is_global']
|
|
120
|
-
plan_discount = {}
|
|
121
|
-
else
|
|
122
|
-
|
|
123
|
-
discount_type = promotional_discount['type'] || 'percentage'
|
|
124
|
-
discount_value = {
|
|
125
|
-
discount_type => discount_type == 'percentage' ? promotional_discount['value'].to_f / 100 : [promotional_discount['value'].to_f, plan['price']].min
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
discount = { 'value' => discount_value }
|
|
129
|
-
|
|
130
|
-
discount['durationLimitInIntervals'] = promotional_discount['duration_intervals'].to_i if promotional_discount['duration_intervals'].to_i.positive?
|
|
131
|
-
plan_discount = { "discount" => discount}
|
|
132
|
-
|
|
133
|
-
end
|
|
134
|
-
|
|
128
|
+
plan_discount = { "discount" => { "durationLimitInIntervals" => (plan['cycle_count'].to_i), "value" => {"#{discount_type}" => discount_val} } }
|
|
135
129
|
end
|
|
136
130
|
|
|
131
|
+
@discount_snapshot = {
|
|
132
|
+
'discount_value' => plan['discount'].to_f,
|
|
133
|
+
'discount_type' => plan['discount_type'] || 'percentage',
|
|
134
|
+
'discount_duration_intervals' => plan['cycle_count'].to_i,
|
|
135
|
+
'discount_source' => plan['discount_is_custom'] ? 'custom' : 'plan'
|
|
136
|
+
}
|
|
137
137
|
end
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
promotional_discount_applies = promotional_discount.present? && promotional_discount.any?
|
|
141
|
-
promotional_discount_id = discount_exists && promotional_discount_applies ? 0 : promotional_discount_applies ? promotional_discount['id'] : 0
|
|
139
|
+
promotional_discount_id = promo_applies ? (promotional_discount['id'] || 0) : 0
|
|
142
140
|
|
|
143
141
|
return_url += "&discount=#{promotional_discount_id}"
|
|
144
142
|
|
data/lib/app_manager/version.rb
CHANGED
data/lib/app_manager.rb
CHANGED
|
@@ -26,6 +26,12 @@ module AppManager
|
|
|
26
26
|
@configuration ||= AppManager::Config.new
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
# Cache key for the discount snapshot captured while a charge is being created.
|
|
30
|
+
# Kept under a separate prefix from "app-manager-cache/*" so clear_cache does not wipe it.
|
|
31
|
+
def self.charge_snapshot_cache_key(shop, timestamp)
|
|
32
|
+
"app-manager:charge-snapshot:#{shop}:#{timestamp}"
|
|
33
|
+
end
|
|
34
|
+
|
|
29
35
|
def self.clear_cache
|
|
30
36
|
begin
|
|
31
37
|
Rails.cache.delete_matched("app-manager-cache/*")
|
|
@@ -82,6 +82,14 @@ module AppManager
|
|
|
82
82
|
end
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
+
def create_charge_discount_snapshot_and_used_migration
|
|
86
|
+
if self.class.migration_exists?("db/app_manager", "add_charge_discount_snapshot_and_used")
|
|
87
|
+
say_status("skipped", "Migration add_charge_discount_snapshot_and_used.rb already exists")
|
|
88
|
+
else
|
|
89
|
+
migration_template("add_charge_discount_snapshot_and_used.erb", "db/app_manager/add_charge_discount_snapshot_and_used.rb")
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
85
93
|
|
|
86
94
|
private
|
|
87
95
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class AddChargeDiscountSnapshotAndUsed < ActiveRecord::Migration[<%= rails_migration_version %>]
|
|
2
|
+
def change
|
|
3
|
+
add_column :charges, :discount_value, :decimal, precision: 8, scale: 2 unless column_exists?(:charges, :discount_value)
|
|
4
|
+
add_column :charges, :discount_type, :string unless column_exists?(:charges, :discount_type)
|
|
5
|
+
add_column :charges, :discount_duration_intervals, :integer unless column_exists?(:charges, :discount_duration_intervals)
|
|
6
|
+
add_column :charges, :discount_source, :string unless column_exists?(:charges, :discount_source)
|
|
7
|
+
add_column :charges, :promotional_discount_id, :bigint unless column_exists?(:charges, :promotional_discount_id)
|
|
8
|
+
|
|
9
|
+
add_column :discount_plans, :used, :boolean, default: false unless column_exists?(:discount_plans, :used)
|
|
10
|
+
add_column :plan_users, :used, :boolean, default: false unless column_exists?(:plan_users, :used)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -29,6 +29,11 @@ class AddFailSafe < ActiveRecord::Migration[<%= rails_migration_version %>]
|
|
|
29
29
|
t.string :charge_id
|
|
30
30
|
t.boolean :test
|
|
31
31
|
t.float :price
|
|
32
|
+
t.decimal :discount_value, precision: 8, scale: 2
|
|
33
|
+
t.string :discount_type
|
|
34
|
+
t.integer :discount_duration_intervals
|
|
35
|
+
t.string :discount_source
|
|
36
|
+
t.bigint :promotional_discount_id
|
|
32
37
|
t.string :interval
|
|
33
38
|
t.integer :trial_days
|
|
34
39
|
t.datetime :billing_on
|
|
@@ -68,6 +73,7 @@ class AddFailSafe < ActiveRecord::Migration[<%= rails_migration_version %>]
|
|
|
68
73
|
t.integer :created_by
|
|
69
74
|
t.integer :app_id
|
|
70
75
|
t.string :discount_type
|
|
76
|
+
t.boolean :used, default: false
|
|
71
77
|
t.timestamps
|
|
72
78
|
end
|
|
73
79
|
create_table :extend_trials do |t|
|
|
@@ -84,6 +90,7 @@ class AddFailSafe < ActiveRecord::Migration[<%= rails_migration_version %>]
|
|
|
84
90
|
t.integer :plan_id
|
|
85
91
|
t.integer :app_id
|
|
86
92
|
t.integer :created_by
|
|
93
|
+
t.boolean :used, default: false
|
|
87
94
|
t.timestamps
|
|
88
95
|
end
|
|
89
96
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: app_manager
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rahul Tiwari @ Hulkapps
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: httparty
|
|
@@ -125,6 +125,7 @@ files:
|
|
|
125
125
|
- lib/app_manager/tasks/sync/local_app_manager.rake
|
|
126
126
|
- lib/app_manager/version.rb
|
|
127
127
|
- lib/generators/app_manager/install/install_generator.rb
|
|
128
|
+
- lib/generators/app_manager/install/templates/add_charge_discount_snapshot_and_used.erb
|
|
128
129
|
- lib/generators/app_manager/install/templates/add_discount_tables.erb
|
|
129
130
|
- lib/generators/app_manager/install/templates/add_external_charge_field.erb
|
|
130
131
|
- lib/generators/app_manager/install/templates/add_is_global_to_plans.erb
|