Skip to content

Commit

Permalink
Added transfer, history and migration APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
jezzsantos committed Jun 16, 2024
1 parent e31cdcc commit a9a4318
Show file tree
Hide file tree
Showing 85 changed files with 3,604 additions and 673 deletions.
51 changes: 44 additions & 7 deletions docs/design-principles/0000-all-use-cases.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,34 +33,48 @@ These are the main use cases of this product that are exposed via "public" APIs

#### Audits

For permanently recording compliance and business critical events that are performed on the platform.

1. [Asynchronously] persist an audit to permanent storage

#### Emails

For delivering emails to 3rd party services

1. [Asynchronously] deliver an email to an email gateway
2. Find all delivered emails

#### Feature Flags

For controlling feature flags for software releases

1. Fetch a specific flag
2. Fetch all feature flags (for the current deployment environment)
3. Fetch a specific flag for a specific user

#### Provisioning

Used for registering new tenants on the platform, when provisioning physical cloud infrastructure for individual tenants.

1. [Asynchronously] notify the provisioning of a new tenant

#### Recording

Recording combines, logging, auditing, metrics and usages in a single concept.

1. Record a new measurement event (from a Frontend)
2. Record a new usage event (from a Frontend)

#### Usages (Product)

Usages are the means to record the usage of a product by a user.

1. [Asynchronously] deliver a usage event to product usage service

### Users (End User)

These are the end users on the platform.

1. Assign [platform] roles to an existing user
2. Unassign [platform] roles to an existing user (except `Standard` role)
3. Invite a guest to register on the platform (a referral)
Expand All @@ -73,21 +87,29 @@ These are the main use cases of this product that are exposed via "public" APIs

#### API Keys

API Key are the way a user (person or machine) can authenticate with the platform using an API key.

1. Create a new API key for the current user
2. List all API keys of the current (Authenticated) user
3. Delete an API Key

#### Auth Tokens

Auth Tokens are the way that a user can authenticate with the platform using one or more tokens.

1. Refresh an access token
2. Revoke an existing access token

#### Machines

Machines are the way that non-human entities can operate on the platform.

1. Register a new machine (anonymously or by authenticated user)

#### Password Credentials

Is the way a user can authenticate with the platform using a username and password.

1. Authenticate the current user (with a password)
2. Register a new person (with a password and with optional invitation)
3. Confirm registration of a person (from email)
Expand All @@ -98,10 +120,14 @@ These are the main use cases of this product that are exposed via "public" APIs

#### Single-Sign On

Is the way that a user can authenticate with the platform using an external OAuth2 provider (like Google, Facebook, etc.)

1. Authenticate and (auto-register) a person from another OAuth2 provider (with an optional invitation)

### Images

Provides a simple image service for uploading and downloading images.

1. Upload a new image (supported image types: jpeg,png, gif, maximum size: 100MB)
2. Update the metadata about an image (i.e. Description)
3. Delete an image
Expand All @@ -110,6 +136,8 @@ These are the main use cases of this product that are exposed via "public" APIs

### Organizations

Organizations are the primary way that users are grouped together on the platform. An organization can be a "personal" organization (for a single user) or a "shared" organization (for multiple users). An organization is the manifestation of a tenant on the platform.

1. Create a new (shared) organization for the current user
2. Inspect a specific organization
3. Change the organization's details
Expand All @@ -122,17 +150,26 @@ These are the main use cases of this product that are exposed via "public" APIs
10. List all members of the organization
11. Delete the organization (must be no remaining members)

### EventNotifications

Event Notifications are the way that subdomains can listen to each other in a loosely-coupled way. A "producing" subdomain produces "domain_events" which are stored on a message bus. This API provides an endpoint to consume those "domain_events".

1. Handle a domain_event published to a message bus

### Subscriptions

A billing subscription is created for every `Organization` (personal and shared) on the platform (person and machine). It represents the billing subscription plan for that tenant/organization (i.e., pricing and cost). The subscription plan determines the `Features` each user has access to on the platform, and it defines the fiscal responsibilities that each `EndUser` has an obligation for (i.e., scheduled payments).
A billing subscription is created for every `Organization` (personal and shared) on the platform for any (person or machine). It represents the billing subscription plan for that tenant/organization (i.e., pricing and cost, and features). The subscription plan determines the `Features` each user has access to on the platform, and it defines the fiscal responsibilities that each `EndUser` has an obligation for (i.e., scheduled payments).

