Handling of paddle purchases and add-ons.

This commit is contained in:
Burak Kaan Köse
2026-03-19 01:50:14 +01:00
parent f306f6eb1c
commit b0ee5c9974
26 changed files with 779 additions and 513 deletions
@@ -0,0 +1,7 @@
namespace Wino.Core.Domain.Enums;
public enum WinoAddOnProductType
{
AI_PACK,
UNLIMITED_ACCOUNTS
}
@@ -1,7 +1,5 @@
using System.Threading.Tasks;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Store;
namespace Wino.Core.Domain.Interfaces;
public interface IStoreManagementService
@@ -9,10 +7,10 @@ public interface IStoreManagementService
/// <summary>
/// Checks whether user has the type of an add-on purchased.
/// </summary>
Task<bool> HasProductAsync(StoreProductType productType);
Task<bool> HasProductAsync(WinoAddOnProductType productType);
/// <summary>
/// Attempts to purchase the given add-on.
/// </summary>
Task<StorePurchaseResult> PurchaseAsync(StoreProductType productType);
Task<StorePurchaseResult> PurchaseAsync(WinoAddOnProductType productType);
}
@@ -2,21 +2,25 @@
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Accounts;
using Wino.Mail.Api.Contracts.Ai;
using Wino.Mail.Api.Contracts.Auth;
using Wino.Mail.Api.Contracts.Billing;
using Wino.Mail.Api.Contracts.Common;
namespace Wino.Core.Domain.Interfaces;
public interface IWinoAccountApiClient
{
Task<ApiEnvelope<AuthResultDto>> RegisterAsync(string email, string password, CancellationToken cancellationToken = default);
Task<ApiEnvelope<AuthResultDto>> LoginAsync(string email, string password, CancellationToken cancellationToken = default);
Task<ApiEnvelope<AuthResultDto>> RefreshAsync(string refreshToken, CancellationToken cancellationToken = default);
Task<WinoAccountApiResult<AuthResultDto>> RegisterAsync(string email, string password, CancellationToken cancellationToken = default);
Task<WinoAccountApiResult<AuthResultDto>> LoginAsync(string email, string password, CancellationToken cancellationToken = default);
Task<WinoAccountApiResult<AuthResultDto>> RefreshAsync(string refreshToken, CancellationToken cancellationToken = default);
Task<ApiEnvelope<JsonElement>> LogoutAsync(string refreshToken, CancellationToken cancellationToken = default);
Task<ApiEnvelope<AuthUserDto>> GetCurrentUserAsync(CancellationToken cancellationToken = default);
Task<ApiEnvelope<AiStatusResultDto>> GetAiStatusAsync(CancellationToken cancellationToken = default);
Task<ApiEnvelope<string>> CreateCheckoutSessionAsync(string productId, CancellationToken cancellationToken = default);
Task<ApiEnvelope<CheckoutSessionResultDto>> CreateCheckoutSessionAsync(WinoAddOnProductType productId, CancellationToken cancellationToken = default);
Task<ApiEnvelope<CustomerPortalResultDto>> CreateCustomerPortalSessionAsync(CancellationToken cancellationToken = default);
Task<string?> GetSettingsAsync(CancellationToken cancellationToken = default);
Task<bool> SaveSettingsAsync(string settingsJson, CancellationToken cancellationToken = default);
}
@@ -2,9 +2,11 @@
using System.Threading;
using System.Threading.Tasks;
using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Accounts;
using Wino.Mail.Api.Contracts.Ai;
using Wino.Mail.Api.Contracts.Auth;
using Wino.Mail.Api.Contracts.Billing;
using Wino.Mail.Api.Contracts.Common;
namespace Wino.Core.Domain.Interfaces;
@@ -17,8 +19,10 @@ public interface IWinoAccountProfileService
Task<WinoAccount?> GetActiveAccountAsync();
Task<WinoAccount?> GetAuthenticatedAccountAsync(CancellationToken cancellationToken = default);
Task<bool> HasActiveAccountAsync();
Task<bool> HasAddOnAsync(WinoAddOnProductType productId, CancellationToken cancellationToken = default);
Task<ApiEnvelope<AuthUserDto>> GetCurrentUserAsync(CancellationToken cancellationToken = default);
Task<ApiEnvelope<AiStatusResultDto>> GetAiStatusAsync(CancellationToken cancellationToken = default);
Task<ApiEnvelope<string>> CreateCheckoutSessionAsync(string productId, CancellationToken cancellationToken = default);
Task<ApiEnvelope<CheckoutSessionResultDto>> CreateCheckoutSessionAsync(WinoAddOnProductType productId, CancellationToken cancellationToken = default);
Task<ApiEnvelope<CustomerPortalResultDto>> CreateCustomerPortalSessionAsync(CancellationToken cancellationToken = default);
Task SignOutAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,12 @@
#nullable enable
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Wino.Core.Domain.Models.Accounts;
namespace Wino.Core.Domain.Interfaces;
public interface IWinoAddOnService
{
Task<IReadOnlyList<WinoAddOnInfo>> GetAvailableAddOnsAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,26 @@
#nullable enable
namespace Wino.Core.Domain.Models.Accounts;
public sealed class WinoAccountApiResult<T>
{
public bool IsSuccess { get; init; }
public string? ErrorCode { get; init; }
public string? ErrorMessage { get; init; }
public T? Result { get; init; }
public static WinoAccountApiResult<T> Success(T result)
=> new()
{
IsSuccess = true,
Result = result
};
public static WinoAccountApiResult<T> Failure(string? errorCode, string? errorMessage = null)
=> new()
{
IsSuccess = false,
ErrorCode = errorCode,
ErrorMessage = errorMessage
};
}
@@ -7,6 +7,7 @@ public sealed class WinoAccountOperationResult
{
public bool IsSuccess { get; init; }
public string? ErrorCode { get; init; }
public string? ErrorMessage { get; init; }
public WinoAccount? Account { get; init; }
public static WinoAccountOperationResult Success(WinoAccount account)
@@ -16,10 +17,11 @@ public sealed class WinoAccountOperationResult
Account = account
};
public static WinoAccountOperationResult Failure(string? errorCode)
public static WinoAccountOperationResult Failure(string? errorCode, string? errorMessage = null)
=> new()
{
IsSuccess = false,
ErrorCode = errorCode
ErrorCode = errorCode,
ErrorMessage = errorMessage
};
}
@@ -0,0 +1,12 @@
using System;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Accounts;
public sealed record WinoAddOnInfo(
WinoAddOnProductType ProductType,
bool IsPurchased,
int? UsageCount = null,
int? UsageLimit = null,
double UsagePercentage = 0,
DateTimeOffset? RenewalDateUtc = null);
@@ -1,6 +0,0 @@
namespace Wino.Core.Domain.Models.Store;
public enum StoreProductType
{
UnlimitedAccounts
}
@@ -84,6 +84,7 @@
"Buttons_SyncAliases": "Synchronize Aliases",
"Buttons_TryAgain": "Try Again",
"Buttons_Yes": "Yes",
"Purchased": "Purchased",
"Sync_SynchronizingFolder": "Synchronizing {0} {1}%",
"Sync_DownloadedMessages": "Downloaded {0} messages from {1}",
"SyncAction_Archiving": "Archiving {0} mail(s)",
@@ -860,7 +861,7 @@
"WinoAccount_Management_SignedOutTitle": "Sign in to Wino Mail",
"WinoAccount_Management_SignedOutDescription": "Sign in or create an account to sync your email, access AI features, and manage your settings across devices.",
"WinoAccount_Management_ProfileSectionHeader": "Profile",
"WinoAccount_Management_AiPackSectionHeader": "AI Pack",
"WinoAccount_Management_AddOnsSectionHeader": "Wino Add-Ons",
"WinoAccount_Management_DataSectionHeader": "Data",
"WinoAccount_Management_AccountActionsSectionHeader": "Account actions",
"WinoAccount_Management_AccountCardTitle": "Account",
@@ -887,14 +888,10 @@
"WinoAccount_Management_AiPackFeatureTranslate": "Translate",
"WinoAccount_Management_AiPackFeatureRewrite": "Rewrite",
"WinoAccount_Management_AiPackFeatureSummarize": "Summarize",
"WinoAccount_Management_ImportSettingsTitle": "Import settings",
"WinoAccount_Management_ImportSettingsDescription": "Restore your preferences from cloud.",
"WinoAccount_Management_ExportSettingsTitle": "Export settings",
"WinoAccount_Management_ExportSettingsDescription": "Save your preferences to cloud.",
"WinoAccount_Management_SyncPreferencesTitle": "Synchronize Preferences",
"WinoAccount_Management_SyncPreferencesDescription": "Import or export your preferences to cloud. Import them across devices.",
"WinoAccount_Management_SignOutTitle": "Sign out",
"WinoAccount_Management_SignOutDescription": "Sign out of your account on this device",
"WinoAccount_Management_SettingsCardTitle": "Settings sync",
"WinoAccount_Management_SettingsCardDescription": "Export your current settings to your Wino Account or import them back on this device.",
"WinoAccount_Management_StatusLabel": "Status: {0}",
"WinoAccount_Management_NoRemoteSettings": "There are no synchronized settings stored for this account yet.",
"WinoAccount_Management_ExportSucceeded": "Your settings were exported to your Wino Account.",
@@ -905,6 +902,12 @@
"WinoAccount_Management_ImportEmpty": "The synchronized settings payload does not contain any values to restore.",
"WinoAccount_Management_LoadFailed": "Wino could not load the latest Wino Account information.",
"WinoAccount_Management_ActionFailed": "The Wino Account request could not be completed.",
"WinoAddOn_AI_PACK_Name": "AI Pack",
"WinoAddOn_AI_PACK_Description": "Translate, rewrite, and summarize emails with Wino AI.",
"WinoAddOn_AI_PACK_Keywords": "Translate · Rewrite · Summarize",
"WinoAddOn_UNLIMITED_ACCOUNTS_Name": "Unlimited Accounts",
"WinoAddOn_UNLIMITED_ACCOUNTS_Description": "Remove the 3-account limit and add as many accounts as you need.",
"WinoAddOn_UNLIMITED_ACCOUNTS_Keywords": "More accounts · No limit · One-time purchase",
"SettingsSearch_MessageList_Keywords": "message;messages;list;threading;threads;avatar;preview;sender",
"SettingsSearch_ReadComposePane_Keywords": "reader;compose;composer;font;fonts;external content;display;reading",
"SettingsSearch_SignatureAndEncryption_Keywords": "signature;signatures;encryption;certificate;certificates;s mime;smime;security",