Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<VersionPrefix>10.7.1</VersionPrefix>
<VersionPrefix>10.7.2</VersionPrefix>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<Title>Stripe Checkout 2.0</Title>
<Description>Stripe Checkout handler</Description>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Dynamicweb.Ecommerce.CheckoutHandlers.StripeCheckout.Service;

namespace Dynamicweb.Ecommerce.CheckoutHandlers.StripeCheckout;
namespace Dynamicweb.Ecommerce.CheckoutHandlers.StripeCheckout.Helpers;

internal static class IdempotencyKeyHelper
{
Expand All @@ -9,14 +9,14 @@ public static string GetKey(ApiCommand command, string merchantName, string orde
//Add new keys if needed. Idempotency keys could be used for any operations, except GET requests.
return command switch
{
ApiCommand.CreatePaymentIntent => GetKey(),
ApiCommand.CreatePaymentMethod => GetKey() + "PM",
ApiCommand.CreateSetupIntent => GetKey() + "SI",
ApiCommand.CreateSession => GetKey() + "SE",
ApiCommand.CreatePaymentIntent => GetKey("PI"),
ApiCommand.CreatePaymentMethod => GetKey("PM"),
ApiCommand.CreateSetupIntent => GetKey("SI"),
ApiCommand.CreateSession => GetKey("SE"),
_ => string.Empty
};

string GetKey() => GetBaseKey(merchantName, orderId);
string GetKey(string prefix) => GetBaseKey(merchantName, orderId) + $"_{prefix}";
}

private static string GetBaseKey(string merchantName, string orderId) => $"{merchantName}:{orderId}";
Expand Down
100 changes: 100 additions & 0 deletions src/Helpers/LogHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using Dynamicweb.Logging;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;

namespace Dynamicweb.Ecommerce.CheckoutHandlers.StripeCheckout.Helpers;

internal static class LogHelper
{
public static void LogBuilderInitialization(StringBuilder logBuilder)
{
logBuilder.AppendLine();
logBuilder.AppendLine("--- STRIPE CHECKOUT INTERACTION LOG ---");
}

public static void LogRequest(StringBuilder logBuilder, string link)
{
logBuilder.AppendLine();
logBuilder.AppendLine("--- REQUEST ---");
logBuilder.AppendLine();

logBuilder.AppendLine($"URL: {link}");
}

public static void LogRequestParameters(StringBuilder logBuilder, Dictionary<string, string> parameters)
{
logBuilder.AppendLine();
logBuilder.AppendLine("--- REQUEST DATA ---");
logBuilder.AppendLine();

foreach ((string key, string value) in parameters)
{
string loggedValue = value;
if (key.Contains("secret", StringComparison.OrdinalIgnoreCase))
loggedValue = "***HIDDEN***";

logBuilder.AppendLine($"{key} = '{loggedValue}'");
}
}

public static void LogResponse(StringBuilder logBuilder, HttpResponseMessage response, string responseText)
{
logBuilder.AppendLine();
logBuilder.AppendLine("--- RESPONSE ---");
logBuilder.AppendLine();

logBuilder.AppendLine($"HttpStatusCode: {response.StatusCode} ({response.ReasonPhrase})");
logBuilder.AppendLine();

logBuilder.AppendLine($"Response Text: {responseText}");
}

public static void LogHttpRequestException(StringBuilder logBuilder, HttpRequestException requestException)
{
logBuilder.AppendLine();
logBuilder.AppendLine($"--- EXCEPTION CAUGHT (HttpRequestException) ---");
logBuilder.AppendLine();
logBuilder.AppendLine($"Message: {requestException.Message}");
if (requestException.StatusCode.HasValue)
logBuilder.AppendLine($"StatusCode: {requestException.StatusCode}");

logBuilder.AppendLine($"Stack Trace: {requestException.StackTrace}");
}

public static void LogErrorMessage(StringBuilder logBuilder, string errorMessage)
{
logBuilder.AppendLine();
logBuilder.AppendLine("--- HTTP ERROR ---");
logBuilder.AppendLine();

logBuilder.AppendLine(errorMessage);
}

public static void LogArgumentException(StringBuilder logBuilder, ArgumentException argumentException)
{
logBuilder.AppendLine();
logBuilder.AppendLine($"--- EXCEPTION CAUGHT (ArgumentException) ---");
logBuilder.AppendLine();
logBuilder.AppendLine($"Message: {argumentException.Message}");
}

public static void LogUnhandledException(StringBuilder logBuilder, Exception exception)
{
logBuilder.AppendLine();
logBuilder.AppendLine($"--- UNEXPECTED EXCEPTION CAUGHT ({exception.GetType().Name}) ---");
logBuilder.AppendLine();
logBuilder.AppendLine($"Message: {exception.Message}");
logBuilder.AppendLine($"Stack Trace: {exception.StackTrace}");
}

public static void FinalizeLog(StringBuilder logBuilder)
{
logBuilder.AppendLine();
logBuilder.AppendLine("--- END OF INTERACTION ---");
string message = logBuilder.ToString();

LogManager.Current.GetLogger(LogCategory.Provider, typeof(StripeCheckout).FullName).Log(message);
}
}
5 changes: 2 additions & 3 deletions src/RequestHelper.cs → src/Helpers/RequestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace Dynamicweb.Ecommerce.CheckoutHandlers.StripeCheckout;
namespace Dynamicweb.Ecommerce.CheckoutHandlers.StripeCheckout.Helpers;

internal static class RequestHelper
{
{
public static bool IsAjaxRequest()
{
return "application/json".Equals(Context.Current.Request.Headers["Content-Type"], StringComparison.OrdinalIgnoreCase);
Expand Down
3 changes: 3 additions & 0 deletions src/Models/Customer/Customer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ internal sealed class Customer
[DataMember(Name = "id")]
public string Id { get; set; }

[DataMember(Name = "deleted")]
public bool Deleted { get; set; }

[DataMember(Name = "description")]
public string Description { get; set; }

Expand Down
169 changes: 96 additions & 73 deletions src/Service/StripeRequest.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using Dynamicweb.Core;
using Dynamicweb.Ecommerce.CheckoutHandlers.StripeCheckout.Helpers;
using Dynamicweb.Ecommerce.CheckoutHandlers.StripeCheckout.Models.Error;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace Dynamicweb.Ecommerce.CheckoutHandlers.StripeCheckout.Service;
Expand All @@ -18,89 +20,110 @@ internal static class StripeRequest

public static string SendRequest(string secretKey, CommandConfiguration configuration)
{
using (var messageHandler = GetMessageHandler())
{
using (var client = new HttpClient(messageHandler))
{
client.BaseAddress = new Uri(BaseAddress);
client.Timeout = new TimeSpan(0, 0, 0, 90);
if (!string.IsNullOrWhiteSpace(configuration.IdempotencyKey))
client.DefaultRequestHeaders.Add("Idempotency-Key", configuration.IdempotencyKey);

string apiCommand = GetCommandLink(configuration.CommandType, configuration.OperatorId, configuration.OperatorSecondId);
Task<HttpResponseMessage> requestTask = configuration.CommandType switch
{
//POST
ApiCommand.CreateCustomer or
ApiCommand.UpdateCustomer or
ApiCommand.CreatePaymentIntent or
ApiCommand.CapturePaymentIntent or
ApiCommand.CancelPaymentIntent or
ApiCommand.CreateSetupIntent or
ApiCommand.ConfirmSetupIntent or
ApiCommand.CreatePaymentMethod or
ApiCommand.AttachPaymentMethod or
ApiCommand.DetachPaymentMethod or
ApiCommand.CreateRefund or
ApiCommand.CreateSession => client.PostAsync(apiCommand, new FormUrlEncodedContent(GetParameters(configuration.Parameters))),
//GET
ApiCommand.GetAllPaymentIntents or
ApiCommand.GetPaymentIntent or
ApiCommand.GetSetupIntent or
ApiCommand.GetPaymentMethod or
ApiCommand.GetCustomerPaymentMethod or
ApiCommand.GetCustomer or
ApiCommand.GetSession => client.GetAsync(apiCommand),
//DELETE
ApiCommand.DeleteCustomer => client.DeleteAsync(apiCommand),
_ => throw new NotSupportedException($"Unknown operation was used. The operation code: {configuration.CommandType}.")
};

try
{
using (HttpResponseMessage response = requestTask.GetAwaiter().GetResult())
{
string data = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();

if (!response.IsSuccessStatusCode)
{
string errorMessage = "Unhandled exception. Operation failed.";

var errorResponse = Converter.Deserialize<StripeErrorResponse>(data);
if (errorResponse.Error is not null)
errorMessage = StripeService.GetErrorMessage(errorResponse.Error);

throw new Exception(errorMessage);
}

return data;
}
}
catch (HttpRequestException requestException)
{
throw new Exception($"An error occurred during Stripe request. Error code: {requestException.StatusCode}");
}
}
}
var logBuilder = new StringBuilder();
LogHelper.LogBuilderInitialization(logBuilder);

using var messageHandler = GetMessageHandler(secretKey);
using var client = new HttpClient(messageHandler);

client.BaseAddress = new Uri(BaseAddress);
client.Timeout = new TimeSpan(0, 0, 0, 90);
if (!string.IsNullOrWhiteSpace(configuration.IdempotencyKey))
client.DefaultRequestHeaders.Add("Idempotency-Key", configuration.IdempotencyKey);

string apiCommand = GetCommandLink(configuration.CommandType, configuration.OperatorId, configuration.OperatorSecondId);
LogHelper.LogRequest(logBuilder, apiCommand);

HttpMessageHandler GetMessageHandler() => new HttpClientHandler()
Task<HttpResponseMessage> requestTask = configuration.CommandType switch
{
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip,
PreAuthenticate = true,
Credentials = new NetworkCredential(secretKey, string.Empty)
//POST
ApiCommand.CreateCustomer or
ApiCommand.UpdateCustomer or
ApiCommand.CreatePaymentIntent or
ApiCommand.CapturePaymentIntent or
ApiCommand.CancelPaymentIntent or
ApiCommand.CreateSetupIntent or
ApiCommand.ConfirmSetupIntent or
ApiCommand.CreatePaymentMethod or
ApiCommand.AttachPaymentMethod or
ApiCommand.DetachPaymentMethod or
ApiCommand.CreateRefund or
ApiCommand.CreateSession => client.PostAsync(apiCommand, GetFormUrlEncodedContent(configuration, logBuilder)),
//GET
ApiCommand.GetAllPaymentIntents or
ApiCommand.GetPaymentIntent or
ApiCommand.GetSetupIntent or
ApiCommand.GetPaymentMethod or
ApiCommand.GetCustomerPaymentMethod or
ApiCommand.GetCustomer or
ApiCommand.GetSession => client.GetAsync(apiCommand),
//DELETE
ApiCommand.DeleteCustomer => client.DeleteAsync(apiCommand),
_ => throw new NotSupportedException($"Unknown operation was used. The operation code: {configuration.CommandType}.")
};

Dictionary<string, string> GetParameters(Dictionary<string, object> parameters)
try
{
if (parameters?.Count is null or 0)
return new();
using HttpResponseMessage response = requestTask.GetAwaiter().GetResult();

Dictionary<string, string> convertedParameters = parameters.ToDictionary(x => x.Key, y => parameters[y.Key]?.ToString() ?? string.Empty);
string responseText = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
LogHelper.LogResponse(logBuilder, response, responseText);

if (!response.IsSuccessStatusCode)
{
string errorMessage = $"Unhandled exception. Operation failed: {response.ReasonPhrase}. Response text: {responseText}";

var errorResponse = Converter.Deserialize<StripeErrorResponse>(responseText);
if (errorResponse.Error is not null)
errorMessage = StripeService.GetErrorMessage(errorResponse.Error);

LogHelper.LogErrorMessage(logBuilder, errorMessage);
throw new Exception(errorMessage);
}

return responseText;

return convertedParameters.Where(pair => !string.IsNullOrWhiteSpace(pair.Key) && !string.IsNullOrWhiteSpace(pair.Value)).ToDictionary(StringComparer.OrdinalIgnoreCase);
}
catch (HttpRequestException requestException)
{
LogHelper.LogHttpRequestException(logBuilder, requestException);
throw;
}
catch (Exception ex)
{
LogHelper.LogUnhandledException(logBuilder, ex);
throw;
}
finally
{
LogHelper.FinalizeLog(logBuilder);
}
}

private static FormUrlEncodedContent GetFormUrlEncodedContent(CommandConfiguration configuration, StringBuilder logBuilder)
{
if (configuration.Parameters?.Count is null or 0)
return null;

Dictionary<string, string> parameters = configuration.Parameters
.ToDictionary(
p => p.Key,
p => configuration.Parameters[p.Key]?.ToString() ?? string.Empty,
StringComparer.OrdinalIgnoreCase
);

LogHelper.LogRequestParameters(logBuilder, parameters);

return new FormUrlEncodedContent(parameters);
}

private static HttpMessageHandler GetMessageHandler(string secretKey) => new HttpClientHandler()
{
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip,
PreAuthenticate = true,
Credentials = new NetworkCredential(secretKey, string.Empty)
};

private static string GetCommandLink(ApiCommand command, string operatorId, string operatorSecondId)
{
return command switch
Expand Down
Loading