Every `Organization` must have a single `EndUser` that owns the fiscal responsibility of the `Organization`, and who can control the extent of that responsibility.
Every `Organization` must have a single `EndUser` that owns the fiscal responsibility of the `Organization` (called the "buyer"), who can control the extent of that responsibility.

1. (coming soon) Inspect the subscription for the current (Authenticated) user
2. (coming soon) Change the subscription plan
3. (coming soon) Cancel the subscription plan
4. (coming soon) Migrate the billing provider data (from one provider to the next)
5. (coming soon) Transfer fiscal responsibility for the organization
1. Inspect the subscription for the current (Authenticated) user
2. Upgrade/Downgrade the plan, or transfer the subscription to another authorized buyer (in the case of the buyer leaving the platform)
3. Cancel the subscription plan
4. List all the available pricing plans
5. Search the billing history for a subscription
6. Transfer the subscription to another authorized buyer
7. List all subscriptions that could be migrated (when migrating off of an existing billing provider)
8. Migrate the billing provider data (from one billing provider to a new one)

### User Profiles

Expand Down
9 changes: 9 additions & 0 deletions src/Application.Interfaces/Audits.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Application.Interfaces/Audits.resx
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,7 @@
<data name="EndUserApplication_TenantFeatureUnassigned" xml:space="preserve">
<value>EndUser.TenantFeatureUnassigned</value>
</data>
<data name="SubscriptionsApplication_BuyerTransferred" xml:space="preserve">
<value>Subscription.BuyerTransferred</value>
</data>
</root>
33 changes: 16 additions & 17 deletions src/Application.Resources.Shared/Subscriptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace Application.Resources.Shared;


public class Subscription : IIdentifiableResource
{
public required string BuyerId { get; set; }
Expand All @@ -16,13 +15,6 @@ public class Subscription : IIdentifiableResource
public required string Id { get; set; }
}

public class SubscriptionForMigration : Subscription
{
#pragma warning disable SAASAPP014
public required Dictionary<string, object> Buyer { get; set; }
#pragma warning restore SAASAPP014
}

