Skip to content

Commit 4fff235

Browse files
committed
Implement support for subscriptions.
See discord/discord-api-docs#7304.
1 parent c153cad commit 4fff235

File tree

12 files changed

+442
-1
lines changed

12 files changed

+442
-1
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//
2+
// ISubscription.cs
3+
//
4+
// Author:
5+
// Jarl Gullberg <[email protected]>
6+
//
7+
// Copyright (c) Jarl Gullberg
8+
//
9+
// This program is free software: you can redistribute it and/or modify
10+
// it under the terms of the GNU Lesser General Public License as published by
11+
// the Free Software Foundation, either version 3 of the License, or
12+
// (at your option) any later version.
13+
//
14+
// This program is distributed in the hope that it will be useful,
15+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
// GNU Lesser General Public License for more details.
18+
//
19+
// You should have received a copy of the GNU Lesser General Public License
20+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
//
22+
23+
using System;
24+
using System.Collections.Generic;
25+
using JetBrains.Annotations;
26+
using Remora.Rest.Core;
27+
28+
namespace Remora.Discord.API.Abstractions.Objects;
29+
30+
/// <summary>
31+
/// Represents a user's recurrent payment for at least one SKU.
32+
/// </summary>
33+
[PublicAPI]
34+
public interface ISubscription
35+
{
36+
/// <summary>
37+
/// Gets the ID of the subscription.
38+
/// </summary>
39+
Snowflake ID { get; }
40+
41+
/// <summary>
42+
/// Gets the ID of the subscribed user.
43+
/// </summary>
44+
Snowflake UserID { get; }
45+
46+
/// <summary>
47+
/// Gets the list of SKUs the user is subscribed to.
48+
/// </summary>
49+
IReadOnlyList<Snowflake> SKUIDs { get; }
50+
51+
/// <summary>
52+
/// Gets the list of entitlements granted for this subscription.
53+
/// </summary>
54+
IReadOnlyList<Snowflake> EntitlementIDs { get; }
55+
56+
/// <summary>
57+
/// Gets the list of SKUs that this user will be subscribed to at renewal.
58+
/// </summary>
59+
IReadOnlyList<Snowflake>? RenewalSKUIDs { get; }
60+
61+
/// <summary>
62+
/// Gets the time at which the current subscription period started.
63+
/// </summary>
64+
DateTimeOffset CurrentPeriodStart { get; }
65+
66+
/// <summary>
67+
/// Gets the time at which the current subscription period ends.
68+
/// </summary>
69+
DateTimeOffset CurrentPeriodEnd { get; }
70+
71+
/// <summary>
72+
/// Gets the status of the subscription.
73+
/// </summary>
74+
SubscriptionStatus Status { get; }
75+
76+
/// <summary>
77+
/// Gets the time at which the subscription was canceled.
78+
/// </summary>
79+
DateTimeOffset? CanceledAt { get; }
80+
81+
/// <summary>
82+
/// Gets the ISO3166-1-alpha-2 country code of the payment source used to purchase the subscription.
83+
/// </summary>
84+
Optional<string> Country { get; }
85+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//
2+
// SubscriptionStatus.cs
3+
//
4+
// Author:
5+
// Jarl Gullberg <[email protected]>
6+
//
7+
// Copyright (c) Jarl Gullberg
8+
//
9+
// This program is free software: you can redistribute it and/or modify
10+
// it under the terms of the GNU Lesser General Public License as published by
11+
// the Free Software Foundation, either version 3 of the License, or
12+
// (at your option) any later version.
13+
//
14+
// This program is distributed in the hope that it will be useful,
15+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
// GNU Lesser General Public License for more details.
18+
//
19+
// You should have received a copy of the GNU Lesser General Public License
20+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
//
22+
23+
using JetBrains.Annotations;
24+
25+
namespace Remora.Discord.API.Abstractions.Objects;
26+
27+
/// <summary>
28+
/// Enumerates various statuses a subscription can have.
29+
/// </summary>
30+
[PublicAPI]
31+
public enum SubscriptionStatus
32+
{
33+
/// <summary>
34+
/// The subscription is active and scheduled to renew.
35+
/// </summary>
36+
Active = 0,
37+
38+
/// <summary>
39+
/// The subscription is active but will not renew.
40+
/// </summary>
41+
Ending = 1,
42+
43+
/// <summary>
44+
/// The subscription is inactive and is not being charged.
45+
/// </summary>
46+
Inactive = 2
47+
}

Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestMonetizationAPI.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,38 @@ Task<Result> DeleteTestEntitlementAsync
131131
/// <param name="ct">The cancellation token for this operation.</param>
132132
/// <returns>The SKUs.</returns>
133133
Task<Result<IReadOnlyList<ISKU>>> ListSKUsAsync(Snowflake applicationID, CancellationToken ct = default);
134+
135+
/// <summary>
136+
/// Gets all subscriptions containing the SKU, filtered by user.
137+
/// </summary>
138+
/// <param name="skuID">The ID of the SKU.</param>
139+
/// <param name="before">The subscription to search before.</param>
140+
/// <param name="after">The subscription to search after.</param>
141+
/// <param name="limit">The maximum number of subscriptions to return (1-100). Defaults to 100.</param>
142+
/// <param name="userID">The ID of the user to limit the search to.</param>
143+
/// <param name="ct">The cancellation token for this operation.</param>
144+
/// <returns>The subscriptions.</returns>
145+
Task<Result<IReadOnlyList<ISubscription>>> ListSKUSubscriptionsAsync
146+
(
147+
Snowflake skuID,
148+
Optional<Snowflake> before = default,
149+
Optional<Snowflake> after = default,
150+
Optional<int> limit = default,
151+
Optional<Snowflake> userID = default,
152+
CancellationToken ct = default
153+
);
154+
155+
/// <summary>
156+
/// Gets a subscription by its ID.
157+
/// </summary>
158+
/// <param name="skuID">The ID of the SKU.</param>
159+
/// <param name="subscriptionID">The ID of the subscription.</param>
160+
/// <param name="ct">The cancellation token for this operation.</param>
161+
/// <returns>The subscription.</returns>
162+
Task<Result<ISubscription>> GetSKUSubscriptionAsync
163+
(
164+
Snowflake skuID,
165+
Snowflake subscriptionID,
166+
CancellationToken ct = default
167+
);
134168
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//
2+
// Subscription.cs
3+
//
4+
// Author:
5+
// Jarl Gullberg <[email protected]>
6+
//
7+
// Copyright (c) Jarl Gullberg
8+
//
9+
// This program is free software: you can redistribute it and/or modify
10+
// it under the terms of the GNU Lesser General Public License as published by
11+
// the Free Software Foundation, either version 3 of the License, or
12+
// (at your option) any later version.
13+
//
14+
// This program is distributed in the hope that it will be useful,
15+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
// GNU Lesser General Public License for more details.
18+
//
19+
// You should have received a copy of the GNU Lesser General Public License
20+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
//
22+
23+
using System;
24+
using System.Collections.Generic;
25+
using JetBrains.Annotations;
26+
using Remora.Discord.API.Abstractions.Objects;
27+
using Remora.Rest.Core;
28+
29+
namespace Remora.Discord.API.Objects;
30+
31+
/// <inheritdoc />
32+
[PublicAPI]
33+
public record Subscription
34+
(
35+
Snowflake ID,
36+
Snowflake UserID,
37+
IReadOnlyList<Snowflake> SKUIDs,
38+
IReadOnlyList<Snowflake> EntitlementIDs,
39+
IReadOnlyList<Snowflake>? RenewalSKUIDs,
40+
DateTimeOffset CurrentPeriodStart,
41+
DateTimeOffset CurrentPeriodEnd,
42+
SubscriptionStatus Status,
43+
DateTimeOffset? CanceledAt,
44+
Optional<string> Country
45+
) : ISubscription;

Backend/Remora.Discord.API/Extensions/ServiceCollectionExtensions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,13 +1305,22 @@ private static JsonSerializerOptions AddMonetizationConverters(this JsonSerializ
13051305
.WithPropertyName(e => e.SKUID, "sku_id")
13061306
.WithPropertyName(e => e.IsDeleted, "deleted")
13071307
.WithPropertyName(e => e.IsConsumed, "consumed");
1308+
13081309
options.AddDataObjectConverter<IPartialEntitlement, PartialEntitlement>()
13091310
.WithPropertyName(e => e.SKUID, "sku_id")
13101311
.WithPropertyName(e => e.IsDeleted, "deleted")
13111312
.WithPropertyName(e => e.IsConsumed, "consumed");
13121313

13131314
options.AddDataObjectConverter<ISKU, SKU>();
13141315

1316+
options.AddDataObjectConverter<ISubscription, Subscription>()
1317+
.WithPropertyName(s => s.SKUIDs, "sku_ids")
1318+
.WithPropertyName(s => s.RenewalSKUIDs, "renewal_sku_ids")
1319+
.WithPropertyName(s => s.EntitlementIDs, "entitlement_ids")
1320+
.WithPropertyConverter(s => s.CurrentPeriodStart, new ISO8601DateTimeOffsetConverter())
1321+
.WithPropertyConverter(s => s.CurrentPeriodEnd, new ISO8601DateTimeOffsetConverter())
1322+
.WithPropertyConverter(s => s.CanceledAt, new ISO8601DateTimeOffsetConverter());
1323+
13151324
return options;
13161325
}
13171326

Backend/Remora.Discord.Rest/API/Monetization/DiscordRestMonetizationAPI.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,38 @@ public Task<Result<IReadOnlyList<ISKU>>> ListSKUsAsync(Snowflake applicationID,
158158
b => b.WithRateLimitContext(this.RateLimitCache),
159159
ct: ct
160160
);
161+
162+
/// <inheritdoc />
163+
public Task<Result<IReadOnlyList<ISubscription>>> ListSKUSubscriptionsAsync
164+
(
165+
Snowflake skuID,
166+
Optional<Snowflake> before = default,
167+
Optional<Snowflake> after = default,
168+
Optional<int> limit = default,
169+
Optional<Snowflake> userID = default,
170+
CancellationToken ct = default
171+
) => this.RestHttpClient.GetAsync<IReadOnlyList<ISubscription>>
172+
(
173+
$"skus/{skuID}/subscriptions",
174+
b => b
175+
.AddQueryParameter("before", before)
176+
.AddQueryParameter("after", after)
177+
.AddQueryParameter("limit", limit)
178+
.AddQueryParameter("user_id", userID)
179+
.WithRateLimitContext(this.RateLimitCache),
180+
ct: ct
181+
);
182+
183+
/// <inheritdoc />
184+
public Task<Result<ISubscription>> GetSKUSubscriptionAsync
185+
(
186+
Snowflake skuID,
187+
Snowflake subscriptionID,
188+
CancellationToken ct = default
189+
) => this.RestHttpClient.GetAsync<ISubscription>
190+
(
191+
$"skus/{skuID}/subscriptions/{subscriptionID}",
192+
b => b.WithRateLimitContext(this.RateLimitCache),
193+
ct: ct
194+
);
161195
}

