diff --git a/src/Core/Billing/Enums/DiscountAudienceType.cs b/src/Core/Billing/Enums/DiscountAudienceType.cs
new file mode 100644
index 0000000000..6ab4646622
--- /dev/null
+++ b/src/Core/Billing/Enums/DiscountAudienceType.cs
@@ -0,0 +1,13 @@
+namespace Bit.Core.Billing.Enums;
+
+///
+/// Defines the target audience for subscription discounts using an extensible strategy pattern.
+/// Each audience type maps to specific eligibility rules implemented via IDiscountAudienceFilter.
+///
+public enum DiscountAudienceType
+{
+ ///
+ /// Discount applies to users who have never had a subscription before.
+ ///
+ UserHasNoPreviousSubscriptions = 0
+}
diff --git a/src/Core/Billing/Subscriptions/Entities/SubscriptionDiscount.cs b/src/Core/Billing/Subscriptions/Entities/SubscriptionDiscount.cs
new file mode 100644
index 0000000000..ec8fa5f090
--- /dev/null
+++ b/src/Core/Billing/Subscriptions/Entities/SubscriptionDiscount.cs
@@ -0,0 +1,48 @@
+#nullable enable
+
+using System.ComponentModel.DataAnnotations;
+using Bit.Core.Billing.Enums;
+using Bit.Core.Entities;
+using Bit.Core.Utilities;
+
+namespace Bit.Core.Billing.Subscriptions.Entities;
+
+public class SubscriptionDiscount : ITableObject, IRevisable, IValidatableObject
+{
+ public Guid Id { get; set; }
+ [MaxLength(50)]
+ public string StripeCouponId { get; set; } = null!;
+ public string? StripeProductIds { get; set; }
+ public decimal? PercentOff { get; set; }
+ public long? AmountOff { get; set; }
+ [MaxLength(10)]
+ public string? Currency { get; set; }
+ [MaxLength(20)]
+ public string Duration { get; set; } = null!;
+ public int? DurationInMonths { get; set; }
+ [MaxLength(100)]
+ public string? Name { get; set; }
+ public DateTime StartDate { get; set; }
+ public DateTime EndDate { get; set; }
+ public DiscountAudienceType AudienceType { get; set; }
+ public DateTime CreationDate { get; set; } = DateTime.UtcNow;
+ public DateTime RevisionDate { get; set; } = DateTime.UtcNow;
+
+ public void SetNewId()
+ {
+ if (Id == default)
+ {
+ Id = CoreHelpers.GenerateComb();
+ }
+ }
+
+ public IEnumerable Validate(ValidationContext validationContext)
+ {
+ if (EndDate < StartDate)
+ {
+ yield return new ValidationResult(
+ "EndDate must be greater than or equal to StartDate.",
+ new[] { nameof(EndDate) });
+ }
+ }
+}
diff --git a/src/Core/Billing/Subscriptions/Repositories/ISubscriptionDiscountRepository.cs b/src/Core/Billing/Subscriptions/Repositories/ISubscriptionDiscountRepository.cs
new file mode 100644
index 0000000000..9a72334688
--- /dev/null
+++ b/src/Core/Billing/Subscriptions/Repositories/ISubscriptionDiscountRepository.cs
@@ -0,0 +1,23 @@
+#nullable enable
+
+using Bit.Core.Billing.Subscriptions.Entities;
+using Bit.Core.Repositories;
+
+namespace Bit.Core.Billing.Subscriptions.Repositories;
+
+public interface ISubscriptionDiscountRepository : IRepository
+{
+ ///
+ /// Retrieves all active subscription discounts that are currently within their valid date range.
+ /// A discount is considered active if the current UTC date falls between StartDate (inclusive) and EndDate (inclusive).
+ ///
+ /// A collection of active subscription discounts.
+ Task> GetActiveDiscountsAsync();
+
+ ///
+ /// Retrieves a subscription discount by its Stripe coupon ID.
+ ///
+ /// The Stripe coupon ID to search for.
+ /// The subscription discount if found; otherwise, null.
+ Task GetByStripeCouponIdAsync(string stripeCouponId);
+}
diff --git a/src/Infrastructure.Dapper/Billing/Repositories/SubscriptionDiscountRepository.cs b/src/Infrastructure.Dapper/Billing/Repositories/SubscriptionDiscountRepository.cs
new file mode 100644
index 0000000000..e7fd6c662b
--- /dev/null
+++ b/src/Infrastructure.Dapper/Billing/Repositories/SubscriptionDiscountRepository.cs
@@ -0,0 +1,39 @@
+using System.Data;
+using Bit.Core.Billing.Subscriptions.Entities;
+using Bit.Core.Billing.Subscriptions.Repositories;
+using Bit.Core.Settings;
+using Bit.Infrastructure.Dapper.Repositories;
+using Dapper;
+using Microsoft.Data.SqlClient;
+
+namespace Bit.Infrastructure.Dapper.Billing.Repositories;
+
+public class SubscriptionDiscountRepository(
+ GlobalSettings globalSettings)
+ : Repository(
+ globalSettings.SqlServer.ConnectionString,
+ globalSettings.SqlServer.ReadOnlyConnectionString), ISubscriptionDiscountRepository
+{
+ public async Task> GetActiveDiscountsAsync()
+ {
+ using var sqlConnection = new SqlConnection(ReadOnlyConnectionString);
+
+ var results = await sqlConnection.QueryAsync(
+ "[dbo].[SubscriptionDiscount_ReadActive]",
+ commandType: CommandType.StoredProcedure);
+
+ return results.ToArray();
+ }
+
+ public async Task GetByStripeCouponIdAsync(string stripeCouponId)
+ {
+ using var sqlConnection = new SqlConnection(ReadOnlyConnectionString);
+
+ var result = await sqlConnection.QueryFirstOrDefaultAsync(
+ "[dbo].[SubscriptionDiscount_ReadByStripeCouponId]",
+ new { StripeCouponId = stripeCouponId },
+ commandType: CommandType.StoredProcedure);
+
+ return result;
+ }
+}
diff --git a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs
index dcb0dc1306..4055281352 100644
--- a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs
+++ b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs
@@ -2,6 +2,7 @@
using Bit.Core.Auth.Repositories;
using Bit.Core.Billing.Organizations.Repositories;
using Bit.Core.Billing.Providers.Repositories;
+using Bit.Core.Billing.Subscriptions.Repositories;
using Bit.Core.Dirt.Reports.Repositories;
using Bit.Core.Dirt.Repositories;
using Bit.Core.KeyManagement.Repositories;
@@ -65,6 +66,7 @@ public static class DapperServiceCollectionExtensions
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
+ services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services
diff --git a/src/Infrastructure.EntityFramework/Billing/Configurations/SubscriptionDiscountEntityTypeConfiguration.cs b/src/Infrastructure.EntityFramework/Billing/Configurations/SubscriptionDiscountEntityTypeConfiguration.cs
new file mode 100644
index 0000000000..512c5506a3
--- /dev/null
+++ b/src/Infrastructure.EntityFramework/Billing/Configurations/SubscriptionDiscountEntityTypeConfiguration.cs
@@ -0,0 +1,30 @@
+using Bit.Infrastructure.EntityFramework.Billing.Models;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace Bit.Infrastructure.EntityFramework.Billing.Configurations;
+
+public class SubscriptionDiscountEntityTypeConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder
+ .Property(t => t.Id)
+ .ValueGeneratedNever();
+
+ builder
+ .HasIndex(sd => sd.StripeCouponId)
+ .IsUnique();
+
+ var dateRangeIndex = builder
+ .HasIndex(sd => new { sd.StartDate, sd.EndDate })
+ .IsClustered(false)
+ .HasDatabaseName("IX_SubscriptionDiscount_DateRange");
+
+ SqlServerIndexBuilderExtensions.IncludeProperties(
+ dateRangeIndex,
+ sd => new { sd.StripeProductIds, sd.AudienceType });
+
+ builder.ToTable(nameof(SubscriptionDiscount));
+ }
+}
diff --git a/src/Infrastructure.EntityFramework/Billing/Models/SubscriptionDiscount.cs b/src/Infrastructure.EntityFramework/Billing/Models/SubscriptionDiscount.cs
new file mode 100644
index 0000000000..b73f2c06fc
--- /dev/null
+++ b/src/Infrastructure.EntityFramework/Billing/Models/SubscriptionDiscount.cs
@@ -0,0 +1,18 @@
+#nullable enable
+
+using AutoMapper;
+
+namespace Bit.Infrastructure.EntityFramework.Billing.Models;
+
+// ReSharper disable once ClassWithVirtualMembersNeverInherited.Global
+public class SubscriptionDiscount : Core.Billing.Subscriptions.Entities.SubscriptionDiscount
+{
+}
+
+public class SubscriptionDiscountMapperProfile : Profile
+{
+ public SubscriptionDiscountMapperProfile()
+ {
+ CreateMap().ReverseMap();
+ }
+}
diff --git a/src/Infrastructure.EntityFramework/Billing/Repositories/SubscriptionDiscountRepository.cs b/src/Infrastructure.EntityFramework/Billing/Repositories/SubscriptionDiscountRepository.cs
new file mode 100644
index 0000000000..e2659afd54
--- /dev/null
+++ b/src/Infrastructure.EntityFramework/Billing/Repositories/SubscriptionDiscountRepository.cs
@@ -0,0 +1,51 @@
+using AutoMapper;
+using Bit.Core.Billing.Subscriptions.Entities;
+using Bit.Core.Billing.Subscriptions.Repositories;
+using Bit.Infrastructure.EntityFramework.Repositories;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using EFSubscriptionDiscount = Bit.Infrastructure.EntityFramework.Billing.Models.SubscriptionDiscount;
+
+namespace Bit.Infrastructure.EntityFramework.Billing.Repositories;
+
+public class SubscriptionDiscountRepository(
+ IMapper mapper,
+ IServiceScopeFactory serviceScopeFactory)
+ : Repository(
+ serviceScopeFactory,
+ mapper,
+ context => context.SubscriptionDiscounts), ISubscriptionDiscountRepository
+{
+ public async Task> GetActiveDiscountsAsync()
+ {
+ using var serviceScope = ServiceScopeFactory.CreateScope();
+
+ var databaseContext = GetDatabaseContext(serviceScope);
+
+ var query =
+ from subscriptionDiscount in databaseContext.SubscriptionDiscounts
+ where subscriptionDiscount.StartDate <= DateTime.UtcNow
+ && subscriptionDiscount.EndDate >= DateTime.UtcNow
+ select subscriptionDiscount;
+
+ var results = await query.ToArrayAsync();
+
+ return Mapper.Map>(results);
+ }
+
+ public async Task GetByStripeCouponIdAsync(string stripeCouponId)
+ {
+ using var serviceScope = ServiceScopeFactory.CreateScope();
+
+ var databaseContext = GetDatabaseContext(serviceScope);
+
+ var query =
+ from subscriptionDiscount in databaseContext.SubscriptionDiscounts
+ where subscriptionDiscount.StripeCouponId == stripeCouponId
+ select subscriptionDiscount;
+
+ var result = await query.FirstOrDefaultAsync();
+
+ return result == null ? null : Mapper.Map(result);
+ }
+}
diff --git a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs
index 320cb9436d..84a370b723 100644
--- a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs
+++ b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs
@@ -2,6 +2,7 @@
using Bit.Core.Auth.Repositories;
using Bit.Core.Billing.Organizations.Repositories;
using Bit.Core.Billing.Providers.Repositories;
+using Bit.Core.Billing.Subscriptions.Repositories;
using Bit.Core.Dirt.Reports.Repositories;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Enums;
@@ -102,6 +103,7 @@ public static class EntityFrameworkServiceCollectionExtensions
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
+ services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services
diff --git a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs
index a0ee0376c0..503cad6895 100644
--- a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs
+++ b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs
@@ -79,6 +79,7 @@ public class DatabaseContext : DbContext
public DbSet WebAuthnCredentials { get; set; }
public DbSet ProviderPlans { get; set; }
public DbSet ProviderInvoiceItems { get; set; }
+ public DbSet SubscriptionDiscounts { get; set; }
public DbSet Notifications { get; set; }
public DbSet NotificationStatuses { get; set; }
public DbSet ClientOrganizationMigrationRecords { get; set; }
diff --git a/test/Infrastructure.IntegrationTest/Billing/Repositories/SubscriptionDiscountRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Billing/Repositories/SubscriptionDiscountRepositoryTests.cs
new file mode 100644
index 0000000000..ecde98191c
--- /dev/null
+++ b/test/Infrastructure.IntegrationTest/Billing/Repositories/SubscriptionDiscountRepositoryTests.cs
@@ -0,0 +1,187 @@
+using Bit.Core.Billing.Enums;
+using Bit.Core.Billing.Subscriptions.Entities;
+using Bit.Core.Billing.Subscriptions.Repositories;
+using Xunit;
+
+namespace Bit.Infrastructure.IntegrationTest.Billing.Repositories;
+
+public class SubscriptionDiscountRepositoryTests
+{
+ private static SubscriptionDiscount CreateTestDiscount(
+ string? stripeCouponId = null,
+ string? stripeProductIds = null,
+ decimal? percentOff = null,
+ long? amountOff = null,
+ string? currency = null,
+ string duration = "once",
+ int? durationInMonths = null,
+ string? name = null,
+ DateTime? startDate = null,
+ DateTime? endDate = null,
+ DiscountAudienceType audienceType = DiscountAudienceType.UserHasNoPreviousSubscriptions,
+ DateTime? creationDate = null,
+ DateTime? revisionDate = null)
+ {
+ var now = DateTime.UtcNow;
+ return new SubscriptionDiscount
+ {
+ StripeCouponId = stripeCouponId ?? $"test-{Guid.NewGuid()}",
+ StripeProductIds = stripeProductIds,
+ PercentOff = percentOff,
+ AmountOff = amountOff,
+ Currency = currency,
+ Duration = duration,
+ DurationInMonths = durationInMonths,
+ Name = name,
+ StartDate = startDate ?? now,
+ EndDate = endDate ?? now.AddDays(30),
+ AudienceType = audienceType,
+ CreationDate = creationDate ?? now,
+ RevisionDate = revisionDate ?? now
+ };
+ }
+
+ [Theory, DatabaseData]
+ public async Task GetActiveDiscountsAsync_ReturnsDiscountsWithinDateRange(
+ ISubscriptionDiscountRepository subscriptionDiscountRepository)
+ {
+ // Create a discount that is currently active
+ var activeDiscount = await subscriptionDiscountRepository.CreateAsync(
+ CreateTestDiscount(
+ stripeCouponId: $"test-active-{Guid.NewGuid()}",
+ percentOff: 25.00m,
+ name: "Active Discount",
+ startDate: DateTime.UtcNow.AddDays(-1),
+ endDate: DateTime.UtcNow.AddDays(30)));
+
+ // Create a discount that has expired
+ var expiredDiscount = await subscriptionDiscountRepository.CreateAsync(
+ CreateTestDiscount(
+ stripeCouponId: $"test-expired-{Guid.NewGuid()}",
+ percentOff: 50.00m,
+ name: "Expired Discount",
+ startDate: DateTime.UtcNow.AddDays(-60),
+ endDate: DateTime.UtcNow.AddDays(-30)));
+
+ // Create a discount that starts in the future
+ var futureDiscount = await subscriptionDiscountRepository.CreateAsync(
+ CreateTestDiscount(
+ stripeCouponId: $"test-future-{Guid.NewGuid()}",
+ percentOff: 15.00m,
+ name: "Future Discount",
+ startDate: DateTime.UtcNow.AddDays(30),
+ endDate: DateTime.UtcNow.AddDays(60)));
+
+ // Act
+ var activeDiscounts = await subscriptionDiscountRepository.GetActiveDiscountsAsync();
+
+ // Assert
+ Assert.Contains(activeDiscounts, d => d.Id == activeDiscount.Id);
+ Assert.DoesNotContain(activeDiscounts, d => d.Id == expiredDiscount.Id);
+ Assert.DoesNotContain(activeDiscounts, d => d.Id == futureDiscount.Id);
+ }
+
+ [Theory, DatabaseData]
+ public async Task GetByStripeCouponIdAsync_ReturnsCorrectDiscount(
+ ISubscriptionDiscountRepository subscriptionDiscountRepository)
+ {
+ // Arrange
+ var couponId = $"test-coupon-{Guid.NewGuid()}";
+ var discount = await subscriptionDiscountRepository.CreateAsync(
+ CreateTestDiscount(
+ stripeCouponId: couponId,
+ stripeProductIds: "[\"prod_123\", \"prod_456\"]",
+ percentOff: 20.00m,
+ duration: "repeating",
+ durationInMonths: 3,
+ name: "Test Discount",
+ endDate: DateTime.UtcNow.AddDays(90)));
+
+ // Act
+ var result = await subscriptionDiscountRepository.GetByStripeCouponIdAsync(couponId);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(discount.Id, result.Id);
+ Assert.Equal(couponId, result.StripeCouponId);
+ Assert.Equal(20.00m, result.PercentOff);
+ Assert.Equal(3, result.DurationInMonths);
+ }
+
+ [Theory, DatabaseData]
+ public async Task GetByStripeCouponIdAsync_ReturnsNull_WhenCouponDoesNotExist(
+ ISubscriptionDiscountRepository subscriptionDiscountRepository)
+ {
+ // Act
+ var result = await subscriptionDiscountRepository.GetByStripeCouponIdAsync("non-existent-coupon");
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Theory, DatabaseData]
+ public async Task CreateAsync_CreatesDiscountSuccessfully(
+ ISubscriptionDiscountRepository subscriptionDiscountRepository)
+ {
+ // Arrange
+ var discount = CreateTestDiscount(
+ stripeCouponId: $"test-create-{Guid.NewGuid()}",
+ stripeProductIds: "[\"prod_789\"]",
+ amountOff: 500,
+ currency: "usd",
+ name: "Fixed Amount Discount");
+
+ // Act
+ var createdDiscount = await subscriptionDiscountRepository.CreateAsync(discount);
+
+ // Assert
+ Assert.NotNull(createdDiscount);
+ Assert.NotEqual(Guid.Empty, createdDiscount.Id);
+ Assert.Equal(discount.StripeCouponId, createdDiscount.StripeCouponId);
+ Assert.Equal(500, createdDiscount.AmountOff);
+ Assert.Equal("usd", createdDiscount.Currency);
+ }
+
+ [Theory, DatabaseData]
+ public async Task ReplaceAsync_UpdatesDiscountSuccessfully(
+ ISubscriptionDiscountRepository subscriptionDiscountRepository)
+ {
+ // Arrange
+ var discount = await subscriptionDiscountRepository.CreateAsync(
+ CreateTestDiscount(
+ stripeCouponId: $"test-update-{Guid.NewGuid()}",
+ percentOff: 10.00m,
+ name: "Original Name"));
+
+ // Act
+ discount.Name = "Updated Name";
+ discount.PercentOff = 15.00m;
+ discount.RevisionDate = DateTime.UtcNow;
+ await subscriptionDiscountRepository.ReplaceAsync(discount);
+
+ // Assert
+ var updatedDiscount = await subscriptionDiscountRepository.GetByIdAsync(discount.Id);
+ Assert.NotNull(updatedDiscount);
+ Assert.Equal("Updated Name", updatedDiscount.Name);
+ Assert.Equal(15.00m, updatedDiscount.PercentOff);
+ }
+
+ [Theory, DatabaseData]
+ public async Task DeleteAsync_RemovesDiscountSuccessfully(
+ ISubscriptionDiscountRepository subscriptionDiscountRepository)
+ {
+ // Arrange
+ var discount = await subscriptionDiscountRepository.CreateAsync(
+ CreateTestDiscount(
+ stripeCouponId: $"test-delete-{Guid.NewGuid()}",
+ percentOff: 25.00m,
+ name: "To Be Deleted"));
+
+ // Act
+ await subscriptionDiscountRepository.DeleteAsync(discount);
+
+ // Assert
+ var deletedDiscount = await subscriptionDiscountRepository.GetByIdAsync(discount.Id);
+ Assert.Null(deletedDiscount);
+ }
+}
diff --git a/util/Migrator/DbScripts/2026-01-27_00_AddSubscriptionDiscountTable.sql b/util/Migrator/DbScripts/2026-01-27_00_AddSubscriptionDiscountTable.sql
new file mode 100644
index 0000000000..7cd292b5a1
--- /dev/null
+++ b/util/Migrator/DbScripts/2026-01-27_00_AddSubscriptionDiscountTable.sql
@@ -0,0 +1,201 @@
+-- Table
+IF OBJECT_ID('[dbo].[SubscriptionDiscount]') IS NULL
+BEGIN
+ CREATE TABLE [dbo].[SubscriptionDiscount] (
+ [Id] UNIQUEIDENTIFIER NOT NULL,
+ [StripeCouponId] VARCHAR(50) NOT NULL,
+ [StripeProductIds] NVARCHAR(MAX) NULL,
+ [PercentOff] DECIMAL(5,2) NULL,
+ [AmountOff] BIGINT NULL,
+ [Currency] VARCHAR(10) NULL,
+ [Duration] VARCHAR(20) NOT NULL,
+ [DurationInMonths] INT NULL,
+ [Name] NVARCHAR(100) NULL,
+ [StartDate] DATETIME2(7) NOT NULL,
+ [EndDate] DATETIME2(7) NOT NULL,
+ [AudienceType] INT NOT NULL DEFAULT 0,
+ [CreationDate] DATETIME2(7) NOT NULL,
+ [RevisionDate] DATETIME2(7) NOT NULL,
+ CONSTRAINT [PK_SubscriptionDiscount] PRIMARY KEY CLUSTERED ([Id] ASC),
+ CONSTRAINT [IX_SubscriptionDiscount_StripeCouponId] UNIQUE ([StripeCouponId])
+ );
+END
+GO
+
+-- Index for date range queries
+IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_SubscriptionDiscount_DateRange' AND object_id = OBJECT_ID('[dbo].[SubscriptionDiscount]'))
+BEGIN
+ CREATE INDEX [IX_SubscriptionDiscount_DateRange] ON [dbo].[SubscriptionDiscount]
+ ([StartDate], [EndDate]) INCLUDE ([StripeProductIds], [AudienceType]);
+END
+GO
+
+-- View
+CREATE OR ALTER VIEW [dbo].[SubscriptionDiscountView]
+AS
+SELECT
+ *
+FROM
+ [dbo].[SubscriptionDiscount]
+GO
+
+-- Stored Procedures: Create
+CREATE OR ALTER PROCEDURE [dbo].[SubscriptionDiscount_Create]
+ @Id UNIQUEIDENTIFIER OUTPUT,
+ @StripeCouponId VARCHAR(50),
+ @StripeProductIds NVARCHAR(MAX),
+ @PercentOff DECIMAL(5,2),
+ @AmountOff BIGINT,
+ @Currency VARCHAR(10),
+ @Duration VARCHAR(20),
+ @DurationInMonths INT,
+ @Name NVARCHAR(100),
+ @StartDate DATETIME2(7),
+ @EndDate DATETIME2(7),
+ @AudienceType INT,
+ @CreationDate DATETIME2(7),
+ @RevisionDate DATETIME2(7)
+AS
+BEGIN
+ SET NOCOUNT ON
+
+ INSERT INTO [dbo].[SubscriptionDiscount]
+ (
+ [Id],
+ [StripeCouponId],
+ [StripeProductIds],
+ [PercentOff],
+ [AmountOff],
+ [Currency],
+ [Duration],
+ [DurationInMonths],
+ [Name],
+ [StartDate],
+ [EndDate],
+ [AudienceType],
+ [CreationDate],
+ [RevisionDate]
+ )
+ VALUES
+ (
+ @Id,
+ @StripeCouponId,
+ @StripeProductIds,
+ @PercentOff,
+ @AmountOff,
+ @Currency,
+ @Duration,
+ @DurationInMonths,
+ @Name,
+ @StartDate,
+ @EndDate,
+ @AudienceType,
+ @CreationDate,
+ @RevisionDate
+ )
+END
+GO
+
+-- Stored Procedures: DeleteById
+CREATE OR ALTER PROCEDURE [dbo].[SubscriptionDiscount_DeleteById]
+ @Id UNIQUEIDENTIFIER
+AS
+BEGIN
+ SET NOCOUNT ON
+
+ DELETE
+ FROM
+ [dbo].[SubscriptionDiscount]
+ WHERE
+ [Id] = @Id
+END
+GO
+
+-- Stored Procedures: ReadById
+CREATE OR ALTER PROCEDURE [dbo].[SubscriptionDiscount_ReadById]
+ @Id UNIQUEIDENTIFIER
+AS
+BEGIN
+ SET NOCOUNT ON
+
+ SELECT
+ *
+ FROM
+ [dbo].[SubscriptionDiscountView]
+ WHERE
+ [Id] = @Id
+END
+GO
+
+-- Stored Procedures: Update
+CREATE OR ALTER PROCEDURE [dbo].[SubscriptionDiscount_Update]
+ @Id UNIQUEIDENTIFIER OUTPUT,
+ @StripeCouponId VARCHAR(50),
+ @StripeProductIds NVARCHAR(MAX),
+ @PercentOff DECIMAL(5,2),
+ @AmountOff BIGINT,
+ @Currency VARCHAR(10),
+ @Duration VARCHAR(20),
+ @DurationInMonths INT,
+ @Name NVARCHAR(100),
+ @StartDate DATETIME2(7),
+ @EndDate DATETIME2(7),
+ @AudienceType INT,
+ @CreationDate DATETIME2(7),
+ @RevisionDate DATETIME2(7)
+AS
+BEGIN
+ SET NOCOUNT ON
+
+ UPDATE
+ [dbo].[SubscriptionDiscount]
+ SET
+ [StripeCouponId] = @StripeCouponId,
+ [StripeProductIds] = @StripeProductIds,
+ [PercentOff] = @PercentOff,
+ [AmountOff] = @AmountOff,
+ [Currency] = @Currency,
+ [Duration] = @Duration,
+ [DurationInMonths] = @DurationInMonths,
+ [Name] = @Name,
+ [StartDate] = @StartDate,
+ [EndDate] = @EndDate,
+ [AudienceType] = @AudienceType,
+ [CreationDate] = @CreationDate,
+ [RevisionDate] = @RevisionDate
+ WHERE
+ [Id] = @Id
+END
+GO
+
+-- Stored Procedures: ReadActive (returns discounts within date range)
+CREATE OR ALTER PROCEDURE [dbo].[SubscriptionDiscount_ReadActive]
+AS
+BEGIN
+ SET NOCOUNT ON
+
+ SELECT
+ *
+ FROM
+ [dbo].[SubscriptionDiscountView]
+ WHERE
+ [StartDate] <= GETUTCDATE()
+ AND [EndDate] >= GETUTCDATE()
+END
+GO
+
+-- Stored Procedures: ReadByStripeCouponId
+CREATE OR ALTER PROCEDURE [dbo].[SubscriptionDiscount_ReadByStripeCouponId]
+ @StripeCouponId VARCHAR(50)
+AS
+BEGIN
+ SET NOCOUNT ON
+
+ SELECT
+ *
+ FROM
+ [dbo].[SubscriptionDiscountView]
+ WHERE
+ [StripeCouponId] = @StripeCouponId
+END
+GO