public class SubscriptionWithPlan : Subscription
{
public bool CanBeCancelled { get; set; }
Expand Down Expand Up @@ -205,23 +197,23 @@ public class PricingPlan : IIdentifiableResource
{
public required PlanPeriod Billing { get; set; }

public decimal Cost { get; set; } // In the denomination of the Currency

public required string Currency { get; set; } // ISO4217

public required string Description { get; set; }
public string? Description { get; set; }

public required string DisplayName { get; set; }
public string? DisplayName { get; set; }

public List<PricingFeatureSection> FeatureSection { get; set; } = new();

public bool IsRecommended { get; set; }

public required string Notes { get; set; }
public string? Notes { get; set; }

public required decimal Cost { get; set; } // In the denomination of the Currency
public decimal SetupCost { get; set; } // In the denomination of the Currency

public required decimal SetupCost { get; set; } // In the denomination of the Currency

public required SubscriptionTrialPeriod Trial { get; set; }
public SubscriptionTrialPeriod? Trial { get; set; }

public required string Id { get; set; }
}
Expand All @@ -237,14 +229,21 @@ public class SubscriptionTrialPeriod

public class PricingFeatureSection
{
public required string Description { get; set; }
public string? Description { get; set; }

public List<PricingFeatureItem> Features { get; set; } = new();
}

public class PricingFeatureItem
{
public required string Description { get; set; }
public string? Description { get; set; }

public bool IsIncluded { get; set; }
}

public class SubscriptionToMigrate : Subscription
{
#pragma warning disable SAASAPP014
public Dictionary<string, string> Buyer { get; set; } = new();
#pragma warning restore SAASAPP014
}
85 changes: 72 additions & 13 deletions src/Application.Services.Shared/IBillingGatewayService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,31 @@ namespace Application.Services.Shared;
/// </summary>
public interface IBillingGatewayService
{
/// <summary>
/// Cancels the subscription for the current buyer
/// </summary>
Task<Result<BillingProviderState, Error>> CancelSubscriptionAsync(ICallerContext caller,
CancelSubscriptionOptions options, BillingProvider provider, CancellationToken cancellationToken);

/// <summary>
/// Changes the plan for the subscription
/// </summary>
Task<Result<BillingProviderState, Error>> ChangeSubscriptionPlanAsync(ICallerContext caller,
ChangePlanOptions options,
BillingProvider provider, CancellationToken cancellationToken);

/// <summary>
/// Returns all the pricing plans for the current provider
/// </summary>
Task<Result<PricingPlans, Error>> ListAllPricingPlansAsync(ICallerContext caller,
CancellationToken cancellationToken);

/// <summary>
/// Lists all invoices for the subscription
/// </summary>
Task<Result<List<Invoice>, Error>> SearchAllInvoicesAsync(ICallerContext caller, BillingProvider provider,
DateTime fromUtc, DateTime toUtc, SearchOptions searchOptions, CancellationToken cancellationToken);

/// <summary>
/// Creates a new subscription the specified <see cref="buyer" />
/// </summary>
Expand All @@ -26,9 +44,8 @@ Task<Result<BillingProviderState, Error>> SubscribeAsync(ICallerContext caller,
/// <summary>
/// Transfers the subscription from the current buyer to another buyer
/// </summary>
Task<Result<BillingProviderState, Error>> TransferSubscriptionPlanAsync(ICallerContext caller,
TransferOptions options,
BillingProvider provider, CancellationToken cancellationToken);
Task<Result<BillingProviderState, Error>> TransferSubscriptionAsync(ICallerContext caller,
TransferOptions options, BillingProvider provider, CancellationToken cancellationToken);
}

/// <summary>
Expand All @@ -41,46 +58,88 @@ public class ChangePlanOptions
public required string PlanId { get; set; }
}

/// <summary>
/// Options for cancelling a subscription
/// </summary>
public class CancelSubscriptionOptions
{
public static readonly CancelSubscriptionOptions EndOfTerm = new()
{
CancelWhen = CancelSubscriptionSchedule.EndOfTerm,
FutureTime = null
};
public static readonly CancelSubscriptionOptions Immediately = new()
{
CancelWhen = CancelSubscriptionSchedule.Immediately,
FutureTime = null
};

public CancelSubscriptionSchedule CancelWhen { get; set; }

public DateTime? FutureTime { get; set; }

public static CancelSubscriptionOptions AtScheduledTime(DateTime time)
{
return new CancelSubscriptionOptions
{
CancelWhen = CancelSubscriptionSchedule.Scheduled,
FutureTime = time
};
}
}

/// <summary>
/// Defines the schedule for cancelling a subscription
/// </summary>
public enum CancelSubscriptionSchedule
{
Immediately = 0,
EndOfTerm = 1,
Scheduled = 2
}

/// <summary>
/// Options for transferring a subscription
/// </summary>
public class TransferOptions
{
public required string PlanId { get; set; }
public string? PlanId { get; set; }

public required SubscriptionBuyer TransfereeBuyer { get; set; }
}


/// <summary>
/// Defines the options for creating a new subscription
/// </summary>
public class SubscribeOptions
{
public static readonly SubscribeOptions Immediately = new()
{
StartWhen = StartSubscriptionOption.Immediately,
StartWhen = StartSubscriptionSchedule.Immediately,
FutureTime = null
};

public DateTime? FutureTime { get; set; }

public StartSubscriptionOption StartWhen { get; set; }
public StartSubscriptionSchedule StartWhen { get; set; }

public static SubscribeOptions AtScheduledTime(DateTime time)
{
return new SubscribeOptions
{
StartWhen = StartSubscriptionOption.AtFutureTime,
StartWhen = StartSubscriptionSchedule.Scheduled,
FutureTime = time
};
}
}

public enum StartSubscriptionOption
/// <summary>
/// Defines the schedule for starting a subscription
/// </summary>
public enum StartSubscriptionSchedule
{
Immediately = 0,
AtFutureTime = 2
Scheduled = 2
}

/// <summary>
Expand All @@ -90,13 +149,13 @@ public class SubscriptionBuyer
{
public required ProfileAddress Address { get; set; }

public required string CompanyReference { get; set; }

public required string EmailAddress { get; set; }

public required string Id { get; set; }

public required PersonName Name { get; set; }

public required string CompanyReference { get; set; }

public string? PhoneNumber { get; set; }
}
}
Loading

0 comments on commit a9a4318

Please sign in to comment.