Skip to content

Commit

Permalink
Add PaymentType to Purchase entity
Browse files Browse the repository at this point in the history
Purchase Type is currently used to distinguish between Free Purchases and MobilePay. However could be extended in the future.
PaymentType is nullable as we old purchases has no assumed payment type.
  • Loading branch information
jonasanker committed Dec 28, 2022
1 parent 7165991 commit cbcffde
Show file tree
Hide file tree
Showing 8 changed files with 816 additions and 8 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;

namespace CoffeeCard.Library.Migrations
{
public partial class PaymentType : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "PaymentType",
schema: "dbo",
table: "Purchases",
type: "nvarchar(max)",
nullable: true);
}

protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "PaymentType",
schema: "dbo",
table: "Purchases");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,9 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property<string>("OrderId")
.HasColumnType("nvarchar(450)");

b.Property<string>("PaymentType")
.HasColumnType("nvarchar(max)");

b.Property<int>("Price")
.HasColumnType("int");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.Property(p => p.Status)
.HasConversion<string>();

modelBuilder.Entity<Purchase>()
.Property(p => p.PaymentType)
.HasConversion<string>();

if (_environmentSettings.EnvironmentType == EnvironmentType.LocalDevelopment)
{
SeedData(modelBuilder);
Expand Down
29 changes: 25 additions & 4 deletions coffeecard/CoffeeCard.Library/Services/v2/PurchaseService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,9 @@ private static void CheckUserIsAllowedToPurchaseProduct(User user, InitiatePurch
OrderId = paymentDetails.OrderId,
TransactionId = transactionId,
PurchasedBy = user,
Status = purchaseStatus
// FIXME State management, PaymentType
Status = purchaseStatus,
PaymentType = purchaseRequest.PaymentType
// FIXME State management
};

return (purchase, paymentDetails);
Expand All @@ -152,7 +153,26 @@ public async Task<SinglePurchaseResponse> GetPurchase(int purchaseId, User user)
$"No purchase was found by Purchase Id: {purchaseId} and User Id: {user.Id}");
}

var paymentDetails = await _mobilePayPaymentsService.GetPayment(Guid.Parse(purchase.TransactionId));
PaymentDetails paymentDetails;
if (purchase.PaymentType == null)
{
paymentDetails = await _mobilePayPaymentsService.GetPayment(Guid.Parse(purchase.TransactionId));
}
else
{
switch (purchase.PaymentType)
{
case PaymentType.MobilePay:
paymentDetails = await _mobilePayPaymentsService.GetPayment(Guid.Parse(purchase.TransactionId));
break;
case PaymentType.FreePurchase:
paymentDetails = new FreePurchasePaymentDetails(purchase.OrderId);
break;
default:
Log.Error("Payment Type {PaymentType} is not handled in PurchaseService", purchase.PaymentType);
throw new ArgumentException($"Payment Type '{purchase.PaymentType}' is not handled");
}
}

return new SinglePurchaseResponse
{
Expand All @@ -176,7 +196,8 @@ public async Task<IEnumerable<SimplePurchaseResponse>> GetPurchases(User user)
ProductId = p.ProductId,
ProductName = p.ProductName,
TotalAmount = p.Price,
PurchaseStatus = p.Status
PurchaseStatus = p.Status,
PaymentType = p.PaymentType
})
.ToListAsync();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json;

namespace CoffeeCard.Models.DataTransferObjects.v2.Purchase
{
Expand All @@ -13,7 +14,8 @@ namespace CoffeeCard.Models.DataTransferObjects.v2.Purchase
/// "productId": 1,
/// "productName": "Coffee",
/// "totalAmount": 300,
/// "purchaseStatus": "Completed"
/// "purchaseStatus": "Completed",
/// "paymentType": "MobilePay"
/// }
/// </example>
public class SimplePurchaseResponse
Expand Down Expand Up @@ -65,5 +67,14 @@ public class SimplePurchaseResponse
/// <example>Completed</example>
[Required]
public PurchaseStatus? PurchaseStatus { get; set; }

/// <summary>
/// Payment Type
/// </summary>
/// <value>Payment Type</value>
/// <example>MobilePay</example>
[Required]
[JsonProperty(Required = Required.AllowNull)]
public PaymentType? PaymentType { get; set; }
}
}
10 changes: 8 additions & 2 deletions coffeecard/CoffeeCard.Models/Entities/Purchase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,6 @@ public Purchase()
/// </summary>
/// <value>Purchase Completed</value>
/// <example>true</example>

// FIXME More detailed state management?
public bool Completed { get; set; }

/// <summary>
Expand All @@ -90,6 +88,14 @@ public Purchase()
/// <value>Status</value>
/// <example>Completed</example>
public PurchaseStatus? Status { get; set; }

// PaymentType is nullable for migration purposes
/// <summary>
/// Payment Type
/// </summary>
/// <value>Payment Type</value>
/// <example>MobilePay</example>
public PaymentType? PaymentType { get; set; }

[ForeignKey("PurchasedBy_Id")]
public virtual User PurchasedBy { get; set; }
Expand Down
141 changes: 140 additions & 1 deletion coffeecard/CoffeeCard.Tests.Unit/Services/v2/PurchaseServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,14 +247,153 @@ public async Task InitiatePurchaseAddsTicketsToUserWhenFree(Product product)
};

