-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
20 changed files
with
253 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
using Aptabase.Data; | ||
using Aptabase.Features.Authentication; | ||
using Dapper; | ||
|
||
namespace Aptabase.Features.Billing; | ||
|
||
public interface IBillingQueries | ||
{ | ||
Task<UserIdentity[]> GetTrialsDueSoon(); | ||
Task<Subscription?> GetUserSubscription(UserIdentity user); | ||
Task<FreeSubscription> GetUserFreeTierOrTrial(UserIdentity user); | ||
Task<string[]> GetOwnedAppIds(UserIdentity user); | ||
} | ||
|
||
public class BillingQueries : IBillingQueries | ||
{ | ||
private readonly IDbContext _db; | ||
|
||
public BillingQueries(IDbContext db) | ||
{ | ||
_db = db ?? throw new ArgumentNullException(nameof(db)); | ||
} | ||
|
||
public async Task<Subscription?> GetUserSubscription(UserIdentity user) | ||
{ | ||
return await _db.Connection.QueryFirstOrDefaultAsync<Subscription>( | ||
@"SELECT * FROM subscriptions | ||
WHERE owner_id = @userId | ||
ORDER BY created_at DESC LIMIT 1", | ||
new { userId = user.Id }); | ||
} | ||
|
||
public async Task<FreeSubscription> GetUserFreeTierOrTrial(UserIdentity user) | ||
{ | ||
return await _db.Connection.QueryFirstAsync<FreeSubscription>( | ||
@"SELECT free_quota, free_trial_ends_at FROM users WHERE id = @userId", | ||
new { userId = user.Id }); | ||
} | ||
|
||
public async Task<string[]> GetOwnedAppIds(UserIdentity user) | ||
{ | ||
var releaseAppIds = await _db.Connection.QueryAsync<string>(@"SELECT id FROM apps WHERE owner_id = @userId", new { userId = user.Id }); | ||
var debugAppIds = releaseAppIds.Select(id => $"{id}_DEBUG"); | ||
return releaseAppIds.Concat(debugAppIds).ToArray(); | ||
} | ||
|
||
public async Task<UserIdentity[]> GetTrialsDueSoon() | ||
{ | ||
var users = await _db.Connection.QueryAsync<UserIdentity>( | ||
@"SELECT DISTINCT u.id, u.name, u.email | ||
FROM users u | ||
LEFT JOIN subscriptions s | ||
ON s.owner_id = u.id | ||
INNER JOIN apps a | ||
ON a.owner_id = u.id | ||
AND a.has_events = true | ||
WHERE u.free_trial_ends_at = now() + INTERVAL '7 DAY' | ||
AND s.id IS NULL"); | ||
return users.ToArray(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
using Aptabase.Features.Notification; | ||
using Sgbj.Cron; | ||
|
||
namespace Aptabase.Features.Billing; | ||
|
||
public class TrialNotificationCronJob : BackgroundService | ||
{ | ||
private readonly IBillingQueries _billingQueries; | ||
private readonly IEmailClient _emailClient; | ||
private readonly EnvSettings _env; | ||
private readonly ILogger _logger; | ||
|
||
public TrialNotificationCronJob(IBillingQueries billingQueries, IEmailClient emailClient, EnvSettings env, ILogger<TrialNotificationCronJob> logger) | ||
{ | ||
_billingQueries = billingQueries ?? throw new ArgumentNullException(nameof(billingQueries)); | ||
_emailClient = emailClient ?? throw new ArgumentNullException(nameof(emailClient)); | ||
_env = env ?? throw new ArgumentNullException(nameof(env)); | ||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | ||
} | ||
|
||
protected override async Task ExecuteAsync(CancellationToken cancellationToken) | ||
{ | ||
try | ||
{ | ||
_logger.LogInformation("TrialNotificationCronJob is starting."); | ||
|
||
using var timer = new CronTimer("0 0 * * *", TimeZoneInfo.Utc); | ||
|
||
while (await timer.WaitForNextTickAsync(cancellationToken)) | ||
{ | ||
_logger.LogInformation("Searching for users to notify about trial expiration."); | ||
var users = await _billingQueries.GetTrialsDueSoon(); | ||
foreach (var user in users) | ||
{ | ||
_logger.LogInformation("Sending trial notification to {name} ({user})", user.Name, user.Email); | ||
await _emailClient.SendEmailAsync(user.Email, "Your Trial ends in 5 days", "TrialEndsSoon", new() | ||
{ | ||
{ "name", user.Name }, | ||
{ "url", $"{_env.SelfBaseUrl}/billing" }, | ||
}, cancellationToken); | ||
} | ||
|
||
_logger.LogInformation("Sent trial notifications to {count} users", users.Length); | ||
} | ||
} | ||
catch (OperationCanceledException) | ||
{ | ||
_logger.LogInformation("TrialNotificationCronJob stopped."); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<p>Hi ##NAME##,</p> | ||
<p> | ||
Your free 30-day trial of Aptabase Analytics ends in 5 days. That means you'll lose access to the dashboard and we'll | ||
stop processing events from your apps. | ||
</p> | ||
<p>If you want to continue using Aptabase for your app analytics, you can upgrade to one of the paid plans.</p> | ||
<p> | ||
<a target="_blank" rel="noopener noreferrer nofollow" href="##URL##">Account Billing</a> | ||
</p> | ||
<p>Thanks for trying Aptabase!</p> | ||
<p><em>- Guilherme, Founder of Aptabase</em></p> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.