Skip to content

Commit

Permalink
Implement support for subscriptions.
Browse files Browse the repository at this point in the history
  • Loading branch information
Nihlus committed Feb 13, 2025
1 parent c153cad commit 4fff235
Show file tree
Hide file tree
Showing 12 changed files with 442 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// ISubscription.cs
//
// Author:
// Jarl Gullberg <[email protected]>
//
// Copyright (c) Jarl Gullberg
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//

using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Remora.Rest.Core;

namespace Remora.Discord.API.Abstractions.Objects;

/// <summary>
/// Represents a user's recurrent payment for at least one SKU.
/// </summary>
[PublicAPI]
public interface ISubscription
{
/// <summary>
/// Gets the ID of the subscription.
/// </summary>
Snowflake ID { get; }

/// <summary>
/// Gets the ID of the subscribed user.
/// </summary>
Snowflake UserID { get; }

/// <summary>
/// Gets the list of SKUs the user is subscribed to.
/// </summary>
IReadOnlyList<Snowflake> SKUIDs { get; }

/// <summary>
/// Gets the list of entitlements granted for this subscription.
/// </summary>
IReadOnlyList<Snowflake> EntitlementIDs { get; }

/// <summary>
/// Gets the list of SKUs that this user will be subscribed to at renewal.
/// </summary>
IReadOnlyList<Snowflake>? RenewalSKUIDs { get; }

/// <summary>
/// Gets the time at which the current subscription period started.
/// </summary>
DateTimeOffset CurrentPeriodStart { get; }

/// <summary>
/// Gets the time at which the current subscription period ends.
/// </summary>
DateTimeOffset CurrentPeriodEnd { get; }

/// <summary>
/// Gets the status of the subscription.
/// </summary>
SubscriptionStatus Status { get; }

/// <summary>
/// Gets the time at which the subscription was canceled.
/// </summary>
DateTimeOffset? CanceledAt { get; }

/// <summary>
/// Gets the ISO3166-1-alpha-2 country code of the payment source used to purchase the subscription.
/// </summary>
Optional<string> Country { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// SubscriptionStatus.cs
//
// Author:
// Jarl Gullberg <[email protected]>
//
// Copyright (c) Jarl Gullberg
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//

using JetBrains.Annotations;

namespace Remora.Discord.API.Abstractions.Objects;

/// <summary>
/// Enumerates various statuses a subscription can have.
/// </summary>
[PublicAPI]
public enum SubscriptionStatus
{
/// <summary>
/// The subscription is active and scheduled to renew.
/// </summary>
Active = 0,

/// <summary>
/// The subscription is active but will not renew.
/// </summary>
Ending = 1,

/// <summary>
/// The subscription is inactive and is not being charged.
/// </summary>
Inactive = 2
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,38 @@ Task<Result> DeleteTestEntitlementAsync
/// <param name="ct">The cancellation token for this operation.</param>
/// <returns>The SKUs.</returns>
Task<Result<IReadOnlyList<ISKU>>> ListSKUsAsync(Snowflake applicationID, CancellationToken ct = default);

/// <summary>
/// Gets all subscriptions containing the SKU, filtered by user.
/// </summary>
/// <param name="skuID">The ID of the SKU.</param>
/// <param name="before">The subscription to search before.</param>
/// <param name="after">The subscription to search after.</param>
/// <param name="limit">The maximum number of subscriptions to return (1-100). Defaults to 100.</param>
/// <param name="userID">The ID of the user to limit the search to.</param>
/// <param name="ct">The cancellation token for this operation.</param>
/// <returns>The subscriptions.</returns>
Task<Result<IReadOnlyList<ISubscription>>> ListSKUSubscriptionsAsync
(
Snowflake skuID,
Optional<Snowflake> before = default,
Optional<Snowflake> after = default,
Optional<int> limit = default,
Optional<Snowflake> userID = default,
CancellationToken ct = default
);

/// <summary>
/// Gets a subscription by its ID.
/// </summary>
/// <param name="skuID">The ID of the SKU.</param>
/// <param name="subscriptionID">The ID of the subscription.</param>
/// <param name="ct">The cancellation token for this operation.</param>
/// <returns>The subscription.</returns>
Task<Result<ISubscription>> GetSKUSubscriptionAsync
(
Snowflake skuID,
Snowflake subscriptionID,
CancellationToken ct = default
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// Subscription.cs
//
// Author:
// Jarl Gullberg <[email protected]>
//
// Copyright (c) Jarl Gullberg
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//

using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Remora.Discord.API.Abstractions.Objects;
using Remora.Rest.Core;

namespace Remora.Discord.API.Objects;

/// <inheritdoc />
[PublicAPI]
public record Subscription
(
Snowflake ID,
Snowflake UserID,
IReadOnlyList<Snowflake> SKUIDs,
IReadOnlyList<Snowflake> EntitlementIDs,
IReadOnlyList<Snowflake>? RenewalSKUIDs,
DateTimeOffset CurrentPeriodStart,
DateTimeOffset CurrentPeriodEnd,
SubscriptionStatus Status,
DateTimeOffset? CanceledAt,
Optional<string> Country
) : ISubscription;
Original file line number Diff line number Diff line change
Expand Up @@ -1305,13 +1305,22 @@ private static JsonSerializerOptions AddMonetizationConverters(this JsonSerializ
.WithPropertyName(e => e.SKUID, "sku_id")
.WithPropertyName(e => e.IsDeleted, "deleted")
.WithPropertyName(e => e.IsConsumed, "consumed");

options.AddDataObjectConverter<IPartialEntitlement, PartialEntitlement>()
.WithPropertyName(e => e.SKUID, "sku_id")
.WithPropertyName(e => e.IsDeleted, "deleted")
.WithPropertyName(e => e.IsConsumed, "consumed");

options.AddDataObjectConverter<ISKU, SKU>();

options.AddDataObjectConverter<ISubscription, Subscription>()
.WithPropertyName(s => s.SKUIDs, "sku_ids")
.WithPropertyName(s => s.RenewalSKUIDs, "renewal_sku_ids")
.WithPropertyName(s => s.EntitlementIDs, "entitlement_ids")
.WithPropertyConverter(s => s.CurrentPeriodStart, new ISO8601DateTimeOffsetConverter())
.WithPropertyConverter(s => s.CurrentPeriodEnd, new ISO8601DateTimeOffsetConverter())
.WithPropertyConverter(s => s.CanceledAt, new ISO8601DateTimeOffsetConverter());

return options;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,38 @@ public Task<Result<IReadOnlyList<ISKU>>> ListSKUsAsync(Snowflake applicationID,
b => b.WithRateLimitContext(this.RateLimitCache),
ct: ct
);

/// <inheritdoc />
public Task<Result<IReadOnlyList<ISubscription>>> ListSKUSubscriptionsAsync
(
Snowflake skuID,
Optional<Snowflake> before = default,
Optional<Snowflake> after = default,
Optional<int> limit = default,
Optional<Snowflake> userID = default,
CancellationToken ct = default
) => this.RestHttpClient.GetAsync<IReadOnlyList<ISubscription>>
(
$"skus/{skuID}/subscriptions",
b => b
.AddQueryParameter("before", before)
.AddQueryParameter("after", after)
.AddQueryParameter("limit", limit)
.AddQueryParameter("user_id", userID)
.WithRateLimitContext(this.RateLimitCache),
ct: ct
);

/// <inheritdoc />
public Task<Result<ISubscription>> GetSKUSubscriptionAsync
(
Snowflake skuID,
Snowflake subscriptionID,
CancellationToken ct = default
) => this.RestHttpClient.GetAsync<ISubscription>
(
$"skus/{skuID}/subscriptions/{subscriptionID}",
b => b.WithRateLimitContext(this.RateLimitCache),
ct: ct
);
}
1 change: 1 addition & 0 deletions Remora.Discord.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RTC/@EntryIndexedValue">RTC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SK/@EntryIndexedValue">SK</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SKU/@EntryIndexedValue">SKU</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SKUI/@EntryIndexedValue">SKUI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SKUID/@EntryIndexedValue">SKUID</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SSRC/@EntryIndexedValue">SSRC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=TTS/@EntryIndexedValue">TTS</s:String>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// SubscriptionTests.cs
//
// Author:
// Jarl Gullberg <[email protected]>
//
// Copyright (c) Jarl Gullberg
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//

using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Tests.TestBases;

namespace Remora.Discord.API.Tests.Objects;

/// <inheritdoc />
public class SubscriptionTests : ObjectTestBase<ISubscription>
{
/// <summary>
/// Initializes a new instance of the <see cref="SubscriptionTests"/> class.
/// </summary>
/// <param name="fixture">The test fixture.</param>
public SubscriptionTests(JsonBackedTypeTestFixture fixture)
: base(fixture)
{
}
}
Loading

0 comments on commit 4fff235

Please sign in to comment.