diff --git a/.editorconfig b/.editorconfig index ae75b11..07820a4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,4 +12,5 @@ trim_trailing_whitespace = false [*.cs] csharp_new_line_before_open_brace = none csharp_new_line_before_catch = false -csharp_new_line_before_finally = false \ No newline at end of file +csharp_new_line_before_finally = false +csharp_new_line_after_else = false \ No newline at end of file diff --git a/.env_Template b/.env_Template index 513b796..dac4027 100755 --- a/.env_Template +++ b/.env_Template @@ -1,4 +1,7 @@ +Payment_Service=StripeIntent # Options are [ StripeIntent ] + Stripe_Key= +Stripe_Endpoint_Secret= MySQL_Server=mistox-database MySQL_User=root diff --git a/ToDo.txt b/ToDo.txt index 4b80527..4a2c385 100755 --- a/ToDo.txt +++ b/ToDo.txt @@ -1,5 +1,4 @@ -Fix stripe payments *Updated API* - Havent Tested +Stripe payments aren't currently tested After a new account is created notify a user that they need to verify their email before logging in @@ -30,9 +29,5 @@ DTO Cannot send dotnet List -> must be arrays Need to update local server to use www-formdata instead of json -PaymentController - PaymentResponse is still tied to Stripe. - Need to change to Interface so different services can be interchanged - ProductController Need to figure out new way to download purchased items as there is currently no way \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 53a2057..e755aad 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,9 @@ services: image: mistox-website:latest restart: always environment: + - PaymentService=${Payment_Service} - StripeKey=${Stripe_Key} + - StripeEndpointSecret=&{Stripe_Endpoint_Secret} - MySQLServer=${MySQL_Server} - MySQLUser=${MySQL_User} - MySQLPass=${MySQL_Pass} diff --git a/src/MistoxWebsite.Server/Controllers/PaymentController.cs b/src/MistoxWebsite.Server/Controllers/PaymentController.cs index de9fe5f..57bc1c5 100755 --- a/src/MistoxWebsite.Server/Controllers/PaymentController.cs +++ b/src/MistoxWebsite.Server/Controllers/PaymentController.cs @@ -2,107 +2,59 @@ using MistoxWebsite.Server.Controllers.Payment; using MistoxWebsite.Server.Services.DatabaseService; using MistoxWebsite.Server.Entities; +using Microsoft.Extensions.Primitives; namespace MistoxWebsite.Server.Controllers { [ApiController] public class PaymentController : ControllerBase { DatabaseService _databaseService; + IPayment _paymentService; - public PaymentController( DatabaseService databaseService ) { + public PaymentController(DatabaseService databaseService) { _databaseService = databaseService; + + if (IPayment._PaymentType == PaymentType.StripeIntent) { + _paymentService = new StripeIntent(_databaseService); + } else { + // Fallback + _paymentService = new StripeIntent(_databaseService); + } + // Add new payment plugins here + } - // Charges - [Route( "api/getCheckoutToken" )] + [Route("api/getCheckoutToken")] [HttpPost] public async Task GetPaymentKey( [FromQuery] string userID ) { - string OrderNumber = Guid.NewGuid().ToString().Substring(0,10); Account? acc = await _databaseService.GetAccount(userID); if (acc != null) { List cart = await _databaseService.GetCart(acc); - - IPayment PaymentPlugin = new StripeIntent(_databaseService); - - (bool, string) PaymentResponse = await PaymentPlugin.Purchase(OrderNumber, acc, cart); + (bool, string) PaymentResponse = await _paymentService.TryGetCheckoutToken(OrderNumber, acc, cart); if (PaymentResponse.Item1) { + // Returns client secret return PaymentResponse.Item2; - } - else { + } else { Console.WriteLine("An error has occured in the payment plugin\n\n"); Console.WriteLine(PaymentResponse.Item2); Console.WriteLine("\n"); - return "0"; + return "An error has occured in the payment plugin"; } - + } else { + return "Unable to find account"; } - return "0"; } [Route( "/api/payment/response" )] [HttpPost] public async Task paymentWebhook() { try { - const string endpointSecret = "whsec_HCO7uv2BPIPmUPOiSg9tfwLZul8usCGG"; string body = await new StreamReader(Request.Body).ReadToEndAsync(); - Stripe.Event e = Stripe.EventUtility.ConstructEvent( body, Request.Headers["Stripe-Signature"], endpointSecret ); - if( e.Type == "payment_intent.succeeded" ) { - - // Extract Data from payment confirm - Stripe.PaymentIntent intent = (Stripe.PaymentIntent)e.Data.Object; - string orderNumber = ""; - int userID = 0; - List productIDs = new List(); - int subtotal = 0; - int total = 0; - - KeyValuePair[] y = intent.Metadata.ToArray(); - foreach( KeyValuePair cur in y ) { - string val = cur.Key; - if( val == "ordernumber" ) { - orderNumber = cur.Value; - } else if( val == "user" ) { - userID = int.Parse( cur.Value ); - } else if( val == "products" ) { - string[] products = cur.Value.Split(','); - foreach( string product in products ) { - if ( !string.IsNullOrEmpty(product) ) { - productIDs.Add( Convert.ToInt32( product ) ); - } - } - } else if( val == "subtotal" ) { - subtotal = int.Parse( cur.Value ); - } else if( val == "total" ) { - total = int.Parse( cur.Value ); - } - } - - // Clear the cart - Account account = new() { - ID = userID - }; - await _databaseService.ClearCart( account ); - - // Add data to misox receipt - for( int i = 0; i < productIDs.Count; i++ ) { - int product = productIDs[i]; - await _databaseService.NewReceipt( new Receipt { - AccountID = userID, - ProductID = product, - ReceiptID = orderNumber, - Time = DateTime.Now, - TaxAmount = total - subtotal, - TotalCost = total, - LineItem = i - } ); - } - } else { - Console.WriteLine( "Unhandled event type: {0}", e.Type ); - } + await _paymentService.ValidatePurchase(body, Request.Headers["Stripe-Signature"].ToString()); return Ok(); - } catch( Exception ex ) { - return Content(ex.ToString()); + } catch (Exception ex) { + return NotFound(ex.ToString()); } } diff --git a/src/MistoxWebsite.Server/Controllers/PaymentMethods/IPayment.cs b/src/MistoxWebsite.Server/Controllers/PaymentMethods/IPayment.cs index bcc9ec5..25ab9d3 100644 --- a/src/MistoxWebsite.Server/Controllers/PaymentMethods/IPayment.cs +++ b/src/MistoxWebsite.Server/Controllers/PaymentMethods/IPayment.cs @@ -4,8 +4,16 @@ namespace MistoxWebsite.Server.Controllers.Payment { public interface IPayment { - public Task<(bool, string)> Purchase(string OrderNumber, Account user, List cart); + public static PaymentType _PaymentType; + public static string _EndpointSecret = ""; + + public Task<(bool, string)> TryGetCheckoutToken(string OrderNumber, Account user, List cart); + public Task ValidatePurchase(string WebHookData, string Headers); } + public enum PaymentType { + StripeIntent + } + } \ No newline at end of file diff --git a/src/MistoxWebsite.Server/Controllers/PaymentMethods/StripeIntents.cs b/src/MistoxWebsite.Server/Controllers/PaymentMethods/StripeIntents.cs index 45aae9c..f1003f8 100644 --- a/src/MistoxWebsite.Server/Controllers/PaymentMethods/StripeIntents.cs +++ b/src/MistoxWebsite.Server/Controllers/PaymentMethods/StripeIntents.cs @@ -8,11 +8,11 @@ namespace MistoxWebsite.Server.Controllers { DatabaseService _databaseService; - public StripeIntent( DatabaseService databaseService ) { + public StripeIntent(DatabaseService databaseService) { _databaseService = databaseService; } - public async Task<(bool, string)> Purchase(string OrderNumber, Account user, List cart) { + public async Task<(bool, string)> TryGetCheckoutToken(string OrderNumber, Account user, List cart) { try { // build Recipt and calculate Tax var options = new Stripe.Tax.CalculationCreateOptions { @@ -72,12 +72,71 @@ namespace MistoxWebsite.Server.Controllers { Stripe.PaymentIntent x = await intentService.CreateAsync(paymentIntent); return (true, x.ClientSecret); - } catch(Exception e) { + } catch (Exception e) { return (false, e.ToString()); } } + public async Task ValidatePurchase(string WebHookData, string Headers) { + Stripe.Event e = Stripe.EventUtility.ConstructEvent( WebHookData, Headers, IPayment._EndpointSecret ); + if (e.Type == "payment_intent.succeeded") { + // Extract Data from payment confirm + Stripe.PaymentIntent intent = (Stripe.PaymentIntent)e.Data.Object; + string orderNumber = ""; + int userID = 0; + List productIDs = new List(); + int subtotal = 0; + int total = 0; + + KeyValuePair[] y = intent.Metadata.ToArray(); + foreach (KeyValuePair cur in y) { + string val = cur.Key; + if (val == "ordernumber") { + orderNumber = cur.Value; + } + else if (val == "user") { + userID = int.Parse(cur.Value); + } + else if (val == "products") { + string[] products = cur.Value.Split(','); + foreach (string product in products) { + if (!string.IsNullOrEmpty(product)) { + productIDs.Add(Convert.ToInt32(product)); + } + } + } + else if (val == "subtotal") { + subtotal = int.Parse(cur.Value); + } + else if (val == "total") { + total = int.Parse(cur.Value); + } + } + + // Clear the cart + Account account = new() { + ID = userID + }; + await _databaseService.ClearCart(account); + + // Add data to misox receipt + for (int i = 0; i < productIDs.Count; i++) { + int product = productIDs[i]; + await _databaseService.NewReceipt(new Receipt { + AccountID = userID, + ProductID = product, + ReceiptID = orderNumber, + Time = DateTime.Now, + TaxAmount = total - subtotal, + TotalCost = total, + LineItem = i + }); + } + } else { + Console.WriteLine("Unhandled event type: {0}", e.Type); + } + } } } \ No newline at end of file diff --git a/src/MistoxWebsite.Server/Program.cs b/src/MistoxWebsite.Server/Program.cs index add3916..5ccf759 100755 --- a/src/MistoxWebsite.Server/Program.cs +++ b/src/MistoxWebsite.Server/Program.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Authentication.Cookies; using MistoxWebsite.Server.Controllers; +using MistoxWebsite.Server.Controllers.Payment; using MistoxWebsite.Server.Services; using MistoxWebsite.Server.Services.DatabaseService; using Stripe; @@ -37,8 +38,15 @@ EmailService Emailservice = new EmailService( EmailServer, EmailPort, EmailAddre builder.Services.Add( new ServiceDescriptor( typeof( EmailService ), Emailservice )); // Payment Service -string? StripeKey = Environment.GetEnvironmentVariable("StripeKey"); -StripeConfiguration.ApiKey = StripeKey; +string? PaymentService = Environment.GetEnvironmentVariable("PaymentService"); +IPayment._PaymentType = (PaymentType)Enum.Parse(typeof(PaymentType), PaymentService, true); + +if (IPayment._PaymentType == PaymentType.StripeIntent) { + string? StripeKey = Environment.GetEnvironmentVariable("StripeKey"); + StripeConfiguration.ApiKey = StripeKey; + string? StripeEndpointKey = Environment.GetEnvironmentVariable("StripeEndpointSecret"); + IPayment._EndpointSecret = string.IsNullOrEmpty(StripeEndpointKey) ? "" : StripeEndpointKey ; +} // Authentication Service builder.Services.AddAuthentication( options => {