Add freemium access economy and token store
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
using MongoDB.Driver;
|
||||
using QuestionsApi.Models;
|
||||
|
||||
namespace QuestionsApi.Services;
|
||||
|
||||
public sealed class AccessPurchaseStore
|
||||
{
|
||||
private readonly IMongoCollection<AccessPurchase> _purchases;
|
||||
|
||||
public AccessPurchaseStore(MongoDatabaseProvider mongo)
|
||||
{
|
||||
_purchases = mongo.Database.GetCollection<AccessPurchase>("AccessPurchases");
|
||||
_purchases.Indexes.CreateMany([
|
||||
new CreateIndexModel<AccessPurchase>(
|
||||
Builders<AccessPurchase>.IndexKeys.Ascending(value => value.UserId).Descending(value => value.CreatedUtc)),
|
||||
new CreateIndexModel<AccessPurchase>(
|
||||
Builders<AccessPurchase>.IndexKeys.Ascending(value => value.ProviderSessionId),
|
||||
new CreateIndexOptions<AccessPurchase>
|
||||
{
|
||||
Unique = true,
|
||||
PartialFilterExpression = Builders<AccessPurchase>.Filter.Ne(value => value.ProviderSessionId, "")
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
public async Task<AccessPurchase> CreateAsync(string userId, AccessProduct product)
|
||||
{
|
||||
var purchase = new AccessPurchase
|
||||
{
|
||||
UserId = userId,
|
||||
Sku = product.Sku,
|
||||
AmountCents = product.AmountCents,
|
||||
Currency = product.Currency
|
||||
};
|
||||
await _purchases.InsertOneAsync(purchase);
|
||||
return purchase;
|
||||
}
|
||||
|
||||
public async Task<AccessPurchase?> GetOwnedAsync(string id, string userId) =>
|
||||
await _purchases.Find(value => value.Id == id && value.UserId == userId).FirstOrDefaultAsync();
|
||||
|
||||
public async Task<AccessPurchase?> GetAsync(string id) =>
|
||||
await _purchases.Find(value => value.Id == id).FirstOrDefaultAsync();
|
||||
|
||||
public Task AttachSessionAsync(string id, string sessionId) =>
|
||||
_purchases.UpdateOneAsync(
|
||||
value => value.Id == id && value.Status == AccessPurchaseStatus.Pending,
|
||||
Builders<AccessPurchase>.Update
|
||||
.Set(value => value.ProviderSessionId, sessionId)
|
||||
.Set(value => value.UpdatedUtc, DateTime.UtcNow));
|
||||
|
||||
public Task MarkCheckoutFailedAsync(string id) =>
|
||||
_purchases.UpdateOneAsync(
|
||||
value => value.Id == id && value.Status == AccessPurchaseStatus.Pending,
|
||||
Builders<AccessPurchase>.Update
|
||||
.Set(value => value.Status, AccessPurchaseStatus.Failed)
|
||||
.Set(value => value.UpdatedUtc, DateTime.UtcNow));
|
||||
|
||||
public async Task<AccessPurchase?> ApplyProviderStatusAsync(
|
||||
string id, string sessionId, long amountCents, string currency,
|
||||
string status, string paymentId, string eventId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(id) || string.IsNullOrWhiteSpace(sessionId)) return null;
|
||||
var filters = Builders<AccessPurchase>.Filter;
|
||||
var filter = filters.Eq(value => value.Id, id) &
|
||||
filters.Eq(value => value.AmountCents, amountCents) &
|
||||
filters.Eq(value => value.Currency, currency.ToLowerInvariant()) &
|
||||
(filters.Eq(value => value.ProviderSessionId, sessionId) |
|
||||
filters.Eq(value => value.ProviderSessionId, "")) &
|
||||
filters.Not(filters.AnyEq(value => value.ProviderEventIds, eventId));
|
||||
var update = Builders<AccessPurchase>.Update
|
||||
.Set(value => value.Status, status)
|
||||
.Set(value => value.ProviderSessionId, sessionId)
|
||||
.Set(value => value.ProviderPaymentId, paymentId)
|
||||
.AddToSet(value => value.ProviderEventIds, eventId)
|
||||
.Set(value => value.UpdatedUtc, DateTime.UtcNow);
|
||||
return await _purchases.FindOneAndUpdateAsync(filter, update,
|
||||
new FindOneAndUpdateOptions<AccessPurchase> { ReturnDocument = ReturnDocument.After });
|
||||
}
|
||||
|
||||
public Task MarkGrantAppliedAsync(string id) =>
|
||||
_purchases.UpdateOneAsync(
|
||||
value => value.Id == id && value.Status == AccessPurchaseStatus.Paid,
|
||||
Builders<AccessPurchase>.Update
|
||||
.Set(value => value.GrantApplied, true)
|
||||
.Set(value => value.UpdatedUtc, DateTime.UtcNow));
|
||||
|
||||
public static AccessPurchaseStatusResponse ToStatus(AccessPurchase purchase) => new()
|
||||
{
|
||||
PurchaseId = purchase.Id ?? "",
|
||||
Sku = purchase.Sku,
|
||||
AmountCents = purchase.AmountCents,
|
||||
Currency = purchase.Currency,
|
||||
Status = purchase.Status
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user