diff --git a/Extensions/Cloudflare.Zones/Internals/InternalCreateZoneSubscriptionRequest.cs b/Extensions/Cloudflare.Zones/Internals/InternalCreateZoneSubscriptionRequest.cs
new file mode 100644
index 0000000..c20565c
--- /dev/null
+++ b/Extensions/Cloudflare.Zones/Internals/InternalCreateZoneSubscriptionRequest.cs
@@ -0,0 +1,11 @@
+namespace AMWD.Net.Api.Cloudflare.Zones.Internals
+{
+ internal class InternalCreateZoneSubscriptionRequest
+ {
+ [JsonProperty("frequency")]
+ public RenewFrequency? Frequency { get; set; }
+
+ [JsonProperty("rate_plan")]
+ public RatePlan? RatePlan { get; set; }
+ }
+}
diff --git a/Extensions/Cloudflare.Zones/Internals/InternalUpdateZoneSubscriptionRequest.cs b/Extensions/Cloudflare.Zones/Internals/InternalUpdateZoneSubscriptionRequest.cs
new file mode 100644
index 0000000..b0c62c0
--- /dev/null
+++ b/Extensions/Cloudflare.Zones/Internals/InternalUpdateZoneSubscriptionRequest.cs
@@ -0,0 +1,11 @@
+namespace AMWD.Net.Api.Cloudflare.Zones.Internals
+{
+ internal class InternalUpdateZoneSubscriptionRequest
+ {
+ [JsonProperty("frequency")]
+ public RenewFrequency? Frequency { get; set; }
+
+ [JsonProperty("rate_plan")]
+ public RatePlan? RatePlan { get; set; }
+ }
+}
diff --git a/Extensions/Cloudflare.Zones/README.md b/Extensions/Cloudflare.Zones/README.md
index cf41503..8f4ce49 100644
--- a/Extensions/Cloudflare.Zones/README.md
+++ b/Extensions/Cloudflare.Zones/README.md
@@ -52,6 +52,13 @@ This package contains the feature set of the _Domain/Zone Management_ section of
- **DEPRECATED** [Get All Zone Settings](https://developers.cloudflare.com/api/resources/zones/subresources/settings/methods/list/)
+##### [Subscriptions]
+
+- [Create Zone Subscription](https://developers.cloudflare.com/api/resources/zones/subresources/subscriptions/methods/create/)
+- [Zone Subscription Details](https://developers.cloudflare.com/api/resources/zones/subresources/subscriptions/methods/get/)
+- [Update Zone Subscription](https://developers.cloudflare.com/api/resources/zones/subresources/subscriptions/methods/update/)
+
+
---
@@ -68,3 +75,4 @@ Published under MIT License (see [choose a license])
[Plans]: https://developers.cloudflare.com/api/resources/zones/subresources/plans/
[Rate Plans]: https://developers.cloudflare.com/api/resources/zones/subresources/rate_plans/
[Settings]: https://developers.cloudflare.com/api/resources/zones/subresources/settings/
+[Subscriptions]: https://developers.cloudflare.com/api/resources/zones/subresources/subscriptions/
diff --git a/Extensions/Cloudflare.Zones/Requests/CreateZoneSubscriptionRequest.cs b/Extensions/Cloudflare.Zones/Requests/CreateZoneSubscriptionRequest.cs
new file mode 100644
index 0000000..29129ab
--- /dev/null
+++ b/Extensions/Cloudflare.Zones/Requests/CreateZoneSubscriptionRequest.cs
@@ -0,0 +1,32 @@
+namespace AMWD.Net.Api.Cloudflare.Zones
+{
+ ///
+ /// Represents a request to create a zone subscription.
+ ///
+ public class CreateZoneSubscriptionRequest
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The zone identifier.
+ public CreateZoneSubscriptionRequest(string zoneId)
+ {
+ ZoneId = zoneId;
+ }
+
+ ///
+ /// The zone identifier.
+ ///
+ public string ZoneId { get; set; }
+
+ ///
+ /// How often the subscription is renewed automatically.
+ ///
+ public RenewFrequency? Frequency { get; set; }
+
+ ///
+ /// The rate plan applied to the subscription.
+ ///
+ public RatePlan? RatePlan { get; set; }
+ }
+}
diff --git a/Extensions/Cloudflare.Zones/Requests/UpdateZoneSubscriptionRequest.cs b/Extensions/Cloudflare.Zones/Requests/UpdateZoneSubscriptionRequest.cs
new file mode 100644
index 0000000..00b67d9
--- /dev/null
+++ b/Extensions/Cloudflare.Zones/Requests/UpdateZoneSubscriptionRequest.cs
@@ -0,0 +1,32 @@
+namespace AMWD.Net.Api.Cloudflare.Zones
+{
+ ///
+ /// Represents a request to update a zone subscription.
+ ///
+ public class UpdateZoneSubscriptionRequest
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The zone identifier.
+ public UpdateZoneSubscriptionRequest(string zoneId)
+ {
+ ZoneId = zoneId;
+ }
+
+ ///
+ /// The zone identifier.
+ ///
+ public string ZoneId { get; set; }
+
+ ///
+ /// How often the subscription is renewed automatically.
+ ///
+ public RenewFrequency? Frequency { get; set; }
+
+ ///
+ /// The rate plan applied to the subscription.
+ ///
+ public RatePlan? RatePlan { get; set; }
+ }
+}
diff --git a/Extensions/Cloudflare.Zones/ZoneSubscriptionsExtensions.cs b/Extensions/Cloudflare.Zones/ZoneSubscriptionsExtensions.cs
new file mode 100644
index 0000000..5565682
--- /dev/null
+++ b/Extensions/Cloudflare.Zones/ZoneSubscriptionsExtensions.cs
@@ -0,0 +1,63 @@
+using System.Threading;
+using System.Threading.Tasks;
+using AMWD.Net.Api.Cloudflare.Zones.Internals;
+
+namespace AMWD.Net.Api.Cloudflare.Zones
+{
+ ///
+ /// Extensions for Zone Subscriptions.
+ ///
+ public static class ZoneSubscriptionsExtensions
+ {
+ ///
+ /// Create a zone subscription, either plan or add-ons.
+ ///
+ /// The instance.
+ /// The request.
+ /// A cancellation token used to propagate notification that this operation should be canceled.
+ public static Task> CreateZoneSubscription(this ICloudflareClient client, CreateZoneSubscriptionRequest request, CancellationToken cancellationToken = default)
+ {
+ request.ZoneId.ValidateCloudflareId();
+
+ var req = new InternalCreateZoneSubscriptionRequest
+ {
+ Frequency = request.Frequency,
+ RatePlan = request.RatePlan
+ };
+
+ return client.PostAsync($"/zones/{request.ZoneId}/subscription", req, null, cancellationToken);
+ }
+
+ ///
+ /// Lists zone subscription details.
+ ///
+ /// The instance.
+ /// The zone identifier.
+ /// A cancellation token used to propagate notification that this operation should be canceled.
+ public static Task> ZoneSubscriptionDetails(this ICloudflareClient client, string zoneId, CancellationToken cancellationToken = default)
+ {
+ zoneId.ValidateCloudflareId();
+
+ return client.GetAsync($"/zones/{zoneId}/subscription", null, cancellationToken);
+ }
+
+ ///
+ /// Updates zone subscriptions, either plan or add-ons.
+ ///
+ /// The instance.
+ /// The request.
+ /// A cancellation token used to propagate notification that this operation should be canceled.
+ public static Task> UpdateZoneSubscription(this ICloudflareClient client, UpdateZoneSubscriptionRequest request, CancellationToken cancellationToken = default)
+ {
+ request.ZoneId.ValidateCloudflareId();
+
+ var req = new InternalUpdateZoneSubscriptionRequest
+ {
+ Frequency = request.Frequency,
+ RatePlan = request.RatePlan
+ };
+
+ return client.PutAsync($"/zones/{request.ZoneId}/subscription", req, cancellationToken);
+ }
+ }
+}
diff --git a/UnitTests/Cloudflare.Zones.Tests/ZoneSubscriptionsExtensions/CreateZoneSubscriptionTest.cs b/UnitTests/Cloudflare.Zones.Tests/ZoneSubscriptionsExtensions/CreateZoneSubscriptionTest.cs
new file mode 100644
index 0000000..9721a6f
--- /dev/null
+++ b/UnitTests/Cloudflare.Zones.Tests/ZoneSubscriptionsExtensions/CreateZoneSubscriptionTest.cs
@@ -0,0 +1,90 @@
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using AMWD.Net.Api.Cloudflare;
+using AMWD.Net.Api.Cloudflare.Zones;
+using AMWD.Net.Api.Cloudflare.Zones.Internals;
+using Moq;
+
+namespace Cloudflare.Zones.Tests.ZoneSubscriptionsExtensions
+{
+ [TestClass]
+ public class CreateZoneSubscriptionTest
+ {
+ private const string ZoneId = "023e105f4ecef8ad9ca31a8372d0c353";
+
+ private Mock _clientMock;
+
+ private CloudflareResponse _response;
+
+ private List<(string RequestPath, InternalCreateZoneSubscriptionRequest Request)> _callbacks;
+
+ private CreateZoneSubscriptionRequest _request;
+
+ [TestInitialize]
+ public void Initialize()
+ {
+ _callbacks = [];
+
+ _response = new CloudflareResponse
+ {
+ Success = true,
+ Messages = [
+ new ResponseInfo(1000, "Message 1")
+ ],
+ Errors = [
+ new ResponseInfo(1000, "Error 1")
+ ],
+ Result = new Subscription()
+ };
+
+ _request = new CreateZoneSubscriptionRequest(ZoneId)
+ {
+ Frequency = RenewFrequency.Quarterly,
+ RatePlan = new RatePlan
+ {
+ Id = RatePlanId.Business,
+ PublicName = "Business Plan"
+ }
+ };
+ }
+
+ [TestMethod]
+ public async Task ShouldCreateZoneSubscription()
+ {
+ // Arrange
+ var client = GetClient();
+
+ // Act
+ var response = await client.CreateZoneSubscription(_request);
+
+ // Assert
+ Assert.IsNotNull(response);
+ Assert.IsTrue(response.Success);
+ Assert.AreEqual(_response.Result, response.Result);
+
+ Assert.AreEqual(1, _callbacks.Count);
+
+ var callback = _callbacks.First();
+ Assert.AreEqual($"/zones/{ZoneId}/subscription", callback.RequestPath);
+ Assert.IsNotNull(callback.Request);
+
+ Assert.AreEqual(_request.Frequency, callback.Request.Frequency);
+ Assert.AreEqual(_request.RatePlan, callback.Request.RatePlan);
+
+ _clientMock.Verify(m => m.PostAsync($"/zones/{ZoneId}/subscription", It.IsAny(), null, It.IsAny()), Times.Once);
+ _clientMock.VerifyNoOtherCalls();
+ }
+
+ private ICloudflareClient GetClient()
+ {
+ _clientMock = new Mock();
+ _clientMock
+ .Setup(m => m.PostAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
+ .Callback((requestPath, request, _, _) => _callbacks.Add((requestPath, request)))
+ .ReturnsAsync(() => _response);
+
+ return _clientMock.Object;
+ }
+ }
+}
diff --git a/UnitTests/Cloudflare.Zones.Tests/ZoneSubscriptionsExtensions/UpdateZoneSubscriptionTest.cs b/UnitTests/Cloudflare.Zones.Tests/ZoneSubscriptionsExtensions/UpdateZoneSubscriptionTest.cs
new file mode 100644
index 0000000..aa31482
--- /dev/null
+++ b/UnitTests/Cloudflare.Zones.Tests/ZoneSubscriptionsExtensions/UpdateZoneSubscriptionTest.cs
@@ -0,0 +1,90 @@
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using AMWD.Net.Api.Cloudflare;
+using AMWD.Net.Api.Cloudflare.Zones;
+using AMWD.Net.Api.Cloudflare.Zones.Internals;
+using Moq;
+
+namespace Cloudflare.Zones.Tests.ZoneSubscriptionsExtensions
+{
+ [TestClass]
+ public class UpdateZoneSubscriptionTest
+ {
+ private const string ZoneId = "023e105f4ecef8ad9ca31a8372d0c353";
+
+ private Mock _clientMock;
+
+ private CloudflareResponse _response;
+
+ private List<(string RequestPath, InternalUpdateZoneSubscriptionRequest Request)> _callbacks;
+
+ private UpdateZoneSubscriptionRequest _request;
+
+ [TestInitialize]
+ public void Initialize()
+ {
+ _callbacks = [];
+
+ _response = new CloudflareResponse
+ {
+ Success = true,
+ Messages = [
+ new ResponseInfo(1000, "Message 1")
+ ],
+ Errors = [
+ new ResponseInfo(1000, "Error 1")
+ ],
+ Result = new Subscription()
+ };
+
+ _request = new UpdateZoneSubscriptionRequest(ZoneId)
+ {
+ Frequency = RenewFrequency.Quarterly,
+ RatePlan = new RatePlan
+ {
+ Id = RatePlanId.Business,
+ PublicName = "Business Plan"
+ }
+ };
+ }
+
+ [TestMethod]
+ public async Task ShouldUpdateZoneSubscription()
+ {
+ // Arrange
+ var client = GetClient();
+
+ // Act
+ var response = await client.UpdateZoneSubscription(_request);
+
+ // Assert
+ Assert.IsNotNull(response);
+ Assert.IsTrue(response.Success);
+ Assert.AreEqual(_response.Result, response.Result);
+
+ Assert.AreEqual(1, _callbacks.Count);
+
+ var callback = _callbacks.First();
+ Assert.AreEqual($"/zones/{ZoneId}/subscription", callback.RequestPath);
+ Assert.IsNotNull(callback.Request);
+
+ Assert.AreEqual(_request.Frequency, callback.Request.Frequency);
+ Assert.AreEqual(_request.RatePlan, callback.Request.RatePlan);
+
+ _clientMock.Verify(m => m.PutAsync($"/zones/{ZoneId}/subscription", It.IsAny(), It.IsAny()), Times.Once);
+ _clientMock.VerifyNoOtherCalls();
+ }
+
+ private ICloudflareClient GetClient()
+ {
+ _clientMock = new Mock();
+ _clientMock
+ .Setup(m => m.PutAsync(It.IsAny(), It.IsAny(), It.IsAny()))
+ .Callback((requestPath, request, _) => _callbacks.Add((requestPath, request)))
+ .ReturnsAsync(() => _response);
+
+ return _clientMock.Object;
+ }
+ }
+}
diff --git a/UnitTests/Cloudflare.Zones.Tests/ZoneSubscriptionsExtensions/ZoneSubscriptionDetailsTest.cs b/UnitTests/Cloudflare.Zones.Tests/ZoneSubscriptionsExtensions/ZoneSubscriptionDetailsTest.cs
new file mode 100644
index 0000000..1cedbea
--- /dev/null
+++ b/UnitTests/Cloudflare.Zones.Tests/ZoneSubscriptionsExtensions/ZoneSubscriptionDetailsTest.cs
@@ -0,0 +1,74 @@
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using AMWD.Net.Api.Cloudflare;
+using AMWD.Net.Api.Cloudflare.Zones;
+using Moq;
+
+namespace Cloudflare.Zones.Tests.ZoneSubscriptionsExtensions
+{
+ [TestClass]
+ public class ZoneSubscriptionDetailsTest
+ {
+ private const string ZoneId = "023e105f4ecef8ad9ca31a8372d0c353";
+
+ private Mock _clientMock;
+
+ private CloudflareResponse _response;
+
+ private List<(string RequestPath, IQueryParameterFilter QueryFilter)> _callbacks;
+
+ [TestInitialize]
+ public void Initialize()
+ {
+ _callbacks = [];
+
+ _response = new CloudflareResponse
+ {
+ Success = true,
+ Messages = [
+ new ResponseInfo(1000, "Message 1")
+ ],
+ Errors = [
+ new ResponseInfo(1000, "Error 1")
+ ],
+ Result = new Subscription()
+ };
+ }
+
+ [TestMethod]
+ public async Task ShouldGetZoneSubscriptionDetails()
+ {
+ // Arrange
+ var client = GetClient();
+
+ // Act
+ var response = await client.ZoneSubscriptionDetails(ZoneId);
+
+ // Assert
+ Assert.IsNotNull(response);
+ Assert.IsTrue(response.Success);
+ Assert.AreEqual(_response.Result, response.Result);
+
+ Assert.AreEqual(1, _callbacks.Count);
+
+ var callback = _callbacks.First();
+ Assert.AreEqual($"/zones/{ZoneId}/subscription", callback.RequestPath);
+ Assert.IsNull(callback.QueryFilter);
+
+ _clientMock.Verify(m => m.GetAsync($"/zones/{ZoneId}/subscription", null, It.IsAny()), Times.Once);
+ _clientMock.VerifyNoOtherCalls();
+ }
+
+ private ICloudflareClient GetClient()
+ {
+ _clientMock = new Mock();
+ _clientMock
+ .Setup(m => m.GetAsync(It.IsAny(), It.IsAny(), It.IsAny()))
+ .Callback((requestPath, queryFilter, _) => _callbacks.Add((requestPath, queryFilter)))
+ .ReturnsAsync(() => _response);
+
+ return _clientMock.Object;
+ }
+ }
+}