Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
meofiscoding committed Nov 26, 2023
2 parents a0d3cfb + e7ddd2e commit bf72180
Show file tree
Hide file tree
Showing 19 changed files with 343 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"editor.inlineSuggest.showToolbar": "onHover"
"editor.inlineSuggest.showToolbar": "always"
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,26 @@
]
}
},
{
"DownstreamPathTemplate": "/api/create-portal-session",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 80
}
],
"UpstreamHttpMethod": [
"POST"
],
"UpstreamPathTemplate":"/payment/customerportal",
"AuthenticationOptions": {
"AuthenticationProviderKey": "IdentityApiKey",
"AllowedScopes": [
"payment"
]
}
},
{
"DownstreamPathTemplate": "/subscription/success",
"DownstreamScheme": "http",
Expand All @@ -94,6 +114,34 @@
],
"UpstreamPathTemplate": "/subscription/success"
},
{
"DownstreamPathTemplate": "/subscription/cancel",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 80
}
],
"UpstreamHttpMethod": [
"GET"
],
"UpstreamPathTemplate": "/subscription/cancel"
},
{
"DownstreamPathTemplate": "/stripe/webhook",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 80
}
],
"UpstreamHttpMethod": [
"POST"
],
"UpstreamPathTemplate": "/webhook"
},
{
"DownstreamPathTemplate": "/api/tag/{everything}",
"DownstreamScheme": "http",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ option csharp_namespace ="Identity.Grpc.Protos";
service PaymentProtoService {
// get user's payment status to update user's role
rpc UpdateUserMembership (PaymentRequest) returns (PaymentResponse);
// Update user email
rpc UpdateUserEmail (CustomerRequest) returns (CustomerResponse);
}

message CustomerRequest{
string userEmail = 1;
}

message CustomerResponse{
bool isUpdateEmailSuccess = 1;
string message = 2;
}

// Input:
Expand All @@ -19,6 +30,6 @@ message PaymentRequest {
// Payment status (field number 1)
message PaymentResponse {
bool isUpdateRoleSuccessSuccess = 1;
string Message = 2;
string message = 2;
string userId = 3;
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,29 @@ public override async Task<PaymentResponse> UpdateUserMembership(PaymentRequest
};
}

public override async Task<CustomerResponse> UpdateUserEmail(CustomerRequest request, ServerCallContext context)
{
// get user by email address
var user = await _userManager.FindByEmailAsync(request.UserEmail);
// var user = await _userManager.FindByIdAsync(request.UserId);
if (user == null)
{
return new CustomerResponse
{
IsUpdateEmailSuccess = false,
Message = "User not found",
};
}

user.Email = request.UserEmail;
user.UserName = request.UserEmail;
var result = await _userManager.UpdateAsync(user);
return new CustomerResponse
{
IsUpdateEmailSuccess = result.Succeeded,
Message = result.Succeeded ? "User updated" : $"User can not be updated due to {result.Errors}",
};
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,20 @@ namespace Payment.API.Controllers
public class SubcriptionController : ControllerBase
{
private readonly PaymentDBContext _context;
private readonly IConfiguration _config;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<SubcriptionController> _logger;
private readonly IStripeService _stripeService;
private readonly PaymentGrpcService _paymentGrpcService;

public SubcriptionController(PaymentDBContext context, IHttpContextAccessor httpContextAccessor, ILogger<SubcriptionController> logger, IStripeService stripeService, PaymentGrpcService paymentGrpcService)
public SubcriptionController(PaymentDBContext context, IConfiguration config, IHttpContextAccessor httpContextAccessor, ILogger<SubcriptionController> logger, IStripeService stripeService, PaymentGrpcService paymentGrpcService)
{
_context = context;
_httpContextAccessor = httpContextAccessor;
_logger = logger;
_stripeService = stripeService;
_paymentGrpcService = paymentGrpcService;
_config = config;
}

// GET: api/pricingPlans
Expand Down Expand Up @@ -82,24 +84,60 @@ public async Task<Results<Ok<string>, BadRequest>> PostSubcription([FromBody] in
}
}

[HttpPost("api/create-payment-intent")]
[Authorize(Roles = "User")]
public async Task<IActionResult> CreatePaymentIntent([FromBody] int amount)
//[HttpPost("api/create-payment-intent")]
//[Authorize(Roles = "User")]
//public async Task<IActionResult> CreatePaymentIntent([FromBody] int amount)
//{
// var options = new PaymentIntentCreateOptions
// {
// Amount = amount * 100,
// Currency = "usd",
// PaymentMethodTypes = new List<string>
// {
// "card",
// }
// };

// var service = new PaymentIntentService();
// var paymentIntent = await service.CreateAsync(options);

// return Ok(paymentIntent);
//}

// Customer Portal
[HttpPost]
[Route("api/create-portal-session")]
[Authorize(Roles = "Member")]
public async Task<IActionResult> CreatePortalSession([FromBody] string userEmail)
{
var options = new PaymentIntentCreateOptions
// Sets up a Billing Portal configuration
var options = new Stripe.BillingPortal.ConfigurationCreateOptions
{
Amount = amount * 100,
Currency = "usd",
PaymentMethodTypes = new List<string>
BusinessProfile = new Stripe.BillingPortal.ConfigurationBusinessProfileOptions
{
"card",
}
Headline = "Simple Netflix partners with Stripe for simplified billing.",
},
Features = new Stripe.BillingPortal.ConfigurationFeaturesOptions
{
InvoiceHistory = new Stripe.BillingPortal.ConfigurationFeaturesInvoiceHistoryOptions
{
Enabled = true,
},
},
};
var service = new Stripe.BillingPortal.ConfigurationService();
await service.CreateAsync(options);

var service = new PaymentIntentService();
var paymentIntent = await service.CreateAsync(options);
var customer = await _stripeService.GetCustomerByEmail(userEmail);
var sessionServiceOptions = new Stripe.BillingPortal.SessionCreateOptions
{
Customer = customer.Id,
ReturnUrl = $"{_config["ClientUrl"]}/account",
};
var sessionService = new Stripe.BillingPortal.SessionService();
var session = await sessionService.CreateAsync(sessionServiceOptions);

return Ok(paymentIntent);
return Ok(session);
}

// POST: subscription/success
Expand Down Expand Up @@ -139,7 +177,6 @@ public async Task<Results<RedirectHttpResult, BadRequest>> CheckoutSuccess([From
{
try
{
// Insert here failure data in data base
var service = new SessionService();
SessionGetOptions options = new SessionGetOptions
{
Expand All @@ -162,10 +199,11 @@ public async Task<Results<RedirectHttpResult, BadRequest>> CheckoutSuccess([From
UserId = response.UserId,
};

_context.UserPayments.Add(userPayment);
await _context.SaveChangesAsync();
// TODO: Uncomment when need to serve explicit service based on user subscription
// _context.UserPayments.Add(userPayment);
// await _context.SaveChangesAsync();
// return a redirect to the front end success page
return TypedResults.Redirect("http://localhost:4200/subscription/success");
return TypedResults.Redirect($"{_config["ClientUrl"]}/subscription/success");
}
catch (Exception ex)
{
Expand All @@ -174,23 +212,90 @@ public async Task<Results<RedirectHttpResult, BadRequest>> CheckoutSuccess([From
}
}

/// <summary>
/// this API is going to be hit when order is a failure
/// </summary>
/// <returns>A redirect to the front end success page</returns>
// [HttpGet("subscription/canceled")]
// public async Task<Results<RedirectHttpResult, BadRequest>> CheckoutCanceled([FromQuery] string sessionId)
// {
// try
// {
// // Insert here failure data in data base

// }
// catch (Exception ex)
// {
// _logger.LogError("error into order Controller on route /canceled " + ex.Message);
// return TypedResults.BadRequest();
// }
// }
[Route("stripe/webhook")]
[HttpPost]
public async Task<Results<RedirectHttpResult, BadRequest>> StripeWebHook()
{
// read webhook secret from configuration
string endpointSecret = _config["Stripe:WebhookSecret"]
?? throw new Exception("Stripe:WebhookSecret not found");
var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
try
{
var stripeEvent = EventUtility.ConstructEvent(json,
Request.Headers["Stripe-Signature"], endpointSecret);

// Handle the event
if (stripeEvent.Type == Events.CustomerSubscriptionUpdated)
{
// If cancel_at_period_end is true, the subscription is canceled at the end of its billing period.
if (stripeEvent.Data.Object is Subscription subscription)
{
if (subscription.CancelAtPeriodEnd == true)
{
// Communicate with IdentityGrpcService to update user membership
var response = await _paymentGrpcService.UpdateUserMembership(subscription.Customer.Email, false);
// redirect to the front end cancel page
return TypedResults.Redirect($"{_config["ClientUrl"]}/subscription/cancel");
}
}
}

// If user update email information
if (stripeEvent.Type == Events.CustomerUpdated)
{
if (stripeEvent.Data.Object is Customer customer)
{
// Communicate with IdentityGrpcService to update user membership
var response = await _paymentGrpcService.UpdateUserEmail(customer.Email);
}
}
else
{
Console.WriteLine("Unhandled event type: {0}", stripeEvent.Type);
}
}
catch (Exception ex)
{
_logger.LogError("error into order Controller on route /webhook " + ex.Message);
}
return TypedResults.BadRequest();
}

[HttpGet("subscription/cancel")]
public async Task<Results<RedirectHttpResult, BadRequest>> CheckoutCanceled([FromQuery] string sessionId)
{
try
{
var service = new SessionService();
SessionGetOptions options = new SessionGetOptions
{
Expand = new List<string> { "line_items" }
};
var sessionInfo = service.Get(sessionId, options);
var priceId = sessionInfo.LineItems.Data.FirstOrDefault()?.Price.Id
?? throw new Exception("PriceId not found");

var subcription = _context.Subcriptions.FirstOrDefault(x => x.StripePriceId == priceId)
?? throw new Exception("Subscription not found");

var userEmail = sessionInfo.CustomerEmail;

// Communicate with IdentityGrpcService to update user membership
var response = await _paymentGrpcService.UpdateUserMembership(userEmail, false);
var userPayment = new UserPayment()
{
Subcription = subcription,
UserId = response.UserId,
};

return TypedResults.Redirect($"{_config["ClientUrl"]}/payment/planform");
}
catch (Exception ex)
{
_logger.LogError("error into order Controller on route /canceled " + ex.Message);
return TypedResults.BadRequest();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace Payment.API.Entity
public class Device
{
public int Id { get; set; }

public string Name { get; set; } = string.Empty;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace Payment.API.Entity
public class PlanType
{
public int Id { get; set; }

public string Name { get; set; } = string.Empty;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace Payment.API.Entity
public class Quality
{
public int Id { get; set; }

public string Name { get; set; } = string.Empty;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@ namespace Payment.API.Entity
public class Subcription
{
public int Id { get; set; }

public string StripeProductId { get; set; } = string.Empty;

public string StripePriceId { get; set; } = string.Empty;

public PlanTypeEnum Plan { get; set; } = new();

public int Price { get; set; }

public QualityEnum VideoQuality { get; set; } = new();

public string Resolution { get; set; } = string.Empty;

public List<Device> Devices { get; set; } = new();
}
}
Loading

0 comments on commit bf72180

Please sign in to comment.