// Act
var purchaseResponse = await purchaseService.InitiatePurchase(request, user);
await purchaseService.InitiatePurchase(request, user);

// Assert
var userUpdated = await context.Users.FindAsync(user.Id);

Assert.Equal(1, userUpdated.Purchases.Count);
Assert.Equal(product.NumberOfTickets, userUpdated.Tickets.Count);
}

[Fact(DisplayName = "GetPurchase calls MobilePay Api when Payment Type is MobilePay")]
public async Task GetPurchaseCallsMobilePayApiWhenPaymentTypeIsMobilePay()
{
// Arrange
var builder = new DbContextOptionsBuilder<CoffeeCardContext>()
.UseInMemoryDatabase(nameof(GetPurchaseCallsMobilePayApiWhenPaymentTypeIsMobilePay));

var databaseSettings = new DatabaseSettings
{
SchemaName = "test"
};
var environmentSettings = new EnvironmentSettings()
{
EnvironmentType = EnvironmentType.Test
};

await using var context = new CoffeeCardContext(builder.Options, databaseSettings, environmentSettings);

var user = new User
{
Id = 1,
Name = "User1",
Email = "[email protected]",
Password = "password",
Salt = "salt",
DateCreated = new DateTime(year: 2020, month: 11, day: 11),
IsVerified = true,
PrivacyActivated = false,
UserGroup = UserGroup.Customer,
UserState = UserState.Active
};
await context.AddAsync(user);

var purchase = new Purchase()
{
Id = 1,
ProductName = "Coffee clip card",
ProductId = 1,
Price = 100,
NumberOfTickets = 10,
DateCreated = DateTime.UtcNow,
Completed = true,
OrderId = Guid.NewGuid().ToString(),
TransactionId = Guid.NewGuid().ToString(),
Status = PurchaseStatus.Completed,
PaymentType = PaymentType.MobilePay,
PurchasedBy = user
};
await context.AddAsync(purchase);
await context.SaveChangesAsync();

var mobilePayService = new Mock<IMobilePayPaymentsService>();
mobilePayService.Setup(mp => mp.GetPayment(Guid.Parse(purchase.TransactionId))).ReturnsAsync(
new MobilePayPaymentDetails(purchase.OrderId, "redirect", purchase.TransactionId, "complete"));

var mailService = new Mock<Library.Services.IEmailService>();
var productService = new ProductService(context);
var ticketService = new TicketService(context, new Mock<IStatisticService>().Object);

var purchaseService = new PurchaseService(context, mobilePayService.Object, ticketService,
mailService.Object, productService);

// Act
var result = await purchaseService.GetPurchase(purchase.Id, user);

// Assert
mobilePayService.Verify(mp => mp.GetPayment(Guid.Parse(purchase.TransactionId)), Times.Once);

Assert.Equal(purchase.PaymentType, result.PaymentDetails.PaymentType);
}

[Fact(DisplayName = "GetPurchase doesnt calls MobilePay Api when Payment Type is FreePurchase")]
public async Task GetPurchaseDoesntCallsMobilePayApiWhenPaymentTypeIsFreePurchase()
{
// Arrange
var builder = new DbContextOptionsBuilder<CoffeeCardContext>()
.UseInMemoryDatabase(nameof(GetPurchaseDoesntCallsMobilePayApiWhenPaymentTypeIsFreePurchase));

var databaseSettings = new DatabaseSettings
{
SchemaName = "test"
};
var environmentSettings = new EnvironmentSettings()
{
EnvironmentType = EnvironmentType.Test
};

await using var context = new CoffeeCardContext(builder.Options, databaseSettings, environmentSettings);

var user = new User
{
Id = 1,
Name = "User1",
Email = "[email protected]",
Password = "password",
Salt = "salt",
DateCreated = new DateTime(year: 2020, month: 11, day: 11),
IsVerified = true,
PrivacyActivated = false,
UserGroup = UserGroup.Customer,
UserState = UserState.Active
};
await context.AddAsync(user);

var purchase = new Purchase()
{
Id = 1,
ProductName = "Coffee clip card",
ProductId = 1,
Price = 100,
NumberOfTickets = 10,
DateCreated = DateTime.UtcNow,
Completed = true,
OrderId = Guid.NewGuid().ToString(),
TransactionId = Guid.NewGuid().ToString(),
Status = PurchaseStatus.Completed,
PaymentType = PaymentType.FreePurchase,
PurchasedBy = user
};
await context.AddAsync(purchase);
await context.SaveChangesAsync();

var mobilePayService = new Mock<IMobilePayPaymentsService>();
var mailService = new Mock<Library.Services.IEmailService>();
var productService = new ProductService(context);
var ticketService = new TicketService(context, new Mock<IStatisticService>().Object);

var purchaseService = new PurchaseService(context, mobilePayService.Object, ticketService,
mailService.Object, productService);

// Act
var result = await purchaseService.GetPurchase(purchase.Id, user);

// Assert
mobilePayService.Verify(mp => mp.GetPayment(Guid.Parse(purchase.TransactionId)), Times.Never);

Assert.Equal(purchase.PaymentType, result.PaymentDetails.PaymentType);
}

public static IEnumerable<object[]> ProductGenerator()
{
Expand Down

0 comments on commit cbcffde

Please sign in to comment.