97 lines
4.2 KiB
C#
97 lines
4.2 KiB
C#
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
|
|
};
|
|
}
|