Merged PR 51: #27 get last receipt, get minimalUser, get my user
#27 get last receipt, get minimalUser, get my user Related work items: #27
This commit is contained in:
@@ -6,6 +6,6 @@ namespace Hutopy.Application.Common.Interfaces;
|
|||||||
|
|
||||||
public interface IStripeService
|
public interface IStripeService
|
||||||
{
|
{
|
||||||
public Task<string> CreateCheckoutSession(int amount, string currency);
|
public Task<string> CreateCheckoutSession(int amount, string creatorId, string currency);
|
||||||
public Result ValidateTransaction(ConfirmStripeTransactionCommand request);
|
public Result ValidateTransaction(ConfirmStripeTransactionCommand request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,15 +56,15 @@ public class ConfirmStripeTransactionCommandHandler(
|
|||||||
if (stripeConfirmation.Succeeded)
|
if (stripeConfirmation.Succeeded)
|
||||||
{
|
{
|
||||||
lastTransaction.IsConfirmed = true;
|
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);
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public class CreateSessionCheckoutCommandHandler(
|
|||||||
{
|
{
|
||||||
public async Task<string> Handle(CreateSessionCheckoutCommand request, CancellationToken cancellationToken)
|
public async Task<string> 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
|
// ReSharper disable once PossibleLossOfFraction
|
||||||
decimal priceInDollars = (request.Amount / 100);
|
decimal priceInDollars = (request.Amount / 100);
|
||||||
|
|||||||
29
src/Application/Stripe/Queries/GetMyLastReceipt.cs
Normal file
29
src/Application/Stripe/Queries/GetMyLastReceipt.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using Hutopy.Application.Common.Interfaces;
|
||||||
|
|
||||||
|
namespace Hutopy.Application.Stripe.Queries;
|
||||||
|
|
||||||
|
public record GetMyLastReceiptQuery : IRequest<MyLastReceiptDto>
|
||||||
|
{
|
||||||
|
public string Email { get; set; } = string.Empty;
|
||||||
|
public string CreatorId { get; set; } = string.Empty;
|
||||||
|
};
|
||||||
|
|
||||||
|
public class GetMyLastReceiptQueryHandler(
|
||||||
|
IApplicationDbContext dbContext
|
||||||
|
)
|
||||||
|
: IRequestHandler<GetMyLastReceiptQuery, MyLastReceiptDto>
|
||||||
|
{
|
||||||
|
public async Task<MyLastReceiptDto> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/Application/Stripe/Queries/MyLastReceiptDto.cs
Normal file
6
src/Application/Stripe/Queries/MyLastReceiptDto.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Hutopy.Application.Stripe.Queries;
|
||||||
|
|
||||||
|
public class MyLastReceiptDto
|
||||||
|
{
|
||||||
|
public string ReceiptUrl { get; set; }
|
||||||
|
}
|
||||||
@@ -18,12 +18,11 @@ public class GetCurrentUserQueryHandler(
|
|||||||
var currentUserId = new Guid(identityUser?.Id ?? "");
|
var currentUserId = new Guid(identityUser?.Id ?? "");
|
||||||
|
|
||||||
var transactions = await context.UserTransactions
|
var transactions = await context.UserTransactions
|
||||||
.Where(x => x.Id == currentUserId)
|
.Where(x => x.ApplicationUserId == currentUserId.ToString())
|
||||||
.OrderBy(x => x.LastModified)
|
.OrderBy(x => x.LastModified)
|
||||||
.ProjectTo<UserTransactionDto>(mapper.ConfigurationProvider)
|
.ProjectTo<UserTransactionDto>(mapper.ConfigurationProvider)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
|
||||||
var user = new UserDto()
|
var user = new UserDto()
|
||||||
{
|
{
|
||||||
Id = currentUserId,
|
Id = currentUserId,
|
||||||
@@ -7,6 +7,7 @@ public class UserDto
|
|||||||
public required string FirstName { get; init; }
|
public required string FirstName { get; init; }
|
||||||
|
|
||||||
public required string LastName { get; init; }
|
public required string LastName { get; init; }
|
||||||
|
public string UserName { get; init; } = String.Empty;
|
||||||
|
|
||||||
public List<UserTransactionDto> UserTransactions { get; init; } = [];
|
public List<UserTransactionDto> UserTransactions { get; init; } = [];
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using Hutopy.Domain.Interfaces;
|
||||||
|
|
||||||
|
namespace Hutopy.Application.Users.Queries.GetMinimalUser;
|
||||||
|
|
||||||
|
public record GetMinimalUserQuery : IRequest<MinimalUserDto>
|
||||||
|
{
|
||||||
|
public string UserId { get; set; } = string.Empty;
|
||||||
|
};
|
||||||
|
|
||||||
|
public class GetMinimalUserQueryHandler(
|
||||||
|
IUserService userService
|
||||||
|
)
|
||||||
|
: IRequestHandler<GetMinimalUserQuery, MinimalUserDto>
|
||||||
|
{
|
||||||
|
public async Task<MinimalUserDto> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ public class UserTransaction : BaseAuditableEntity
|
|||||||
public string TipMessage { get; set; } = string.Empty;
|
public string TipMessage { get; set; } = string.Empty;
|
||||||
|
|
||||||
// Foreign key to ApplicationUser
|
// Foreign key to ApplicationUser
|
||||||
public string ApplicationUserId { get; set; } = string.Empty;
|
public required string ApplicationUserId { get; set; }
|
||||||
public bool IsConfirmed { get; set; }
|
public bool IsConfirmed { get; set; }
|
||||||
public string StripeEventId { get; set; } = string.Empty;
|
public string StripeEventId { get; set; } = string.Empty;
|
||||||
public string StripeChargeId { get; set; } = string.Empty;
|
public string StripeChargeId { get; set; } = string.Empty;
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
using Hutopy.Domain.Interfaces;
|
using System.Security.Claims;
|
||||||
|
using Hutopy.Domain.Interfaces;
|
||||||
using Hutopy.Domain.Models;
|
using Hutopy.Domain.Models;
|
||||||
using Hutopy.Infrastructure.Identity;
|
using Hutopy.Infrastructure.Identity;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
namespace Hutopy.Infrastructure.Services;
|
namespace Hutopy.Infrastructure.Services;
|
||||||
|
|
||||||
public class UserService(UserManager<ApplicationUser> userManager) : IUserService
|
public class UserService(UserManager<ApplicationUser> userManager, IHttpContextAccessor contextAccessor) : IUserService
|
||||||
{
|
{
|
||||||
public async Task CreateUserAsync(string email, string userName, string firstName, string lastName, string password)
|
public async Task CreateUserAsync(string email, string userName, string firstName, string lastName, string password)
|
||||||
{
|
{
|
||||||
@@ -38,7 +40,7 @@ public class UserService(UserManager<ApplicationUser> userManager) : IUserServic
|
|||||||
UserName = response.UserName,
|
UserName = response.UserName,
|
||||||
FirstName = response.FirstName,
|
FirstName = response.FirstName,
|
||||||
LastName = response.LastName,
|
LastName = response.LastName,
|
||||||
Email = response.Email
|
Email = response.Email,
|
||||||
};
|
};
|
||||||
|
|
||||||
return userModel;
|
return userModel;
|
||||||
@@ -47,13 +49,13 @@ public class UserService(UserManager<ApplicationUser> userManager) : IUserServic
|
|||||||
public async Task<UserModel?> GetCurrentUserAsync()
|
public async Task<UserModel?> GetCurrentUserAsync()
|
||||||
{
|
{
|
||||||
// todo: Get the id of the user doing the request.
|
// todo: Get the id of the user doing the request.
|
||||||
var userId = "";
|
var currentUserId = contextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||||
if (string.IsNullOrEmpty(userId))
|
if (string.IsNullOrEmpty(currentUserId))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await FindUserByIdAsync(userId);
|
return await FindUserByIdAsync(currentUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<UserModel?> FindUserByEmailAsync(string email)
|
public async Task<UserModel?> FindUserByEmailAsync(string email)
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ namespace Hutopy.Infrastructure.Stripe;
|
|||||||
|
|
||||||
public class StripeService : IStripeService
|
public class StripeService : IStripeService
|
||||||
{
|
{
|
||||||
const string EndpointSecret = "";
|
|
||||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
|
|
||||||
public StripeService(IHttpContextAccessor httpContextAccessor)
|
public StripeService(IHttpContextAccessor httpContextAccessor)
|
||||||
@@ -18,7 +17,7 @@ public class StripeService : IStripeService
|
|||||||
StripeConfiguration.ApiKey = "";
|
StripeConfiguration.ApiKey = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> CreateCheckoutSession(int amount, string currency = "cad")
|
public async Task<string> CreateCheckoutSession(int amount, string creatorId, string currency = "cad")
|
||||||
{
|
{
|
||||||
var options = new SessionCreateOptions
|
var options = new SessionCreateOptions
|
||||||
{
|
{
|
||||||
@@ -38,7 +37,9 @@ public class StripeService : IStripeService
|
|||||||
],
|
],
|
||||||
Mode = "payment",
|
Mode = "payment",
|
||||||
UiMode = "embedded",
|
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();
|
var service = new SessionService();
|
||||||
@@ -57,7 +58,6 @@ public class StripeService : IStripeService
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Result(false, new List<string>());
|
return new Result(false, new List<string>());
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (StripeException e)
|
catch (StripeException e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public class GetMyUser : EndpointGroupBase
|
|||||||
public override void Map(WebApplication app)
|
public override void Map(WebApplication app)
|
||||||
{
|
{
|
||||||
app.MapGroup(this)
|
app.MapGroup(this)
|
||||||
|
.RequireAuthorization()
|
||||||
.MapGet(GetCurrentUser);
|
.MapGet(GetCurrentUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Hutopy.Application.Stripe.Commands;
|
using Hutopy.Application.Stripe.Commands;
|
||||||
|
using Hutopy.Application.Stripe.Queries;
|
||||||
|
|
||||||
namespace Hutopy.Web.Endpoints;
|
namespace Hutopy.Web.Endpoints;
|
||||||
|
|
||||||
@@ -8,6 +9,7 @@ public class Stripe : EndpointGroupBase
|
|||||||
{
|
{
|
||||||
app.MapGroup(this)
|
app.MapGroup(this)
|
||||||
.MapPost(ConfirmTransaction, "/confirmTransaction")
|
.MapPost(ConfirmTransaction, "/confirmTransaction")
|
||||||
|
.MapGet(GetMyLastReceipt, "/getMyLastReceipt")
|
||||||
.MapPost(CreateSessionCheckout);
|
.MapPost(CreateSessionCheckout);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,4 +22,9 @@ public class Stripe : EndpointGroupBase
|
|||||||
{
|
{
|
||||||
return await sender.Send(command);
|
return await sender.Send(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task<MyLastReceiptDto> GetMyLastReceipt(ISender sender, [AsParameters] GetMyLastReceiptQuery query)
|
||||||
|
{
|
||||||
|
return await sender.Send(query);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Hutopy.Application.Users.Commands;
|
using Hutopy.Application.Users.Commands;
|
||||||
|
using Hutopy.Application.Users.Queries.GetMinimalUser;
|
||||||
using Hutopy.Domain.Interfaces;
|
using Hutopy.Domain.Interfaces;
|
||||||
using Hutopy.Infrastructure.Identity;
|
using Hutopy.Infrastructure.Identity;
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ public class Users : EndpointGroupBase
|
|||||||
{
|
{
|
||||||
app.MapGroup(this)
|
app.MapGroup(this)
|
||||||
.MapPost(CreateUser)
|
.MapPost(CreateUser)
|
||||||
|
.MapGet(GetMinimalUser)
|
||||||
.MapIdentityApi<ApplicationUser>();
|
.MapIdentityApi<ApplicationUser>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,4 +20,9 @@ public class Users : EndpointGroupBase
|
|||||||
await userService.CreateUserAsync(command.EmailAddress, command.UserName, command.FirstName, command.LastName, command.Password);
|
await userService.CreateUserAsync(command.EmailAddress, command.UserName, command.FirstName, command.LastName, command.Password);
|
||||||
return await sender.Send(command);
|
return await sender.Send(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task<MinimalUserDto> GetMinimalUser(ISender sender, [AsParameters] GetMinimalUserQuery query)
|
||||||
|
{
|
||||||
|
return await sender.Send(query);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"JWT": []
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/JoinUs": {
|
"/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": {
|
"/api/Stripe": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"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": {
|
"/api/Users/register": {
|
||||||
@@ -625,6 +702,9 @@
|
|||||||
"lastName": {
|
"lastName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"userName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"userTransactions": {
|
"userTransactions": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -814,6 +894,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"MyLastReceiptDto": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"receiptUrl": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"CreateSessionCheckoutCommand": {
|
"CreateSessionCheckoutCommand": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
@@ -854,6 +943,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"MinimalUserDto": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"firstName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"lastName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"userName": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"HttpValidationProblemDetails": {
|
"HttpValidationProblemDetails": {
|
||||||
"allOf": [
|
"allOf": [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user