goodmail 0.3.1 → 0.4.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/.rubocop.yml +199 -0
- data/.simplecov +55 -0
- data/CHANGELOG.md +73 -0
- data/README.md +127 -55
- data/Rakefile +12 -1
- data/context7.json +4 -0
- data/examples/PAY_TESTING_GUIDE.md +497 -0
- data/examples/pay_goodmailer.rb +518 -0
- data/lib/goodmail/action_mailer_integration.rb +286 -0
- data/lib/goodmail/builder.rb +304 -15
- data/lib/goodmail/configuration.rb +40 -3
- data/lib/goodmail/dispatcher.rb +41 -42
- data/lib/goodmail/email.rb +82 -46
- data/lib/goodmail/layout.erb +0 -1
- data/lib/goodmail/layout.rb +1 -1
- data/lib/goodmail/mailer.rb +19 -44
- data/lib/goodmail/plaintext.rb +236 -0
- data/lib/goodmail/version.rb +1 -1
- data/lib/goodmail.rb +8 -4
- metadata +22 -29
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# PayGoodmailer - Production-ready Pay gem + Goodmail integration
|
|
4
|
+
#
|
|
5
|
+
# This mailer provides beautiful, i18n-ready transactional emails for all
|
|
6
|
+
# Pay gem notifications with a centralized approach and comprehensive features.
|
|
7
|
+
#
|
|
8
|
+
# FEATURES:
|
|
9
|
+
# - All 7 Pay notification types (receipt, refund, subscriptions, etc.)
|
|
10
|
+
# - Full i18n support with sensible English defaults
|
|
11
|
+
# - Centralized email rendering logic
|
|
12
|
+
# - Automatic List-Unsubscribe header handling
|
|
13
|
+
# - Receipt PDF attachment support
|
|
14
|
+
# - Extra billing info support
|
|
15
|
+
# - URL helper integration
|
|
16
|
+
#
|
|
17
|
+
# SETUP INSTRUCTIONS:
|
|
18
|
+
#
|
|
19
|
+
# 1. Copy this file to your Rails app:
|
|
20
|
+
# app/mailers/pay_goodmailer.rb
|
|
21
|
+
#
|
|
22
|
+
# 2. Configure Pay to use this mailer in config/initializers/pay.rb:
|
|
23
|
+
#
|
|
24
|
+
# Pay.setup do |config|
|
|
25
|
+
# config.parent_mailer = "ApplicationMailer"
|
|
26
|
+
# config.mailer = "PayGoodmailer"
|
|
27
|
+
# # ... other Pay configuration
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# 3. Ensure Goodmail is configured in config/initializers/goodmail.rb
|
|
31
|
+
#
|
|
32
|
+
# 4. (Optional) Add i18n translations to config/locales/pay.en.yml
|
|
33
|
+
#
|
|
34
|
+
# 5. Customize URL helpers and email content to match your app
|
|
35
|
+
#
|
|
36
|
+
# SOURCES:
|
|
37
|
+
# - Pay::UserMailer: https://github.com/pay-rails/pay/blob/main/app/mailers/pay/user_mailer.rb
|
|
38
|
+
# - Charge webhooks: https://github.com/pay-rails/pay/blob/main/lib/pay/stripe/webhooks/charge_succeeded.rb
|
|
39
|
+
# - Subscription webhooks: https://github.com/pay-rails/pay/tree/main/lib/pay/stripe/webhooks
|
|
40
|
+
# - Pay::Charge model: https://github.com/pay-rails/pay/blob/main/app/models/pay/charge.rb
|
|
41
|
+
# - Pay configuration: https://github.com/pay-rails/pay/blob/main/docs/2_configuration.md
|
|
42
|
+
#
|
|
43
|
+
class PayGoodmailer < Pay.parent_mailer.constantize
|
|
44
|
+
include Rails.application.routes.url_helpers
|
|
45
|
+
|
|
46
|
+
# Sends a payment receipt email
|
|
47
|
+
#
|
|
48
|
+
# Triggered by: charge.succeeded webhook
|
|
49
|
+
# Params: params[:pay_customer], params[:pay_charge]
|
|
50
|
+
def receipt
|
|
51
|
+
pay_charge = params[:pay_charge]
|
|
52
|
+
|
|
53
|
+
formatted_date = localize_date(pay_charge.created_at)
|
|
54
|
+
|
|
55
|
+
# Capture URLs before the block
|
|
56
|
+
# receipt_link = receipt_url(pay_charge) # Uncomment and customize
|
|
57
|
+
|
|
58
|
+
send_pay_goodmail(:receipt) do
|
|
59
|
+
# Add a friendly GIF (customize the URL to your own!)
|
|
60
|
+
image("https://example.com/mailers/ok.gif", "Payment confirmed!", width: 250)
|
|
61
|
+
|
|
62
|
+
h1 t('pay.mailer.receipt.title', default: 'Payment Received!')
|
|
63
|
+
|
|
64
|
+
text t('pay.mailer.receipt.message',
|
|
65
|
+
application_name: app_name,
|
|
66
|
+
default: "Thanks for your payment! We received your #{app_name} subscription payment. We appreciate your business!"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
space
|
|
70
|
+
h3 t('pay.mailer.receipt.details_title', default: 'Payment Details')
|
|
71
|
+
price_row t('pay.mailer.receipt.amount', default: 'Amount'), pay_charge.amount_with_currency
|
|
72
|
+
price_row t('pay.mailer.receipt.charged_to', default: 'Charged to'), pay_charge.charged_to
|
|
73
|
+
price_row t('pay.mailer.receipt.date', default: 'Date'), formatted_date
|
|
74
|
+
|
|
75
|
+
# Optional: Add extra billing info if your users have it
|
|
76
|
+
if pay_charge.customer.owner.respond_to?(:extra_billing_info?) && pay_charge.customer.owner.extra_billing_info?
|
|
77
|
+
space
|
|
78
|
+
text pay_charge.customer.owner.extra_billing_info
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
space
|
|
82
|
+
text t('pay.mailer.receipt.reference_info',
|
|
83
|
+
transaction_id: pay_charge.id,
|
|
84
|
+
formatted_date: formatted_date,
|
|
85
|
+
default: "For your records: this payment was processed on #{formatted_date} (Transaction ID: #{pay_charge.id})."
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Optional: Add a button to view receipt
|
|
89
|
+
# space
|
|
90
|
+
# button t('pay.mailer.receipt.view_button', default: 'View Receipt'), receipt_link
|
|
91
|
+
|
|
92
|
+
space
|
|
93
|
+
text t('pay.mailer.receipt.questions', default: 'Questions? Just reply to this email — we\'re here to help!')
|
|
94
|
+
|
|
95
|
+
sign
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Sends a refund notification email
|
|
100
|
+
#
|
|
101
|
+
# Triggered by: charge.refunded webhook
|
|
102
|
+
# Params: params[:pay_customer], params[:pay_charge]
|
|
103
|
+
def refund
|
|
104
|
+
pay_charge = params[:pay_charge]
|
|
105
|
+
|
|
106
|
+
formatted_date = localize_date(pay_charge.created_at)
|
|
107
|
+
|
|
108
|
+
send_pay_goodmail(:refund) do
|
|
109
|
+
# Add a friendly GIF (customize the URL to your own!)
|
|
110
|
+
image("https://example.com/mailers/ok.gif", "Refund confirmed!", width: 250)
|
|
111
|
+
|
|
112
|
+
h1 t('pay.mailer.refund.title', default: 'Refund Processed')
|
|
113
|
+
|
|
114
|
+
text t('pay.mailer.refund.message',
|
|
115
|
+
application_name: app_name,
|
|
116
|
+
default: "We've processed your refund. The money should be back in your account soon!"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
space
|
|
120
|
+
h3 t('pay.mailer.refund.details_title', default: 'Refund Details')
|
|
121
|
+
price_row t('pay.mailer.refund.amount', default: 'Refund Amount'), pay_charge.amount_refunded_with_currency
|
|
122
|
+
price_row t('pay.mailer.refund.original_charge', default: 'Original Charge'), pay_charge.amount_with_currency
|
|
123
|
+
price_row t('pay.mailer.refund.date', default: 'Date'), formatted_date
|
|
124
|
+
|
|
125
|
+
space
|
|
126
|
+
text t('pay.mailer.refund.reference_info',
|
|
127
|
+
transaction_id: pay_charge.id,
|
|
128
|
+
default: "Transaction ID: #{pay_charge.id}"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
space
|
|
132
|
+
text t('pay.mailer.refund.processing_time',
|
|
133
|
+
default: 'The refund will show up on your statement within 5-10 business days, depending on your bank.'
|
|
134
|
+
)
|
|
135
|
+
text t('pay.mailer.refund.questions', default: 'Questions? Just reply to this email!')
|
|
136
|
+
|
|
137
|
+
sign
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Sends a subscription renewal reminder
|
|
142
|
+
# (Used for annual subscriptions that will renew soon)
|
|
143
|
+
#
|
|
144
|
+
# Triggered by: invoice.upcoming webhook (for annual subscriptions)
|
|
145
|
+
# Params: params[:pay_customer], params[:pay_subscription], params[:date]
|
|
146
|
+
def subscription_renewing
|
|
147
|
+
pay_subscription = params[:pay_subscription]
|
|
148
|
+
renewal_date = params[:date]
|
|
149
|
+
|
|
150
|
+
formatted_renewal_date = renewal_date ? localize_date(renewal_date) : nil
|
|
151
|
+
days_until_renewal = renewal_date ? (renewal_date.to_date - Date.current).to_i : nil
|
|
152
|
+
|
|
153
|
+
# Capture URLs before the block
|
|
154
|
+
billing_link = billing_url
|
|
155
|
+
|
|
156
|
+
send_pay_goodmail(:subscription_renewing) do
|
|
157
|
+
h1 t('pay.mailer.subscription_renewing.title', default: 'Your Subscription Renews Soon')
|
|
158
|
+
|
|
159
|
+
text t('pay.mailer.subscription_renewing.message',
|
|
160
|
+
application_name: app_name,
|
|
161
|
+
days: days_until_renewal,
|
|
162
|
+
default: "Just a heads up — your #{app_name} subscription will renew in #{days_until_renewal} days."
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
space
|
|
166
|
+
h3 t('pay.mailer.subscription_renewing.details_title', default: 'Subscription Details')
|
|
167
|
+
price_row t('pay.mailer.subscription_renewing.plan', default: 'Plan'), pay_subscription.name
|
|
168
|
+
price_row t('pay.mailer.subscription_renewing.status', default: 'Status'), pay_subscription.status.humanize
|
|
169
|
+
|
|
170
|
+
if formatted_renewal_date
|
|
171
|
+
price_row t('pay.mailer.subscription_renewing.next_billing', default: 'Next Billing Date'), formatted_renewal_date
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
space
|
|
175
|
+
text t('pay.mailer.subscription_renewing.auto_renewal',
|
|
176
|
+
default: 'No action needed — everything will renew automatically. We\'ll charge your payment method on file.'
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Optional: Add manage subscription button
|
|
180
|
+
# space
|
|
181
|
+
# button t('pay.mailer.subscription_renewing.manage_button', default: 'Manage Subscription'), billing_link
|
|
182
|
+
|
|
183
|
+
space
|
|
184
|
+
text t('pay.mailer.subscription_renewing.questions', default: 'Questions? Just reply to this email!')
|
|
185
|
+
|
|
186
|
+
sign
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Sends a notification when payment action is required
|
|
191
|
+
# (e.g., 3D Secure authentication, expired card)
|
|
192
|
+
#
|
|
193
|
+
# Triggered by: invoice.payment_action_required webhook
|
|
194
|
+
# Params: params[:pay_customer], params[:pay_subscription]
|
|
195
|
+
def payment_action_required
|
|
196
|
+
pay_subscription = params[:pay_subscription]
|
|
197
|
+
|
|
198
|
+
# Capture URLs before the block
|
|
199
|
+
billing_link = billing_url
|
|
200
|
+
|
|
201
|
+
send_pay_goodmail(:payment_action_required) do
|
|
202
|
+
h1 t('pay.mailer.payment_action_required.title', default: 'Quick Action Needed')
|
|
203
|
+
|
|
204
|
+
text t('pay.mailer.payment_action_required.message',
|
|
205
|
+
subscription_name: pay_subscription.name,
|
|
206
|
+
default: "We need your help! Your payment for #{pay_subscription.name} needs additional verification."
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
space
|
|
210
|
+
text t('pay.mailer.payment_action_required.description',
|
|
211
|
+
default: 'This is a security thing (it happens!) — just verify your payment to keep everything running smoothly.'
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
space
|
|
215
|
+
button t('pay.mailer.payment_action_required.action_button', default: 'Complete Payment'), billing_link
|
|
216
|
+
|
|
217
|
+
space
|
|
218
|
+
text t('pay.mailer.payment_action_required.urgency',
|
|
219
|
+
default: 'Try to do this within the next few days so your service doesn\'t get interrupted.'
|
|
220
|
+
)
|
|
221
|
+
text t('pay.mailer.payment_action_required.questions', default: 'Need help? Just reply to this email!')
|
|
222
|
+
|
|
223
|
+
sign
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Sends a reminder that the trial period is ending soon
|
|
228
|
+
#
|
|
229
|
+
# Triggered by: customer.subscription.trial_will_end webhook (3 days before trial ends)
|
|
230
|
+
# Params: params[:pay_customer], params[:pay_subscription]
|
|
231
|
+
def subscription_trial_will_end
|
|
232
|
+
pay_subscription = params[:pay_subscription]
|
|
233
|
+
|
|
234
|
+
formatted_trial_end = pay_subscription.trial_ends_at ? localize_date(pay_subscription.trial_ends_at) : nil
|
|
235
|
+
days_remaining = pay_subscription.trial_ends_at ? (pay_subscription.trial_ends_at.to_date - Date.current).to_i : nil
|
|
236
|
+
|
|
237
|
+
# Capture URLs before the block
|
|
238
|
+
billing_link = billing_url
|
|
239
|
+
|
|
240
|
+
send_pay_goodmail(:subscription_trial_will_end) do
|
|
241
|
+
h1 t('pay.mailer.subscription_trial_will_end.title', default: 'Your Trial Ends Soon')
|
|
242
|
+
|
|
243
|
+
text t('pay.mailer.subscription_trial_will_end.message',
|
|
244
|
+
application_name: app_name,
|
|
245
|
+
days: days_remaining,
|
|
246
|
+
default: "Quick reminder — your #{app_name} trial wraps up in #{days_remaining} days."
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
space
|
|
250
|
+
h3 t('pay.mailer.subscription_trial_will_end.details_title', default: 'Trial Details')
|
|
251
|
+
price_row t('pay.mailer.subscription_trial_will_end.plan', default: 'Plan'), pay_subscription.name
|
|
252
|
+
|
|
253
|
+
if formatted_trial_end
|
|
254
|
+
price_row t('pay.mailer.subscription_trial_will_end.trial_ends', default: 'Trial Ends'), formatted_trial_end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
space
|
|
258
|
+
text t('pay.mailer.subscription_trial_will_end.continue_message',
|
|
259
|
+
default: 'After your trial, your subscription will continue automatically. Just make sure your payment info is current!'
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# Optional: Add manage subscription button
|
|
263
|
+
# space
|
|
264
|
+
# button t('pay.mailer.subscription_trial_will_end.manage_button', default: 'Manage My Subscription'), billing_link
|
|
265
|
+
|
|
266
|
+
space
|
|
267
|
+
text t('pay.mailer.subscription_trial_will_end.questions', default: 'Questions? Just reply!')
|
|
268
|
+
|
|
269
|
+
sign
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Sends a notification that the trial period has ended
|
|
274
|
+
#
|
|
275
|
+
# Triggered by: When trial_ends_at passes (triggered by trial_will_end webhook if trial already ended)
|
|
276
|
+
# Params: params[:pay_customer], params[:pay_subscription]
|
|
277
|
+
def subscription_trial_ended
|
|
278
|
+
pay_subscription = params[:pay_subscription]
|
|
279
|
+
|
|
280
|
+
# Capture URLs before the block
|
|
281
|
+
billing_link = billing_url
|
|
282
|
+
# dashboard_link = dashboard_url
|
|
283
|
+
|
|
284
|
+
send_pay_goodmail(:subscription_trial_ended) do
|
|
285
|
+
h1 t('pay.mailer.subscription_trial_ended.title', default: 'Your Trial Just Ended')
|
|
286
|
+
|
|
287
|
+
text t('pay.mailer.subscription_trial_ended.message',
|
|
288
|
+
application_name: app_name,
|
|
289
|
+
default: "Your #{app_name} trial period is now complete."
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
space
|
|
293
|
+
h3 t('pay.mailer.subscription_trial_ended.details_title', default: 'Subscription Details')
|
|
294
|
+
price_row t('pay.mailer.subscription_trial_ended.plan', default: 'Plan'), pay_subscription.name
|
|
295
|
+
price_row t('pay.mailer.subscription_trial_ended.status', default: 'Status'), pay_subscription.status.humanize
|
|
296
|
+
|
|
297
|
+
space
|
|
298
|
+
|
|
299
|
+
if subscription_continuing_after_trial?(pay_subscription)
|
|
300
|
+
text t('pay.mailer.subscription_trial_ended.continue_message',
|
|
301
|
+
default: 'Thanks for sticking with us! Your subscription is now active and billing normally.'
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# Optional: Add dashboard button
|
|
305
|
+
# space
|
|
306
|
+
# button t('pay.mailer.subscription_trial_ended.dashboard_button', default: 'Go to My Dashboard'), dashboard_link
|
|
307
|
+
else
|
|
308
|
+
text t('pay.mailer.subscription_trial_ended.inactive_message',
|
|
309
|
+
default: 'Looks like we couldn\'t activate your subscription. Update your payment method to keep going!'
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
space
|
|
313
|
+
button t('pay.mailer.subscription_trial_ended.update_button', default: 'Update Payment Info'), billing_link
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
space
|
|
317
|
+
text t('pay.mailer.subscription_trial_ended.questions', default: 'Need help? Just reply to this email!')
|
|
318
|
+
|
|
319
|
+
sign
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# Sends a notification when a payment has failed
|
|
324
|
+
#
|
|
325
|
+
# Triggered by: invoice.payment_failed webhook
|
|
326
|
+
# Params: params[:pay_customer], params[:pay_subscription]
|
|
327
|
+
def payment_failed
|
|
328
|
+
pay_subscription = params[:pay_subscription]
|
|
329
|
+
|
|
330
|
+
# Capture URLs before the block
|
|
331
|
+
billing_link = billing_url
|
|
332
|
+
|
|
333
|
+
send_pay_goodmail(:payment_failed) do
|
|
334
|
+
# Add a friendly "uh-oh" GIF (customize the URL to your own!)
|
|
335
|
+
image("https://example.com/mailers/uh-oh.gif", "Uh-oh!", width: 150)
|
|
336
|
+
|
|
337
|
+
h1 t('pay.mailer.payment_failed.title', default: 'Uh-oh! Payment Issue')
|
|
338
|
+
|
|
339
|
+
text t('pay.mailer.payment_failed.message',
|
|
340
|
+
application_name: app_name,
|
|
341
|
+
subscription_name: pay_subscription.name,
|
|
342
|
+
default: "We tried to charge your payment method for #{pay_subscription.name}, but it didn't go through."
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
space
|
|
346
|
+
h3 t('pay.mailer.payment_failed.details_title', default: 'Subscription Details')
|
|
347
|
+
price_row t('pay.mailer.payment_failed.subscription', default: 'Subscription'), pay_subscription.name
|
|
348
|
+
|
|
349
|
+
space
|
|
350
|
+
text t('pay.mailer.payment_failed.reasons_title', default: 'This usually happens because:')
|
|
351
|
+
text t('pay.mailer.payment_failed.reasons',
|
|
352
|
+
default: "• Not enough funds in the account\n• Expired card\n• Your bank declined it\n• Wrong billing address"
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
space
|
|
356
|
+
text t('pay.mailer.payment_failed.action_message',
|
|
357
|
+
default: 'No worries — just update your payment info and you\'re good to go!'
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
space
|
|
361
|
+
button t('pay.mailer.payment_failed.update_button', default: 'Update Payment Info'), billing_link
|
|
362
|
+
|
|
363
|
+
space
|
|
364
|
+
text t('pay.mailer.payment_failed.urgency',
|
|
365
|
+
default: 'Please update it in the next few days so we don\'t have to pause your subscription.'
|
|
366
|
+
)
|
|
367
|
+
text t('pay.mailer.payment_failed.questions', default: 'Need help? Just reply to this email!')
|
|
368
|
+
|
|
369
|
+
sign
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
private
|
|
374
|
+
|
|
375
|
+
# Centralized method to send Pay emails with Goodmail
|
|
376
|
+
# This method:
|
|
377
|
+
# - Gets mail arguments from Pay's configuration
|
|
378
|
+
# - Sets up i18n subject and preheader
|
|
379
|
+
# - Lets Goodmail render the DSL and call Action Mailer's `mail`
|
|
380
|
+
# - Adds List-Unsubscribe / RFC 8058 one-click headers if configured
|
|
381
|
+
# - Attaches receipts for receipt emails
|
|
382
|
+
# - Sends the email via Action Mailer
|
|
383
|
+
def send_pay_goodmail(action_sym, &dsl_block)
|
|
384
|
+
# Ensure pay_customer is set in params (get from subscription if needed)
|
|
385
|
+
# This is necessary because Pay.mail_arguments expects params[:pay_customer] to exist
|
|
386
|
+
if params[:pay_customer].nil? && params[:pay_subscription].present?
|
|
387
|
+
params[:pay_customer] = params[:pay_subscription].customer
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# Get the mail arguments from Pay's configuration (same as Pay::UserMailer does)
|
|
391
|
+
pay_mail_arguments = instance_exec(&Pay.mail_arguments)
|
|
392
|
+
|
|
393
|
+
# Construct subject with i18n support
|
|
394
|
+
custom_subject = t(
|
|
395
|
+
"pay.mailer.#{action_sym}.subject",
|
|
396
|
+
application_name: app_name,
|
|
397
|
+
default: pay_mail_arguments[:subject] || default_subject_for(action_sym)
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
# Update subject in mail arguments
|
|
401
|
+
pay_mail_arguments[:subject] = custom_subject
|
|
402
|
+
|
|
403
|
+
preheader = t(
|
|
404
|
+
"pay.mailer.#{action_sym}.preheader",
|
|
405
|
+
application_name: app_name,
|
|
406
|
+
default: custom_subject
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
# Pay's optional receipt helper exposes `receipt` plus
|
|
410
|
+
# `receipt_filename` / `filename`; attach only when that helper is
|
|
411
|
+
# actually mixed into the charge object.
|
|
412
|
+
# Source: https://github.com/pay-rails/pay/blob/v11.4.3/lib/pay/receipts.rb#L3-L8
|
|
413
|
+
if action_sym == :receipt && params[:pay_charge]&.respond_to?(:receipt)
|
|
414
|
+
filename =
|
|
415
|
+
if params[:pay_charge].respond_to?(:receipt_filename)
|
|
416
|
+
params[:pay_charge].receipt_filename
|
|
417
|
+
else
|
|
418
|
+
params[:pay_charge].filename
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
attachments[filename] = params[:pay_charge].receipt
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# Preserve Pay.mail_arguments as the envelope/header source of truth, just
|
|
425
|
+
# as Pay::UserMailer does, and let Goodmail own the Goodmail-specific
|
|
426
|
+
# render keys, multipart body, attachments, and unsubscribe headers.
|
|
427
|
+
#
|
|
428
|
+
# Sources:
|
|
429
|
+
# - Pay::UserMailer calls `mail mail_arguments`:
|
|
430
|
+
# https://github.com/pay-rails/pay/blob/v11.4.3/app/mailers/pay/user_mailer.rb#L2-L38
|
|
431
|
+
# - Pay.mail_arguments default:
|
|
432
|
+
# https://github.com/pay-rails/pay/blob/v11.4.3/lib/pay.rb#L93-L101
|
|
433
|
+
goodmail_mail(pay_mail_arguments, preheader: preheader, &dsl_block)
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# Avoid depending on Pay's instance predicate here; older Pay versions have
|
|
437
|
+
# had status predicate differences across loaded model code. The email only
|
|
438
|
+
# needs to choose active-vs-inactive copy, so persisted status fields are the
|
|
439
|
+
# stable source of truth.
|
|
440
|
+
# Source: https://github.com/pay-rails/pay/blob/v11.4.3/app/models/pay/subscription.rb#L97-L102
|
|
441
|
+
def subscription_continuing_after_trial?(pay_subscription)
|
|
442
|
+
return false unless %w[active trialing].include?(pay_subscription.status.to_s)
|
|
443
|
+
return true unless pay_subscription.respond_to?(:ends_at) && pay_subscription.ends_at.present?
|
|
444
|
+
|
|
445
|
+
pay_subscription.ends_at.future?
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# Localize date using i18n
|
|
449
|
+
def localize_date(date)
|
|
450
|
+
I18n.l(date, format: :long)
|
|
451
|
+
rescue
|
|
452
|
+
date.strftime("%B %d, %Y at %I:%M %p")
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
# Get application name from Goodmail config
|
|
456
|
+
def app_name
|
|
457
|
+
Goodmail.config.company_name
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
# i18n helper (delegate to I18n.t)
|
|
461
|
+
def t(key, **options)
|
|
462
|
+
I18n.t(key, **options)
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
# Default subjects for each email type
|
|
466
|
+
def default_subject_for(action_sym)
|
|
467
|
+
{
|
|
468
|
+
receipt: "Receipt for your payment",
|
|
469
|
+
refund: "Refund processed",
|
|
470
|
+
subscription_renewing: "Your subscription will renew soon",
|
|
471
|
+
payment_action_required: "Action required for your payment",
|
|
472
|
+
subscription_trial_will_end: "Your trial is ending soon",
|
|
473
|
+
subscription_trial_ended: "Your trial has ended",
|
|
474
|
+
payment_failed: "Payment failed"
|
|
475
|
+
}[action_sym] || "Notification from #{app_name}"
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
# ============================================================================
|
|
479
|
+
# URL HELPERS - CUSTOMIZE THESE TO MATCH YOUR APP
|
|
480
|
+
# ============================================================================
|
|
481
|
+
|
|
482
|
+
# Generates URL for billing/payment method management
|
|
483
|
+
# CUSTOMIZE THIS to match your app's routing
|
|
484
|
+
def billing_url
|
|
485
|
+
# Example implementations:
|
|
486
|
+
# - billing_url (if you have a billing route)
|
|
487
|
+
# - account_billing_url
|
|
488
|
+
# - edit_user_registration_url(anchor: 'billing')
|
|
489
|
+
# - "https://yourdomain.com/billing"
|
|
490
|
+
|
|
491
|
+
# Default placeholder:
|
|
492
|
+
root_url
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# Generates URL for dashboard
|
|
496
|
+
# CUSTOMIZE THIS to match your app's routing
|
|
497
|
+
def dashboard_url
|
|
498
|
+
# Example implementations:
|
|
499
|
+
# - dashboard_url
|
|
500
|
+
# - root_url
|
|
501
|
+
# - "https://yourdomain.com/dashboard"
|
|
502
|
+
|
|
503
|
+
# Default placeholder:
|
|
504
|
+
root_url
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
# Generates URL for viewing a receipt
|
|
508
|
+
# CUSTOMIZE THIS to match your app's routing
|
|
509
|
+
def receipt_url(pay_charge)
|
|
510
|
+
# Example implementations:
|
|
511
|
+
# - receipt_url(pay_charge)
|
|
512
|
+
# - charge_url(pay_charge)
|
|
513
|
+
# - "https://yourdomain.com/receipts/#{pay_charge.id}"
|
|
514
|
+
|
|
515
|
+
# Default placeholder:
|
|
516
|
+
root_url
|
|
517
|
+
end
|
|
518
|
+
end
|