From 4f97f8ad25669d88589214db2a47a925fdf5968d Mon Sep 17 00:00:00 2001 From: Dominic Villemure Date: Thu, 9 May 2024 18:00:42 -0400 Subject: [PATCH 1/8] #27 added more info to transaction + callback from stripe --- Directory.Packages.props | 2 +- src/Application/Application.csproj | 1 + .../Common/Interfaces/IStripeService.cs | 4 + .../Commands/ConfirmStripeTransaction.cs | 69 ++- .../Commands/CreateSessionCheckoutCommand.cs | 2 - src/Domain/Entities/UserTransaction.cs | 10 +- ...ddMoreInformationToTransaction.Designer.cs | 418 ++++++++++++++++++ ...9215538_AddMoreInformationToTransaction.cs | 106 +++++ .../ApplicationDbContextModelSnapshot.cs | 31 ++ src/Infrastructure/Stripe/StripeService.cs | 34 +- src/Web/Endpoints/Stripe.cs | 4 +- src/Web/appsettings.Development.json | 21 +- src/Web/wwwroot/api/specification.json | 90 +++- 13 files changed, 770 insertions(+), 22 deletions(-) create mode 100644 src/Infrastructure/Migrations/20240509215538_AddMoreInformationToTransaction.Designer.cs create mode 100644 src/Infrastructure/Migrations/20240509215538_AddMoreInformationToTransaction.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 74620e0..90a35bf 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -37,7 +37,7 @@ - + \ No newline at end of file diff --git a/src/Application/Application.csproj b/src/Application/Application.csproj index 8544947..e01512a 100644 --- a/src/Application/Application.csproj +++ b/src/Application/Application.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Application/Common/Interfaces/IStripeService.cs b/src/Application/Common/Interfaces/IStripeService.cs index 75edbe9..ca32184 100644 --- a/src/Application/Common/Interfaces/IStripeService.cs +++ b/src/Application/Common/Interfaces/IStripeService.cs @@ -1,7 +1,11 @@ +using Hutopy.Application.Common.Models; +using Hutopy.Application.Stripe.Commands; + namespace Hutopy.Application.Common.Interfaces; public interface IStripeService { public Task CreateCheckoutSession(int amount, string currency); + public Result ValidateTransaction(ConfirmStripeTransactionCommand request); } diff --git a/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs b/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs index 7aa8a3e..ea5a61c 100644 --- a/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs +++ b/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs @@ -1,24 +1,75 @@ using Hutopy.Application.Common.Interfaces; +using Stripe; + namespace Hutopy.Application.Stripe.Commands; -public record ConfirmStripeTransactionCommand : IRequest +public class ConfirmStripeTransactionCommand : IRequest { - public required Guid UserTransactionId { get; init; } - public required bool IsConfirmed { get; init; } + public string Id { get; set; } + public string Object { get; set; } + public int Created { get; set; } + public Data Data { get; set; } + public Request Request { get; set; } +} + +public class Data +{ + public Object Object { get; set; } +} + +public class Object +{ + public string Id { get; set; } = String.Empty; + public int Amount { get; set; } + public BillingDetails Billing_details { get; set; } = new(); + public string Calculated_statement_descriptor { get; set; } = String.Empty; + public string Currency { get; set; } = String.Empty; + public bool Paid { get; set; } + public string Payment_intent { get; set; } = String.Empty; + public string Payment_method { get; set; } = String.Empty; + public string Receipt_url { get; set; } = String.Empty; + public string Status { get; set; } = String.Empty; + public string Failure_message { get; set; } = String.Empty; +} + +public class BillingDetails +{ + public string Email { get; set; } = String.Empty; + public string Name { get; set; } = String.Empty; + public string Phone { get; set; } = String.Empty; +} + +public class Request +{ + public string Id { get; set; } = String.Empty; } public class ConfirmStripeTransactionCommandHandler( - IApplicationDbContext dbContext + IApplicationDbContext dbContext, + IStripeService stripeService ) : IRequestHandler { public async Task Handle(ConfirmStripeTransactionCommand request, CancellationToken cancellationToken) { - var transaction = await dbContext.UserTransactions.FirstOrDefaultAsync(x => x.Id == request.UserTransactionId, cancellationToken); - if (transaction is null) return ""; - transaction.IsConfirmed = request.IsConfirmed; - dbContext.UserTransactions.Update(transaction); + var lastTransaction = await dbContext.UserTransactions.OrderBy(x => x.Created).LastAsync(cancellationToken); + var stripeConfirmation = stripeService.ValidateTransaction(request); - return transaction.Id.ToString(); + if (stripeConfirmation.Succeeded) + { + lastTransaction.IsConfirmed = true; + lastTransaction.Paid = request.Data.Object.Paid; + lastTransaction.StripeChargeId = request.Data.Object.Id; + lastTransaction.StripeEventId = request.Id; + lastTransaction.StripeReceiptUrl = request.Data.Object.Receipt_url; + lastTransaction.StripePaymentIntent = request.Data.Object.Payment_intent; + lastTransaction.StripePaymentMethod = request.Data.Object.Payment_method; + lastTransaction.StripeBillingDetailEmail = request.Data.Object.Billing_details.Email; + lastTransaction.StripeBillingDetailName = request.Data.Object.Billing_details.Name; + } + + await dbContext.SaveChangesAsync(cancellationToken); + + return ""; } } diff --git a/src/Application/Stripe/Commands/CreateSessionCheckoutCommand.cs b/src/Application/Stripe/Commands/CreateSessionCheckoutCommand.cs index b428733..28e9d72 100644 --- a/src/Application/Stripe/Commands/CreateSessionCheckoutCommand.cs +++ b/src/Application/Stripe/Commands/CreateSessionCheckoutCommand.cs @@ -22,9 +22,7 @@ public class CreateSessionCheckoutCommandHandler( // ReSharper disable once PossibleLossOfFraction decimal priceInDollars = (request.Amount / 100); - - //todo: Need to add this transaction after the confirmation from stripe in the frontEnd ( redirect, re-call backend ) var userTransaction = new UserTransaction { Currency = request.Currency, Amount = priceInDollars, TipMessage = request.TipMessage, ApplicationUserId = request.CreatorId diff --git a/src/Domain/Entities/UserTransaction.cs b/src/Domain/Entities/UserTransaction.cs index b879fd2..5ab7314 100644 --- a/src/Domain/Entities/UserTransaction.cs +++ b/src/Domain/Entities/UserTransaction.cs @@ -8,5 +8,13 @@ public class UserTransaction : BaseAuditableEntity // Foreign key to ApplicationUser public string ApplicationUserId { get; set; } = string.Empty; - public bool IsConfirmed { get; set; } = false; + public bool IsConfirmed { get; set; } + public string StripeEventId { get; set; } = string.Empty; + public string StripeChargeId { get; set; } = string.Empty; + public string StripePaymentIntent { get; set; } = string.Empty; + public string StripePaymentMethod { get; set; } = string.Empty; + public string StripeReceiptUrl { get; set; } = string.Empty; + public string StripeBillingDetailEmail { get; set; } = string.Empty; + public string StripeBillingDetailName { get; set; } = string.Empty; + public bool Paid { get; set; } } diff --git a/src/Infrastructure/Migrations/20240509215538_AddMoreInformationToTransaction.Designer.cs b/src/Infrastructure/Migrations/20240509215538_AddMoreInformationToTransaction.Designer.cs new file mode 100644 index 0000000..f8c9e81 --- /dev/null +++ b/src/Infrastructure/Migrations/20240509215538_AddMoreInformationToTransaction.Designer.cs @@ -0,0 +1,418 @@ +// +using System; +using Hutopy.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Hutopy.Infrastructure.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20240509215538_AddMoreInformationToTransaction")] + partial class AddMoreInformationToTransaction + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Hutopy.Domain.Entities.FutureCreator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Created") + .HasColumnType("datetimeoffset"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("EmailAddress") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastModified") + .HasColumnType("datetimeoffset"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ReasonToJoin") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SocialNetworkAccount") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("FutureCreators"); + }); + + modelBuilder.Entity("Hutopy.Domain.Entities.UserTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ApplicationUserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Created") + .HasColumnType("datetimeoffset"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Currency") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsConfirmed") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetimeoffset"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Paid") + .HasColumnType("bit"); + + b.Property("StripeBillingDetailEmail") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StripeBillingDetailName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StripeChargeId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StripeEventId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StripePaymentIntent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StripePaymentMethod") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StripeReceiptUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("TipMessage") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId"); + + b.ToTable("UserTransactions"); + }); + + modelBuilder.Entity("Hutopy.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Hutopy.Domain.Entities.UserTransaction", b => + { + b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Migrations/20240509215538_AddMoreInformationToTransaction.cs b/src/Infrastructure/Migrations/20240509215538_AddMoreInformationToTransaction.cs new file mode 100644 index 0000000..d91d3cf --- /dev/null +++ b/src/Infrastructure/Migrations/20240509215538_AddMoreInformationToTransaction.cs @@ -0,0 +1,106 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Hutopy.Infrastructure.Migrations +{ + /// + public partial class AddMoreInformationToTransaction : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Paid", + table: "UserTransactions", + type: "bit", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "StripeBillingDetailEmail", + table: "UserTransactions", + type: "nvarchar(max)", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "StripeBillingDetailName", + table: "UserTransactions", + type: "nvarchar(max)", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "StripeChargeId", + table: "UserTransactions", + type: "nvarchar(max)", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "StripeEventId", + table: "UserTransactions", + type: "nvarchar(max)", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "StripePaymentIntent", + table: "UserTransactions", + type: "nvarchar(max)", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "StripePaymentMethod", + table: "UserTransactions", + type: "nvarchar(max)", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "StripeReceiptUrl", + table: "UserTransactions", + type: "nvarchar(max)", + nullable: false, + defaultValue: ""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Paid", + table: "UserTransactions"); + + migrationBuilder.DropColumn( + name: "StripeBillingDetailEmail", + table: "UserTransactions"); + + migrationBuilder.DropColumn( + name: "StripeBillingDetailName", + table: "UserTransactions"); + + migrationBuilder.DropColumn( + name: "StripeChargeId", + table: "UserTransactions"); + + migrationBuilder.DropColumn( + name: "StripeEventId", + table: "UserTransactions"); + + migrationBuilder.DropColumn( + name: "StripePaymentIntent", + table: "UserTransactions"); + + migrationBuilder.DropColumn( + name: "StripePaymentMethod", + table: "UserTransactions"); + + migrationBuilder.DropColumn( + name: "StripeReceiptUrl", + table: "UserTransactions"); + } + } +} diff --git a/src/Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs index dedb234..cb3b55f 100644 --- a/src/Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs @@ -102,6 +102,37 @@ namespace Hutopy.Infrastructure.Migrations b.Property("LastModifiedBy") .HasColumnType("nvarchar(max)"); + b.Property("Paid") + .HasColumnType("bit"); + + b.Property("StripeBillingDetailEmail") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StripeBillingDetailName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StripeChargeId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StripeEventId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StripePaymentIntent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StripePaymentMethod") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StripeReceiptUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + b.Property("TipMessage") .IsRequired() .HasColumnType("nvarchar(max)"); diff --git a/src/Infrastructure/Stripe/StripeService.cs b/src/Infrastructure/Stripe/StripeService.cs index 881b6c4..dc84e34 100644 --- a/src/Infrastructure/Stripe/StripeService.cs +++ b/src/Infrastructure/Stripe/StripeService.cs @@ -1,13 +1,20 @@ using Stripe; using Stripe.Checkout; using Hutopy.Application.Common.Interfaces; +using Microsoft.AspNetCore.Http; +using Hutopy.Application.Common.Models; +using Hutopy.Application.Stripe.Commands; namespace Hutopy.Infrastructure.Stripe; public class StripeService : IStripeService { - public StripeService() + const string EndpointSecret = ""; + private readonly IHttpContextAccessor _httpContextAccessor; + + public StripeService(IHttpContextAccessor httpContextAccessor) { + _httpContextAccessor = httpContextAccessor; StripeConfiguration.ApiKey = ""; } @@ -31,7 +38,7 @@ public class StripeService : IStripeService ], Mode = "payment", UiMode = "embedded", - ReturnUrl = "https://zealous-bay-08204590f.5.azurestaticapps.net/paymentcompleted", + ReturnUrl = "https://hutopy.ca/paymentcompleted", }; var service = new SessionService(); @@ -39,4 +46,27 @@ public class StripeService : IStripeService return session.ClientSecret; } + + public Result ValidateTransaction(ConfirmStripeTransactionCommand request) + { + try + { + if (request.Data.Object.Status is "succeeded") + { + return new Result(true, new List()); + } + + return new Result(false, new List()); + + } + catch (StripeException e) + { + Console.WriteLine("Error: {0}", e.Message); + return new Result(false, new List{e.Message}); + } + catch (Exception e) + { + return new Result(false, new List{e.Message}); + } + } } diff --git a/src/Web/Endpoints/Stripe.cs b/src/Web/Endpoints/Stripe.cs index b944419..264f4c8 100644 --- a/src/Web/Endpoints/Stripe.cs +++ b/src/Web/Endpoints/Stripe.cs @@ -16,8 +16,8 @@ public class Stripe : EndpointGroupBase return sender.Send(command); } - private static Task ConfirmTransaction(ISender sender, ConfirmStripeTransactionCommand command) + private async static Task ConfirmTransaction(ISender sender, ConfirmStripeTransactionCommand command) { - return sender.Send(command); + return await sender.Send(command); } } diff --git a/src/Web/appsettings.Development.json b/src/Web/appsettings.Development.json index 84308c9..b98033c 100644 --- a/src/Web/appsettings.Development.json +++ b/src/Web/appsettings.Development.json @@ -2,9 +2,28 @@ "Logging": { "LogLevel": { "Default": "Information", - "Microsoft": "Warning", + "Microsoft": "Information", "Microsoft.AspNetCore.SpaProxy": "Information", "Microsoft.Hosting.Lifetime": "Information" } + }, + "Google": { + "ClientId": "468391910875-78sfopq1t12ulrv4f5vj227j45guuj66.apps.googleusercontent.com", + "ClientSecret": "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", + "ProjectId": "hutopy-420016", + "AuthUri": "https://accounts.google.com/o/oauth2/auth", + "TokenUri": "https://oauth2.googleapis.com/token", + "AuthProviderX509CertUrl": "https://www.googleapis.com/oauth2/v1/certs", + "RedirectUris": [ + "https://hutopy.ca", + "https://hutopy.com", + "http://localhost" + ], + "JavascriptOrigins": [ + "https://hutopy.ca", + "https://hutopy.com", + "http://localhost" + ] } } + diff --git a/src/Web/wwwroot/api/specification.json b/src/Web/wwwroot/api/specification.json index 5a1838c..9c7a80c 100644 --- a/src/Web/wwwroot/api/specification.json +++ b/src/Web/wwwroot/api/specification.json @@ -723,12 +723,94 @@ "type": "object", "additionalProperties": false, "properties": { - "userTransactionId": { - "type": "string", - "format": "guid" + "id": { + "type": "string" }, - "isConfirmed": { + "object": { + "type": "string" + }, + "created": { + "type": "integer", + "format": "int32" + }, + "data": { + "$ref": "#/components/schemas/Data" + }, + "request": { + "$ref": "#/components/schemas/Request" + } + } + }, + "Data": { + "type": "object", + "additionalProperties": false, + "properties": { + "object": { + "$ref": "#/components/schemas/Object" + } + } + }, + "Object": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "amount": { + "type": "integer", + "format": "int32" + }, + "billing_details": { + "$ref": "#/components/schemas/BillingDetails" + }, + "calculated_statement_descriptor": { + "type": "string" + }, + "currency": { + "type": "string" + }, + "paid": { "type": "boolean" + }, + "payment_intent": { + "type": "string" + }, + "payment_method": { + "type": "string" + }, + "receipt_url": { + "type": "string" + }, + "status": { + "type": "string" + }, + "failure_message": { + "type": "string" + } + } + }, + "BillingDetails": { + "type": "object", + "additionalProperties": false, + "properties": { + "email": { + "type": "string" + }, + "name": { + "type": "string" + }, + "phone": { + "type": "string" + } + } + }, + "Request": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string" } } }, From c06c3039e8f9d1e34839659684f77be912f70e9f Mon Sep 17 00:00:00 2001 From: Dominic Villemure Date: Thu, 9 May 2024 21:58:21 -0400 Subject: [PATCH 2/8] #27 not needed stripe in application --- src/Application/Application.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Application/Application.csproj b/src/Application/Application.csproj index e01512a..8544947 100644 --- a/src/Application/Application.csproj +++ b/src/Application/Application.csproj @@ -10,7 +10,6 @@ - From e3eed8adebd1d3af1f405dab88f1d82b4c55a1f8 Mon Sep 17 00:00:00 2001 From: Dominic Villemure Date: Thu, 9 May 2024 21:59:01 -0400 Subject: [PATCH 3/8] #27 remove using --- src/Application/Stripe/Commands/ConfirmStripeTransaction.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs b/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs index ea5a61c..eb074a0 100644 --- a/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs +++ b/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs @@ -1,6 +1,4 @@ using Hutopy.Application.Common.Interfaces; -using Stripe; - namespace Hutopy.Application.Stripe.Commands; public class ConfirmStripeTransactionCommand : IRequest From 59b6dc265da577b93240f028ee32f1fc9ce6d63c Mon Sep 17 00:00:00 2001 From: Dominic Villemure Date: Thu, 9 May 2024 22:00:00 -0400 Subject: [PATCH 4/8] #27 remove secrets --- src/Web/appsettings.Development.json | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/Web/appsettings.Development.json b/src/Web/appsettings.Development.json index b98033c..be4dfad 100644 --- a/src/Web/appsettings.Development.json +++ b/src/Web/appsettings.Development.json @@ -6,24 +6,6 @@ "Microsoft.AspNetCore.SpaProxy": "Information", "Microsoft.Hosting.Lifetime": "Information" } - }, - "Google": { - "ClientId": "468391910875-78sfopq1t12ulrv4f5vj227j45guuj66.apps.googleusercontent.com", - "ClientSecret": "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", - "ProjectId": "hutopy-420016", - "AuthUri": "https://accounts.google.com/o/oauth2/auth", - "TokenUri": "https://oauth2.googleapis.com/token", - "AuthProviderX509CertUrl": "https://www.googleapis.com/oauth2/v1/certs", - "RedirectUris": [ - "https://hutopy.ca", - "https://hutopy.com", - "http://localhost" - ], - "JavascriptOrigins": [ - "https://hutopy.ca", - "https://hutopy.com", - "http://localhost" - ] } } From 8789001babfb1e3e9df339f00e5693e0d8d1c263 Mon Sep 17 00:00:00 2001 From: Dominic Villemure Date: Thu, 9 May 2024 22:02:21 -0400 Subject: [PATCH 5/8] #27 remove appsettings.Development.json from tracked files --- .gitignore | 4 ++++ src/Web/appsettings.Development.json | 11 ----------- 2 files changed, 4 insertions(+), 11 deletions(-) delete mode 100644 src/Web/appsettings.Development.json diff --git a/.gitignore b/.gitignore index 87e0e84..6614a0b 100644 --- a/.gitignore +++ b/.gitignore @@ -485,3 +485,7 @@ $RECYCLE.BIN/ # Other IDE files .vscode/ .idea/ + + +#AppSettings dev +*/appsettings.Development.json diff --git a/src/Web/appsettings.Development.json b/src/Web/appsettings.Development.json deleted file mode 100644 index be4dfad..0000000 --- a/src/Web/appsettings.Development.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Information", - "Microsoft.AspNetCore.SpaProxy": "Information", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} - From 0bcbd9fc0ea7ac16619d550198d6e1322af1a264 Mon Sep 17 00:00:00 2001 From: Dominic Villemure Date: Thu, 9 May 2024 22:03:05 -0400 Subject: [PATCH 6/8] #26 specific ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6614a0b..d6fda7d 100644 --- a/.gitignore +++ b/.gitignore @@ -489,3 +489,4 @@ $RECYCLE.BIN/ #AppSettings dev */appsettings.Development.json +/src/Web/appsettings.Development.json From 3f2056385001cbf992e6eec382c879f0ec3ea836 Mon Sep 17 00:00:00 2001 From: Dominic Villemure Date: Thu, 9 May 2024 22:03:55 -0400 Subject: [PATCH 7/8] #27 dist file for devs --- src/Web/appsettings.Development.dist | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/Web/appsettings.Development.dist diff --git a/src/Web/appsettings.Development.dist b/src/Web/appsettings.Development.dist new file mode 100644 index 0000000..58d6716 --- /dev/null +++ b/src/Web/appsettings.Development.dist @@ -0,0 +1,29 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Information", + "Microsoft.AspNetCore.SpaProxy": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "Google": { + "ClientId": "", + "ClientSecret": "", + "ProjectId": "", + "AuthUri": "", + "TokenUri": "", + "AuthProviderX509CertUrl": "", + "RedirectUris": [ + "https://hutopy.ca", + "https://hutopy.com", + "http://localhost" + ], + "JavascriptOrigins": [ + "https://hutopy.ca", + "https://hutopy.com", + "http://localhost" + ] + } +} + From 5161e3a91ac592b2ffe787bb6d30c32582ad2be2 Mon Sep 17 00:00:00 2001 From: Dominic Villemure Date: Sun, 12 May 2024 16:40:24 -0400 Subject: [PATCH 8/8] #27 get last receipt, get minimalUser, get my user --- .../Common/Interfaces/IStripeService.cs | 2 +- .../Commands/ConfirmStripeTransaction.cs | 16 +-- .../Commands/CreateSessionCheckoutCommand.cs | 2 +- .../Stripe/Queries/GetMyLastReceipt.cs | 29 +++++ .../Stripe/Queries/MyLastReceiptDto.cs | 6 + .../{ => GetCurrentUser}/GetCurrentUser.cs | 3 +- .../Queries/{ => GetCurrentUser}/UserDto.cs | 1 + .../UserTransactionDto.cs | 0 .../Queries/GetMinimalUser/GetMinimalUser.cs | 28 +++++ .../Queries/GetMinimalUser/MinimalUserDto.cs | 8 ++ src/Domain/Entities/UserTransaction.cs | 2 +- src/Infrastructure/Services/UserService.cs | 14 ++- src/Infrastructure/Stripe/StripeService.cs | 8 +- src/Web/Endpoints/GetMyUser.cs | 1 + src/Web/Endpoints/Stripe.cs | 7 ++ src/Web/Endpoints/Users.cs | 7 ++ src/Web/wwwroot/api/specification.json | 106 +++++++++++++++++- 17 files changed, 216 insertions(+), 24 deletions(-) create mode 100644 src/Application/Stripe/Queries/GetMyLastReceipt.cs create mode 100644 src/Application/Stripe/Queries/MyLastReceiptDto.cs rename src/Application/Users/Queries/{ => GetCurrentUser}/GetCurrentUser.cs (93%) rename src/Application/Users/Queries/{ => GetCurrentUser}/UserDto.cs (83%) rename src/Application/Users/Queries/{ => GetCurrentUser}/UserTransactionDto.cs (100%) create mode 100644 src/Application/Users/Queries/GetMinimalUser/GetMinimalUser.cs create mode 100644 src/Application/Users/Queries/GetMinimalUser/MinimalUserDto.cs diff --git a/src/Application/Common/Interfaces/IStripeService.cs b/src/Application/Common/Interfaces/IStripeService.cs index ca32184..6737a3d 100644 --- a/src/Application/Common/Interfaces/IStripeService.cs +++ b/src/Application/Common/Interfaces/IStripeService.cs @@ -6,6 +6,6 @@ namespace Hutopy.Application.Common.Interfaces; public interface IStripeService { - public Task CreateCheckoutSession(int amount, string currency); + public Task CreateCheckoutSession(int amount, string creatorId, string currency); public Result ValidateTransaction(ConfirmStripeTransactionCommand request); } diff --git a/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs b/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs index eb074a0..0ddd17e 100644 --- a/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs +++ b/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs @@ -56,15 +56,15 @@ public class ConfirmStripeTransactionCommandHandler( if (stripeConfirmation.Succeeded) { lastTransaction.IsConfirmed = true; - lastTransaction.Paid = request.Data.Object.Paid; - lastTransaction.StripeChargeId = request.Data.Object.Id; - lastTransaction.StripeEventId = request.Id; - lastTransaction.StripeReceiptUrl = request.Data.Object.Receipt_url; - lastTransaction.StripePaymentIntent = request.Data.Object.Payment_intent; - lastTransaction.StripePaymentMethod = request.Data.Object.Payment_method; - lastTransaction.StripeBillingDetailEmail = request.Data.Object.Billing_details.Email; - lastTransaction.StripeBillingDetailName = request.Data.Object.Billing_details.Name; } + lastTransaction.Paid = request.Data.Object.Paid; + lastTransaction.StripeChargeId = request.Data.Object.Id; + lastTransaction.StripeEventId = request.Id; + lastTransaction.StripeReceiptUrl = request.Data.Object.Receipt_url; + lastTransaction.StripePaymentIntent = request.Data.Object.Payment_intent; + lastTransaction.StripePaymentMethod = request.Data.Object.Payment_method; + lastTransaction.StripeBillingDetailEmail = request.Data.Object.Billing_details.Email; + lastTransaction.StripeBillingDetailName = request.Data.Object.Billing_details.Name; await dbContext.SaveChangesAsync(cancellationToken); diff --git a/src/Application/Stripe/Commands/CreateSessionCheckoutCommand.cs b/src/Application/Stripe/Commands/CreateSessionCheckoutCommand.cs index 28e9d72..681c4d8 100644 --- a/src/Application/Stripe/Commands/CreateSessionCheckoutCommand.cs +++ b/src/Application/Stripe/Commands/CreateSessionCheckoutCommand.cs @@ -18,7 +18,7 @@ public class CreateSessionCheckoutCommandHandler( { public async Task Handle(CreateSessionCheckoutCommand request, CancellationToken cancellationToken) { - var stripeSecret = await stripeService.CreateCheckoutSession(request.Amount, request.Currency); + var stripeSecret = await stripeService.CreateCheckoutSession(request.Amount, request.CreatorId, request.Currency); // ReSharper disable once PossibleLossOfFraction decimal priceInDollars = (request.Amount / 100); diff --git a/src/Application/Stripe/Queries/GetMyLastReceipt.cs b/src/Application/Stripe/Queries/GetMyLastReceipt.cs new file mode 100644 index 0000000..b8a0602 --- /dev/null +++ b/src/Application/Stripe/Queries/GetMyLastReceipt.cs @@ -0,0 +1,29 @@ +using Hutopy.Application.Common.Interfaces; + +namespace Hutopy.Application.Stripe.Queries; + +public record GetMyLastReceiptQuery : IRequest +{ + public string Email { get; set; } = string.Empty; + public string CreatorId { get; set; } = string.Empty; +}; + +public class GetMyLastReceiptQueryHandler( + IApplicationDbContext dbContext + ) + : IRequestHandler +{ + public async Task Handle(GetMyLastReceiptQuery request, CancellationToken cancellationToken) + { + var lastTransaction = await dbContext.UserTransactions.OrderBy(x => x.Created) + .LastOrDefaultAsync(x => x.ApplicationUserId == request.CreatorId && x.StripeBillingDetailEmail == request.Email, + cancellationToken); + + var receiptUrl = new MyLastReceiptDto + { + ReceiptUrl = lastTransaction?.StripeReceiptUrl ?? "", + }; + + return receiptUrl; + } +} diff --git a/src/Application/Stripe/Queries/MyLastReceiptDto.cs b/src/Application/Stripe/Queries/MyLastReceiptDto.cs new file mode 100644 index 0000000..5ce1645 --- /dev/null +++ b/src/Application/Stripe/Queries/MyLastReceiptDto.cs @@ -0,0 +1,6 @@ +namespace Hutopy.Application.Stripe.Queries; + +public class MyLastReceiptDto +{ + public string ReceiptUrl { get; set; } +} diff --git a/src/Application/Users/Queries/GetCurrentUser.cs b/src/Application/Users/Queries/GetCurrentUser/GetCurrentUser.cs similarity index 93% rename from src/Application/Users/Queries/GetCurrentUser.cs rename to src/Application/Users/Queries/GetCurrentUser/GetCurrentUser.cs index a087cf8..82dc2f8 100644 --- a/src/Application/Users/Queries/GetCurrentUser.cs +++ b/src/Application/Users/Queries/GetCurrentUser/GetCurrentUser.cs @@ -18,12 +18,11 @@ public class GetCurrentUserQueryHandler( var currentUserId = new Guid(identityUser?.Id ?? ""); var transactions = await context.UserTransactions - .Where(x => x.Id == currentUserId) + .Where(x => x.ApplicationUserId == currentUserId.ToString()) .OrderBy(x => x.LastModified) .ProjectTo(mapper.ConfigurationProvider) .ToListAsync(cancellationToken); - var user = new UserDto() { Id = currentUserId, diff --git a/src/Application/Users/Queries/UserDto.cs b/src/Application/Users/Queries/GetCurrentUser/UserDto.cs similarity index 83% rename from src/Application/Users/Queries/UserDto.cs rename to src/Application/Users/Queries/GetCurrentUser/UserDto.cs index da62e26..acece9c 100644 --- a/src/Application/Users/Queries/UserDto.cs +++ b/src/Application/Users/Queries/GetCurrentUser/UserDto.cs @@ -7,6 +7,7 @@ public class UserDto public required string FirstName { get; init; } public required string LastName { get; init; } + public string UserName { get; init; } = String.Empty; public List UserTransactions { get; init; } = []; } diff --git a/src/Application/Users/Queries/UserTransactionDto.cs b/src/Application/Users/Queries/GetCurrentUser/UserTransactionDto.cs similarity index 100% rename from src/Application/Users/Queries/UserTransactionDto.cs rename to src/Application/Users/Queries/GetCurrentUser/UserTransactionDto.cs diff --git a/src/Application/Users/Queries/GetMinimalUser/GetMinimalUser.cs b/src/Application/Users/Queries/GetMinimalUser/GetMinimalUser.cs new file mode 100644 index 0000000..4206c79 --- /dev/null +++ b/src/Application/Users/Queries/GetMinimalUser/GetMinimalUser.cs @@ -0,0 +1,28 @@ +using Hutopy.Domain.Interfaces; + +namespace Hutopy.Application.Users.Queries.GetMinimalUser; + +public record GetMinimalUserQuery : IRequest +{ + public string UserId { get; set; } = string.Empty; +}; + +public class GetMinimalUserQueryHandler( + IUserService userService + ) + : IRequestHandler +{ + public async Task Handle(GetMinimalUserQuery request, CancellationToken cancellationToken) + { + var identityUser = await userService.FindUserByIdAsync(request.UserId); + + var user = new MinimalUserDto() + { + FirstName = identityUser?.FirstName ?? "", + LastName = identityUser?.LastName ?? "", + UserName = identityUser?.UserName ?? "" + }; + + return user; + } +} diff --git a/src/Application/Users/Queries/GetMinimalUser/MinimalUserDto.cs b/src/Application/Users/Queries/GetMinimalUser/MinimalUserDto.cs new file mode 100644 index 0000000..0b01fba --- /dev/null +++ b/src/Application/Users/Queries/GetMinimalUser/MinimalUserDto.cs @@ -0,0 +1,8 @@ +namespace Hutopy.Application.Users.Queries.GetMinimalUser; + +public class MinimalUserDto +{ + public required string FirstName { get; init; } + public required string LastName { get; init; } + public string UserName { get; init; } = String.Empty; +} diff --git a/src/Domain/Entities/UserTransaction.cs b/src/Domain/Entities/UserTransaction.cs index 5ab7314..78e32d4 100644 --- a/src/Domain/Entities/UserTransaction.cs +++ b/src/Domain/Entities/UserTransaction.cs @@ -7,7 +7,7 @@ public class UserTransaction : BaseAuditableEntity public string TipMessage { get; set; } = string.Empty; // Foreign key to ApplicationUser - public string ApplicationUserId { get; set; } = string.Empty; + public required string ApplicationUserId { get; set; } public bool IsConfirmed { get; set; } public string StripeEventId { get; set; } = string.Empty; public string StripeChargeId { get; set; } = string.Empty; diff --git a/src/Infrastructure/Services/UserService.cs b/src/Infrastructure/Services/UserService.cs index b0704c2..66dca70 100644 --- a/src/Infrastructure/Services/UserService.cs +++ b/src/Infrastructure/Services/UserService.cs @@ -1,11 +1,13 @@ -using Hutopy.Domain.Interfaces; +using System.Security.Claims; +using Hutopy.Domain.Interfaces; using Hutopy.Domain.Models; using Hutopy.Infrastructure.Identity; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; namespace Hutopy.Infrastructure.Services; -public class UserService(UserManager userManager) : IUserService +public class UserService(UserManager userManager, IHttpContextAccessor contextAccessor) : IUserService { public async Task CreateUserAsync(string email, string userName, string firstName, string lastName, string password) { @@ -38,7 +40,7 @@ public class UserService(UserManager userManager) : IUserServic UserName = response.UserName, FirstName = response.FirstName, LastName = response.LastName, - Email = response.Email + Email = response.Email, }; return userModel; @@ -47,13 +49,13 @@ public class UserService(UserManager userManager) : IUserServic public async Task GetCurrentUserAsync() { // todo: Get the id of the user doing the request. - var userId = ""; - if (string.IsNullOrEmpty(userId)) + var currentUserId = contextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; + if (string.IsNullOrEmpty(currentUserId)) { return null; } - return await FindUserByIdAsync(userId); + return await FindUserByIdAsync(currentUserId); } public async Task FindUserByEmailAsync(string email) diff --git a/src/Infrastructure/Stripe/StripeService.cs b/src/Infrastructure/Stripe/StripeService.cs index dc84e34..061f9e2 100644 --- a/src/Infrastructure/Stripe/StripeService.cs +++ b/src/Infrastructure/Stripe/StripeService.cs @@ -9,7 +9,6 @@ namespace Hutopy.Infrastructure.Stripe; public class StripeService : IStripeService { - const string EndpointSecret = ""; private readonly IHttpContextAccessor _httpContextAccessor; public StripeService(IHttpContextAccessor httpContextAccessor) @@ -18,7 +17,7 @@ public class StripeService : IStripeService StripeConfiguration.ApiKey = ""; } - public async Task CreateCheckoutSession(int amount, string currency = "cad") + public async Task CreateCheckoutSession(int amount, string creatorId, string currency = "cad") { var options = new SessionCreateOptions { @@ -38,7 +37,9 @@ public class StripeService : IStripeService ], Mode = "payment", UiMode = "embedded", - ReturnUrl = "https://hutopy.ca/paymentcompleted", + ReturnUrl = $"https://hutopy.ca/paymentcompleted?creatorId={creatorId}", + InvoiceCreation = new SessionInvoiceCreationOptions(){ Enabled = true}, + ClientReferenceId = creatorId }; var service = new SessionService(); @@ -57,7 +58,6 @@ public class StripeService : IStripeService } return new Result(false, new List()); - } catch (StripeException e) { diff --git a/src/Web/Endpoints/GetMyUser.cs b/src/Web/Endpoints/GetMyUser.cs index 5fe1c79..d7c78a6 100644 --- a/src/Web/Endpoints/GetMyUser.cs +++ b/src/Web/Endpoints/GetMyUser.cs @@ -7,6 +7,7 @@ public class GetMyUser : EndpointGroupBase public override void Map(WebApplication app) { app.MapGroup(this) + .RequireAuthorization() .MapGet(GetCurrentUser); } diff --git a/src/Web/Endpoints/Stripe.cs b/src/Web/Endpoints/Stripe.cs index 264f4c8..2eb01dc 100644 --- a/src/Web/Endpoints/Stripe.cs +++ b/src/Web/Endpoints/Stripe.cs @@ -1,4 +1,5 @@ using Hutopy.Application.Stripe.Commands; +using Hutopy.Application.Stripe.Queries; namespace Hutopy.Web.Endpoints; @@ -8,6 +9,7 @@ public class Stripe : EndpointGroupBase { app.MapGroup(this) .MapPost(ConfirmTransaction, "/confirmTransaction") + .MapGet(GetMyLastReceipt, "/getMyLastReceipt") .MapPost(CreateSessionCheckout); } @@ -20,4 +22,9 @@ public class Stripe : EndpointGroupBase { return await sender.Send(command); } + + private static async Task GetMyLastReceipt(ISender sender, [AsParameters] GetMyLastReceiptQuery query) + { + return await sender.Send(query); + } } diff --git a/src/Web/Endpoints/Users.cs b/src/Web/Endpoints/Users.cs index 0492fb5..1cbd880 100644 --- a/src/Web/Endpoints/Users.cs +++ b/src/Web/Endpoints/Users.cs @@ -1,4 +1,5 @@ using Hutopy.Application.Users.Commands; +using Hutopy.Application.Users.Queries.GetMinimalUser; using Hutopy.Domain.Interfaces; using Hutopy.Infrastructure.Identity; @@ -10,6 +11,7 @@ public class Users : EndpointGroupBase { app.MapGroup(this) .MapPost(CreateUser) + .MapGet(GetMinimalUser) .MapIdentityApi(); } @@ -18,4 +20,9 @@ public class Users : EndpointGroupBase await userService.CreateUserAsync(command.EmailAddress, command.UserName, command.FirstName, command.LastName, command.Password); return await sender.Send(command); } + + private static async Task GetMinimalUser(ISender sender, [AsParameters] GetMinimalUserQuery query) + { + return await sender.Send(query); + } } diff --git a/src/Web/wwwroot/api/specification.json b/src/Web/wwwroot/api/specification.json index 9c7a80c..d0680de 100644 --- a/src/Web/wwwroot/api/specification.json +++ b/src/Web/wwwroot/api/specification.json @@ -23,7 +23,12 @@ } } } - } + }, + "security": [ + { + "JWT": [] + } + ] } }, "/api/JoinUs": { @@ -131,6 +136,48 @@ } } }, + "/api/Stripe/getMyLastReceipt": { + "get": { + "tags": [ + "Stripe" + ], + "operationId": "GetMyLastReceipt", + "parameters": [ + { + "name": "Email", + "in": "query", + "required": true, + "schema": { + "type": "string", + "nullable": true + }, + "x-position": 1 + }, + { + "name": "CreatorId", + "in": "query", + "required": true, + "schema": { + "type": "string", + "nullable": true + }, + "x-position": 2 + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MyLastReceiptDto" + } + } + } + } + } + } + }, "/api/Stripe": { "post": { "tags": [ @@ -194,6 +241,36 @@ } } } + }, + "get": { + "tags": [ + "Users" + ], + "operationId": "GetMinimalUser", + "parameters": [ + { + "name": "UserId", + "in": "query", + "required": true, + "schema": { + "type": "string", + "nullable": true + }, + "x-position": 1 + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MinimalUserDto" + } + } + } + } + } } }, "/api/Users/register": { @@ -625,6 +702,9 @@ "lastName": { "type": "string" }, + "userName": { + "type": "string" + }, "userTransactions": { "type": "array", "items": { @@ -814,6 +894,15 @@ } } }, + "MyLastReceiptDto": { + "type": "object", + "additionalProperties": false, + "properties": { + "receiptUrl": { + "type": "string" + } + } + }, "CreateSessionCheckoutCommand": { "type": "object", "additionalProperties": false, @@ -854,6 +943,21 @@ } } }, + "MinimalUserDto": { + "type": "object", + "additionalProperties": false, + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "userName": { + "type": "string" + } + } + }, "HttpValidationProblemDetails": { "allOf": [ {