Skip to content

Commit 56717ee

Browse files
committed
Add C# billing service
1 parent cce1aa4 commit 56717ee

File tree

6 files changed

+190
-0
lines changed

6 files changed

+190
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0</TargetFramework>
5+
<IsPackable>false</IsPackable>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
10+
<PackageReference Include="xunit" Version="2.4.2" />
11+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="Billing.csproj" />
16+
</ItemGroup>
17+
18+
</Project>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
<PropertyGroup>
3+
<TargetFramework>net6.0</TargetFramework>
4+
</PropertyGroup>
5+
6+
<ItemGroup>
7+
<!-- existing packages… -->
8+
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
9+
</ItemGroup>
10+
<ItemGroup>
11+
<Compile Remove="Tests\**\*.cs" />
12+
</ItemGroup>
13+
</Project>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using System.Text.Json;
3+
using System.Text.Json.Serialization;
4+
using System.Threading.Tasks;
5+
using System;
6+
7+
public class ChargeRequest
8+
{
9+
[JsonPropertyName("username")]
10+
public string Username { get; set; }
11+
12+
[JsonPropertyName("productId")]
13+
public string ProductId { get; set; }
14+
15+
[JsonPropertyName("quantity")]
16+
public int Quantity { get; set; }
17+
}
18+
19+
[ApiController]
20+
[Route("billing")]
21+
public class BillingController : ControllerBase
22+
{
23+
private readonly string EXPECTED_SECRET = Environment.GetEnvironmentVariable("BILLING_SECRET");
24+
25+
[HttpPost("charge")]
26+
public async Task<IActionResult> Charge([FromBody] ChargeRequest request)
27+
{
28+
var receivedSecret = Request.Headers["X-Service-Secret"].ToString();
29+
if (string.IsNullOrEmpty(receivedSecret) || receivedSecret != EXPECTED_SECRET)
30+
return Unauthorized("Missing or invalid service secret");
31+
32+
if (string.IsNullOrEmpty(request.Username) || string.IsNullOrEmpty(request.ProductId) || request.Quantity <= 0)
33+
return BadRequest("Invalid request payload");
34+
35+
var responsePayload = new
36+
{
37+
status = "charged",
38+
user = request.Username,
39+
product = request.ProductId,
40+
quantity = request.Quantity
41+
};
42+
43+
return Ok(JsonSerializer.Serialize(responsePayload));
44+
}
45+
}

services/billing-csharp/Dockerfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
2+
WORKDIR /src
3+
COPY . .
4+
RUN dotnet restore Billing.csproj && dotnet restore Billing.Tests.csproj
5+
RUN dotnet test Billing.Tests.csproj --no-restore --logger "trx;LogFileName=test_results.trx"
6+
RUN dotnet publish Billing.csproj -c Release -o /app/publish
7+
8+
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS runtime
9+
WORKDIR /app
10+
COPY --from=build /app/publish .
11+
ENTRYPOINT ["dotnet", "Billing.dll"]

services/billing-csharp/Program.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Microsoft.AspNetCore.Builder;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Microsoft.OpenApi.Models;
4+
5+
6+
var builder = WebApplication.CreateBuilder(args);
7+
8+
builder.Services.AddControllers();
9+
builder.Services.AddSwaggerGen(c =>
10+
{
11+
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Billing API", Version = "v1" });
12+
});
13+
14+
var app = builder.Build();
15+
16+
app.UseSwagger();
17+
app.UseSwaggerUI(c =>
18+
{
19+
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Billing API v1"));
20+
});
21+
22+
app.MapControllers();
23+
app.Run();
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Microsoft.AspNetCore.Http;
3+
using Xunit;
4+
using System.Text.Json;
5+
using System.Threading.Tasks;
6+
7+
public class BillingControllerTests
8+
{
9+
[Fact]
10+
public async Task ReturnsOkWithValidPayload()
11+
{
12+
System.Environment.SetEnvironmentVariable("BILLING_SECRET", "topsecretvalue");
13+
14+
var controller = new BillingController();
15+
controller.ControllerContext = new ControllerContext
16+
{
17+
HttpContext = new DefaultHttpContext()
18+
};
19+
controller.Request.Headers["X-Service-Secret"] = "topsecretvalue";
20+
21+
var result = await controller.Charge(new ChargeRequest
22+
{
23+
Username = "alice",
24+
ProductId = "widget-123",
25+
Quantity = 1
26+
});
27+
28+
var okResult = Assert.IsType<OkObjectResult>(result);
29+
var json = okResult.Value as string;
30+
Assert.NotNull(json);
31+
32+
var doc = JsonSerializer.Deserialize<JsonElement>(json);
33+
Assert.Equal("charged", doc.GetProperty("status").GetString());
34+
Assert.Equal("alice", doc.GetProperty("user").GetString());
35+
Assert.Equal("widget-123", doc.GetProperty("product").GetString());
36+
Assert.Equal(1, doc.GetProperty("quantity").GetInt32());
37+
}
38+
39+
[Fact]
40+
public async Task ReturnsUnauthorizedWithoutSecret()
41+
{
42+
var controller = new BillingController();
43+
controller.ControllerContext = new ControllerContext
44+
{
45+
HttpContext = new DefaultHttpContext()
46+
};
47+
controller.Request.Headers["X-Service-Secret"] = "wrongsecret";
48+
49+
var result = await controller.Charge(new ChargeRequest
50+
{
51+
Username = "bob",
52+
ProductId = "widget-123",
53+
Quantity = 1
54+
});
55+
56+
Assert.IsType<UnauthorizedObjectResult>(result);
57+
}
58+
59+
[Fact]
60+
public async Task ReturnsBadRequestWithMissingFields()
61+
{
62+
System.Environment.SetEnvironmentVariable("BILLING_SECRET", "topsecretvalue");
63+
64+
var controller = new BillingController();
65+
controller.ControllerContext = new ControllerContext
66+
{
67+
HttpContext = new DefaultHttpContext()
68+
};
69+
controller.Request.Headers["X-Service-Secret"] = "topsecretvalue";
70+
71+
var result = await controller.Charge(new ChargeRequest
72+
{
73+
Username = "",
74+
ProductId = "",
75+
Quantity = 0
76+
});
77+
78+
Assert.IsType<BadRequestObjectResult>(result);
79+
}
80+
}

0 commit comments

Comments
 (0)