Remora.Discord.sln.DotSettings

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RTC/@EntryIndexedValue">RTC</s:String>
125125
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SK/@EntryIndexedValue">SK</s:String>
126126
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SKU/@EntryIndexedValue">SKU</s:String>
127+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SKUI/@EntryIndexedValue">SKUI</s:String>
127128
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SKUID/@EntryIndexedValue">SKUID</s:String>
128129
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SSRC/@EntryIndexedValue">SSRC</s:String>
129130
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=TTS/@EntryIndexedValue">TTS</s:String>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//
2+
// SubscriptionTests.cs
3+
//
4+
// Author:
5+
// Jarl Gullberg <[email protected]>
6+
//
7+
// Copyright (c) Jarl Gullberg
8+
//
9+
// This program is free software: you can redistribute it and/or modify
10+
// it under the terms of the GNU Lesser General Public License as published by
11+
// the Free Software Foundation, either version 3 of the License, or
12+
// (at your option) any later version.
13+
//
14+
// This program is distributed in the hope that it will be useful,
15+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
// GNU Lesser General Public License for more details.
18+
//
19+
// You should have received a copy of the GNU Lesser General Public License
20+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
//
22+
23+
using Remora.Discord.API.Abstractions.Objects;
24+
using Remora.Discord.API.Tests.TestBases;
25+
26+
namespace Remora.Discord.API.Tests.Objects;
27+
28+
/// <inheritdoc />
29+
public class SubscriptionTests : ObjectTestBase<ISubscription>
30+
{
31+
/// <summary>
32+
/// Initializes a new instance of the <see cref="SubscriptionTests"/> class.
33+
/// </summary>
34+
/// <param name="fixture">The test fixture.</param>
35+
public SubscriptionTests(JsonBackedTypeTestFixture fixture)
36+
: base(fixture)
37+
{
38+
}
39+
}

0 commit comments

Comments
 (0)