-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #24 from InternAcademy/21-implement-monthly-subscr…
…iption-functionality Monthly subscription functionallity
- Loading branch information
Showing
19 changed files
with
410 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace CookingApp.Common | ||
{ | ||
public static class ExceptionMessages | ||
{ | ||
public const string NullOrEmptyInputValues = "The provided input contains either null or an empty value"; | ||
public const string SubscriptionCreationFail = "Failed to create a subscription. {0}"; | ||
|
||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
src/server/CookingApp/Controllers/SubscriptionController.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using CookingApp.Services.Stripe; | ||
using CookingApp.ViewModels.Stripe.Customer; | ||
using Microsoft.AspNetCore.Mvc; | ||
|
||
namespace CookingApp.Controllers | ||
{ | ||
//The endpoints are still not working without authorizationScheme | ||
[Route("api/subscription")] | ||
[ApiController] | ||
public class SubscriptionController : ControllerBase | ||
{ | ||
private readonly IStripeService stripeService; | ||
|
||
public SubscriptionController(IStripeService _stripeService) | ||
{ | ||
stripeService = _stripeService; | ||
} | ||
|
||
[HttpPost("create-customer")] | ||
public async Task<ActionResult> CreateCustomerAsync ([FromBody] CustomerCreation model) | ||
{ | ||
return Ok(await stripeService.CreateCustomerAsync(model.Email)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 9 additions & 0 deletions
9
src/server/CookingApp/Infrastructure/Configurations/Stripe/StripeOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace CookingApp.Infrastructure.Configurations.Stripe | ||
{ | ||
public class StripeOptions | ||
{ | ||
public string PublishableKey { get; set; } = string.Empty; | ||
public string SecretKey { get; set; } = string.Empty; | ||
public string WebhookSecret { get; set; } = string.Empty; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using CookingApp.ViewModels.Stripe.Customer; | ||
using CookingApp.ViewModels.Stripe.Product; | ||
using CookingApp.ViewModels.Stripe.Subscription; | ||
|
||
namespace CookingApp.Services.Stripe | ||
{ | ||
public interface IStripeService | ||
{ | ||
Task<CustomerCreationResponse> CreateCustomerAsync(string email); | ||
Task<IEnumerable<ProductsResponse>> GetProductsAsync(); | ||
Task<SubscriptionCreationResponse> CreateSubscriptionAsync(SubscriptionCreation model); | ||
Task<SubscriptionCancellationResponse> CancelSubscriptionAsync(SubscriptionCancellation model); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
using CookingApp.ViewModels.Stripe.Customer; | ||
using CookingApp.ViewModels.Stripe.Product; | ||
using CookingApp.ViewModels.Stripe.Subscription; | ||
using static CookingApp.Common.ExceptionMessages; | ||
using Stripe; | ||
|
||
namespace CookingApp.Services.Stripe | ||
{ | ||
public class StripeService : IStripeService | ||
{ | ||
private readonly CustomerService customerService; | ||
private readonly PriceService priceService; | ||
private readonly ProductService productService; | ||
private readonly SubscriptionService subscriptionService; | ||
|
||
public StripeService(CustomerService _customerService, | ||
PriceService _priceService, | ||
ProductService _productService, | ||
SubscriptionService _subscriptionService) | ||
{ | ||
customerService = _customerService; | ||
priceService = _priceService; | ||
productService = _productService; | ||
subscriptionService = _subscriptionService; | ||
} | ||
|
||
/// <summary> | ||
/// Creates a customer object in Stripe. | ||
/// It is used to create recurring charges and track payments that belong to the same customer. | ||
/// </summary> | ||
public async Task<CustomerCreationResponse> CreateCustomerAsync(string email) | ||
{ | ||
ArgumentException.ThrowIfNullOrEmpty(email); | ||
|
||
var options = new CustomerCreateOptions | ||
{ | ||
Email = email | ||
}; | ||
|
||
Customer customer = await customerService.CreateAsync(options); | ||
|
||
return (new CustomerCreationResponse( | ||
customer.Id, | ||
customer.Email) | ||
); | ||
} | ||
|
||
/// <summary> | ||
/// Gets all products that are in the Stripe account. | ||
/// </summary> | ||
public async Task<IEnumerable<ProductsResponse>> GetProductsAsync() | ||
{ | ||
var options = new ProductListOptions { Limit = 3 }; | ||
|
||
StripeList<Product> products = await productService.ListAsync(options); | ||
|
||
List<ProductsResponse> result = new List<ProductsResponse>(); | ||
|
||
foreach (var product in products) | ||
{ | ||
var price = await priceService.GetAsync(product.DefaultPriceId); | ||
result.Add( | ||
new ProductsResponse(product.Id, | ||
product.Name, | ||
price.UnitAmount, | ||
product.DefaultPriceId, | ||
product.Description, | ||
product.Images[0])); | ||
} | ||
return result; | ||
} | ||
|
||
/// <summary> | ||
/// Creates a subscription with a status "default_incomplete" because the subscription | ||
/// requires a payment. It automatically generates an an initial Invoice. | ||
/// Once the initial Invoice is payed the status then is set to active. | ||
/// If the Invoice is not payed in 23 hours the status then is set to "incomplete_expired" | ||
/// </summary> | ||
public async Task<SubscriptionCreationResponse> CreateSubscriptionAsync(SubscriptionCreation model) | ||
{ | ||
if(model == null || | ||
string.IsNullOrEmpty(model.CustomerId) || | ||
string.IsNullOrEmpty(model.PriceId)) | ||
{ | ||
throw new ArgumentNullException(NullOrEmptyInputValues); | ||
} | ||
var subscriptionOptions = new SubscriptionCreateOptions | ||
{ | ||
Customer = model.CustomerId, | ||
Items = new List<SubscriptionItemOptions> | ||
{ | ||
new SubscriptionItemOptions | ||
{ | ||
Price = model.PriceId, | ||
}, | ||
}, | ||
PaymentBehavior = "default_incomplete", | ||
}; | ||
subscriptionOptions.AddExpand("latest_invoice.payment_intent"); | ||
try | ||
{ | ||
Subscription subscription = await subscriptionService.CreateAsync(subscriptionOptions); | ||
|
||
return new SubscriptionCreationResponse( | ||
subscription.Id, | ||
subscription.LatestInvoice.PaymentIntent.ClientSecret, | ||
subscription.LatestInvoiceId, | ||
subscription.LatestInvoice.HostedInvoiceUrl | ||
); | ||
} | ||
catch (StripeException e) | ||
{ | ||
throw new InvalidOperationException(string.Format(SubscriptionCreationFail, e)); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Cancels a subscription immediatly. The customer will not be charged again for the subscription | ||
/// </summary> | ||
public async Task<SubscriptionCancellationResponse> CancelSubscriptionAsync(SubscriptionCancellation model) | ||
{ | ||
ArgumentException.ThrowIfNullOrEmpty(model.SubscriptionId); | ||
var subscription = await subscriptionService.CancelAsync(model.SubscriptionId); | ||
return new SubscriptionCancellationResponse(subscription.CanceledAt); | ||
} | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
src/server/CookingApp/ViewModels/Stripe/Customer/CustomerCreation.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
using System.ComponentModel.DataAnnotations; | ||
|
||
namespace CookingApp.ViewModels.Stripe.Customer | ||
{ | ||
public class CustomerCreation | ||
{ | ||
[Required] | ||
[EmailAddress] | ||
public string Email { get; init; } = string.Empty; | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
src/server/CookingApp/ViewModels/Stripe/Customer/CustomerCreationResponse.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
using System.Text.Json.Serialization; | ||
|
||
namespace CookingApp.ViewModels.Stripe.Customer | ||
{ | ||
public record CustomerCreationResponse( | ||
string Id, | ||
string Email); | ||
} |
10 changes: 10 additions & 0 deletions
10
src/server/CookingApp/ViewModels/Stripe/Product/ProductsResponse.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
namespace CookingApp.ViewModels.Stripe.Product | ||
{ | ||
public record ProductsResponse( | ||
string Id, | ||
string Name, | ||
long? Price, | ||
string PriceId, | ||
string Description, | ||
string ImageUrl); | ||
} |
4 changes: 4 additions & 0 deletions
4
src/server/CookingApp/ViewModels/Stripe/Subscription/SubscriptionCancellation.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
namespace CookingApp.ViewModels.Stripe.Subscription | ||
{ | ||
public record SubscriptionCancellation(string SubscriptionId); | ||
} |
5 changes: 5 additions & 0 deletions
5
src/server/CookingApp/ViewModels/Stripe/Subscription/SubscriptionCancellationResponse.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
namespace CookingApp.ViewModels.Stripe.Subscription | ||
{ | ||
public record SubscriptionCancellationResponse( | ||
DateTime? CanceledAt); | ||
} |
16 changes: 16 additions & 0 deletions
16
src/server/CookingApp/ViewModels/Stripe/Subscription/SubscriptionCreation.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
using System.ComponentModel.DataAnnotations; | ||
using System.Text.Json.Serialization; | ||
|
||
namespace CookingApp.ViewModels.Stripe.Subscription | ||
{ | ||
public class SubscriptionCreation | ||
{ | ||
[Required] | ||
[JsonPropertyName("customerId")] | ||
public string CustomerId { get; set; } = string.Empty; | ||
|
||
[Required] | ||
[JsonPropertyName("priceId")] | ||
public string PriceId { get; set; } = string.Empty; | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
src/server/CookingApp/ViewModels/Stripe/Subscription/SubscriptionCreationResponse.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace CookingApp.ViewModels.Stripe.Subscription | ||
{ | ||
public record SubscriptionCreationResponse( | ||
string SubscriptionId, | ||
string ClientSecret, | ||
string InvoiceId, | ||
string InvoiceUrl); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 37 additions & 0 deletions
37
test/CookingApp.UnitTests/ControllerTests/SubscriptionControllerTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
using CookingApp.Controllers; | ||
using CookingApp.Services.Stripe; | ||
using CookingApp.ViewModels.Stripe.Customer; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Moq; | ||
using NUnit.Framework; | ||
using Assert = NUnit.Framework.Assert; | ||
|
||
namespace CookingApp.UnitTests.ControllerTests | ||
{ | ||
internal class SubscriptionControllerTests | ||
{ | ||
private Mock<IStripeService> stripeService; | ||
private SubscriptionController subsController; | ||
|
||
[SetUp] | ||
public void Setup() | ||
{ | ||
stripeService = new Mock<IStripeService>(); | ||
stripeService.Setup(service => service.CreateCustomerAsync(It.IsAny<string>())) | ||
.ReturnsAsync(new CustomerCreationResponse("cust_12345", "test@example.com")); | ||
subsController = new SubscriptionController(stripeService.Object); | ||
} | ||
[Test] | ||
public async Task ShouldReturnSubscription() | ||
{ | ||
var result = await subsController.CreateCustomerAsync(new CustomerCreation() { Email="TEST"}); | ||
|
||
var okResult = result as OkObjectResult; | ||
Assert.That(okResult,Is.Not.Null); | ||
Assert.That(okResult?.StatusCode,Is.EqualTo(200)); | ||
Assert.That(okResult?.Value, Is.Not.Null); | ||
|
||
|
||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.