diff --git a/.gitignore b/.gitignore
index 87e0e84..d6fda7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -485,3 +485,8 @@ $RECYCLE.BIN/
# Other IDE files
.vscode/
.idea/
+
+
+#AppSettings dev
+*/appsettings.Development.json
+/src/Web/appsettings.Development.json
diff --git a/Directory.Packages.props b/Directory.Packages.props
index d3ae35d..b8593ab 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -40,7 +40,7 @@
-
+
\ No newline at end of file
diff --git a/src/Application/Common/Interfaces/IStripeService.cs b/src/Application/Common/Interfaces/IStripeService.cs
index 75edbe9..6737a3d 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 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 7aa8a3e..0ddd17e 100644
--- a/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs
+++ b/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs
@@ -1,24 +1,73 @@
using Hutopy.Application.Common.Interfaces;
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..681c4d8 100644
--- a/src/Application/Stripe/Commands/CreateSessionCheckoutCommand.cs
+++ b/src/Application/Stripe/Commands/CreateSessionCheckoutCommand.cs
@@ -18,13 +18,11 @@ 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);
-
- //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/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 b879fd2..78e32d4 100644
--- a/src/Domain/Entities/UserTransaction.cs
+++ b/src/Domain/Entities/UserTransaction.cs
@@ -7,6 +7,14 @@ public class UserTransaction : BaseAuditableEntity
public string TipMessage { get; set; } = string.Empty;
// Foreign key to ApplicationUser
- public string ApplicationUserId { get; set; } = string.Empty;
- public bool IsConfirmed { get; set; } = false;
+ 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;
+ 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/Services/UserService.cs b/src/Infrastructure/Services/UserService.cs
index 12910a4..ebb5695 100644
--- a/src/Infrastructure/Services/UserService.cs
+++ b/src/Infrastructure/Services/UserService.cs
@@ -1,16 +1,16 @@
-using System.Text;
+using System.Text;
using Google.Apis.Oauth2.v2.Data;
+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
{
- private readonly Random _random = new(DateTime.Now.Millisecond);
-
public async Task CreateUserAsync(string email, string userName, string firstName, string lastName, string password)
{
var applicationUser = new ApplicationUser
@@ -47,7 +47,7 @@ public class UserService(UserManager userManager) : IUserServic
UserName = response.UserName,
FirstName = response.FirstName,
LastName = response.LastName,
- Email = response.Email
+ Email = response.Email,
};
return userModel;
@@ -56,13 +56,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 881b6c4..061f9e2 100644
--- a/src/Infrastructure/Stripe/StripeService.cs
+++ b/src/Infrastructure/Stripe/StripeService.cs
@@ -1,17 +1,23 @@
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()
+ private readonly IHttpContextAccessor _httpContextAccessor;
+
+ public StripeService(IHttpContextAccessor httpContextAccessor)
{
+ _httpContextAccessor = httpContextAccessor;
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
{
@@ -31,7 +37,9 @@ public class StripeService : IStripeService
],
Mode = "payment",
UiMode = "embedded",
- ReturnUrl = "https://zealous-bay-08204590f.5.azurestaticapps.net/paymentcompleted",
+ ReturnUrl = $"https://hutopy.ca/paymentcompleted?creatorId={creatorId}",
+ InvoiceCreation = new SessionInvoiceCreationOptions(){ Enabled = true},
+ ClientReferenceId = creatorId
};
var service = new SessionService();
@@ -39,4 +47,26 @@ 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/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 b944419..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);
}
@@ -16,8 +18,13 @@ 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);
+ }
+
+ 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/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"
+ ]
+ }
+}
+
diff --git a/src/Web/appsettings.Development.json b/src/Web/appsettings.Development.json
deleted file mode 100644
index 84308c9..0000000
--- a/src/Web/appsettings.Development.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft": "Warning",
- "Microsoft.AspNetCore.SpaProxy": "Information",
- "Microsoft.Hosting.Lifetime": "Information"
- }
- }
-}
diff --git a/src/Web/wwwroot/api/specification.json b/src/Web/wwwroot/api/specification.json
index fcb4d9f..b5a4c09 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": {
@@ -638,6 +715,9 @@
"lastName": {
"type": "string"
},
+ "userName": {
+ "type": "string"
+ },
"userTransactions": {
"type": "array",
"items": {
@@ -736,12 +816,103 @@
"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"
+ }
+ }
+ },
+ "MyLastReceiptDto": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "receiptUrl": {
+ "type": "string"
}
}
},
@@ -785,6 +956,21 @@
}
}
},
+ "MinimalUserDto": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "firstName": {
+ "type": "string"
+ },
+ "lastName": {
+ "type": "string"
+ },
+ "userName": {
+ "type": "string"
+ }
+ }
+ },
"HttpValidationProblemDetails": {
"allOf": [
{