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
1,871 changes: 811 additions & 1,060 deletions src/CyberSource.cs

Large diffs are not rendered by default.

23 changes: 20 additions & 3 deletions src/Dynamicweb.Ecommerce.CheckoutHandlers.CyberSource.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<VersionPrefix>10.0.3</VersionPrefix>
<VersionPrefix>10.11.2</VersionPrefix>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<Title>CyberSource</Title>
<Description>"Payment system, http://www.cybersource.com"</Description>
Expand All @@ -14,17 +14,34 @@
<Copyright>Copyright © 2023 Dynamicweb Software A/S</Copyright>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IncludeSymbols>true</IncludeSymbols>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PackageIcon>cybersource-logo.png</PackageIcon>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dynamicweb.Ecommerce" Version="10.0.34" />
<None Remove="Updates\checkouthandler_cancel.html" />
<None Remove="Updates\checkouthandler_error.html" />
<None Remove="Updates\Payment.html" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Updates\checkouthandler_cancel.html" />
<EmbeddedResource Include="Updates\checkouthandler_error.html" />
<EmbeddedResource Include="Updates\Payment.html" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Dynamicweb.Ecommerce" Version="10.11.2" />
<PackageReference Include="jose-jwt" Version="4.0.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<None Update="cybersource-logo.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
</Project>
52 changes: 52 additions & 0 deletions src/Helpers/Helper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using Dynamicweb.Core.Helpers;
using Dynamicweb.Ecommerce.Orders;
using System;
using System.IO;

namespace Dynamicweb.Ecommerce.CheckoutHandlers.CyberSource.Helpers;

internal static class Helper
{
public static string GetCertificateFilePath(string certificateFile)
{
if (string.IsNullOrWhiteSpace(certificateFile))
return string.Empty;

string path = FilePathHelper.GetAbsolutePath(certificateFile);
if (File.Exists(path))
return path;

return string.Empty;
}

public static string GetCustomerLastName(Order order, string customerName)
{
string lastName = order.CustomerSurname;
int delimeterPosition = customerName.IndexOf(' ');
if (string.IsNullOrWhiteSpace(lastName))
lastName = delimeterPosition > -1 ? customerName.Substring(delimeterPosition + 1) : customerName;

return lastName;
}

public static string GetCustomerFirstName(Order order, string customerName)
{
string firstName = order.CustomerFirstName;
int delimeterPosition = customerName.IndexOf(' ');
if (string.IsNullOrWhiteSpace(firstName))
firstName = delimeterPosition > -1 ? customerName.Substring(0, delimeterPosition) : customerName;

return firstName;
}

public static string GetTransactionAmount(Order order)
{
int decimals = order.Currency.Rounding is null || string.IsNullOrEmpty(order.Currency.Rounding.Id)
? 2
: order.Currency.Rounding.Decimals;

string amount = Math.Round(order.Price.Price, decimals).ToString("0.00", System.Globalization.CultureInfo.CreateSpecificCulture("en-US"));

return amount;
}
}
82 changes: 82 additions & 0 deletions src/Helpers/SecurityHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Security.Cryptography;

namespace Dynamicweb.Ecommerce.CheckoutHandlers.CyberSource.Helpers;

internal static class SecurityHelper
{
/// <summary>
///
/// </summary>
/// <param name="publicKey"></param>
/// <param name="secretKey"></param>
/// <param name="signature"></param>
/// <returns></returns>
/// <remarks>ToDo: Params property hasn't been defined in new Dynamicweb.Context class.</remarks>
public static bool ValidateResponseSignation(string publicKey, string secretKey, out string signature)
{
var parameters = Context.Current.Request.Params;
return ValidateResponseSignation(parameters, publicKey, secretKey, out signature);
}

/// <summary>
/// Validates transaction signature
/// </summary>
/// <param name="parameters">Transaction parameters</param>
/// <param name="publicKey">public key</param>
/// <param name="secretKey">secret key</param>
/// <param name="signature">signature</param>
/// <returns>Boolean result of validating transaction signature</returns>
public static bool ValidateResponseSignation(NameValueCollection parameters, string publicKey, string secretKey, out string signature)
{
var transactionSignature = parameters["signature"];
var signedFieldNames = parameters["signed_field_names"].Split(',');

var dataToSign = new List<string>();
foreach (string signedFieldName in signedFieldNames)
{
dataToSign.Add(signedFieldName + "=" + parameters[signedFieldName]);
}
signature = Sign(string.Join(",", dataToSign), secretKey).Replace("\n", string.Empty);

return transactionSignature.Equals(signature);
}

/// <summary>
/// Signs parameters with secret key
/// </summary>
/// <param name="parameters">set of key value pairs</param>
/// <param name="secretKey">key that is used for encription</param>
/// <returns>Encrypted string</returns>
public static string Sign(Dictionary<string, string> parameters, string secretKey)
{
return Sign(BuildSignation(parameters), secretKey);
}

private static string Sign(string data, string secretKey)
{
var encoding = new System.Text.UTF8Encoding();
var keyBytes = encoding.GetBytes(secretKey);

using (var hmacsha256 = new HMACSHA256(keyBytes))
{
var messageBytes = encoding.GetBytes(data);
return Convert.ToBase64String(hmacsha256.ComputeHash(messageBytes));
}
}

private static string BuildSignation(IDictionary<string, string> parameters)
{
var signedFieldNames = parameters["signed_field_names"].Split(',');
var dataToSign = new List<string>();

foreach (string signedFieldName in signedFieldNames)
{
dataToSign.Add(signedFieldName + "=" + parameters[signedFieldName]);
}

return string.Join(",", dataToSign);
}
}
18 changes: 18 additions & 0 deletions src/Models/Request/CaptureRequestData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Dynamicweb.Ecommerce.CheckoutHandlers.CyberSource.Models.Request.Common;
using System.Runtime.Serialization;

namespace Dynamicweb.Ecommerce.CheckoutHandlers.CyberSource.Models.Request;

/// <summary>
/// Request to capture the payment
/// See: https://developer.cybersource.com/api-reference-assets/index.html#payments_capture_capture-a-payment
/// </summary>
[DataContract]
internal sealed class CaptureRequestData
{
[DataMember(Name = "clientReferenceInformation")]
public ClientReferenceInformation ClientReferenceInformation { get; set; }

[DataMember(Name = "orderInformation")]
public OrderInformation OrderInformation { get; set; }
}
20 changes: 20 additions & 0 deletions src/Models/Request/Common/AmountDetails.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Runtime.Serialization;

namespace Dynamicweb.Ecommerce.CheckoutHandlers.CyberSource.Models.Request.Common;

[DataContract]
internal sealed class AmountDetails
{
/// <summary>
/// Grand total for the order. This value cannot be negative. You can include a decimal point (.), but no other special characters.
/// CyberSource truncates the amount to the correct number of decimal places.
/// </summary>
[DataMember(Name = "totalAmount")]
public string TotalAmount { get; set; }

/// <summary>
/// Currency used for the order. Use the three-character ISO Standard Currency Codes.
/// </summary>
[DataMember(Name = "currency")]
public string Currency { get; set; }
}
68 changes: 68 additions & 0 deletions src/Models/Request/Common/BillTo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.Runtime.Serialization;

namespace Dynamicweb.Ecommerce.CheckoutHandlers.CyberSource.Models.Request.Common;

[DataContract]
internal sealed class BillTo
{
/// <summary>
/// Customer’s first name. This name must be the same as the name on the card.
/// </summary>
[DataMember(Name = "firstName")]
public string FirstName { get; set; }

/// <summary>
/// Customer’s last name. This name must be the same as the name on the card.
/// </summary>
[DataMember(Name = "lastName")]
public string LastName { get; set; }

/// <summary>
/// Payment card billing street address as it appears on the credit card issuer’s records.
/// </summary>
[DataMember(Name = "address1")]
public string Address1 { get; set; }

/// <summary>
/// Payment card billing city.
/// </summary>
[DataMember(Name = "locality")]
public string Locality { get; set; }

/// <summary>
/// State or province of the billing address. Use the State, Province, and Territory Codes for the United States and Canada.
/// </summary>
[DataMember(Name = "administrativeArea")]
public string AdministrativeArea { get; set; }

/// <summary>
/// Postal code for the billing address. The postal code must consist of 5 to 9 digits.
/// When the billing country is the U.S., the 9-digit postal code must follow this format:
/// [5 digits][dash][4 digits]
/// Example 12345-6789
/// When the billing country is Canada, the 6-digit postal code must follow this format:
/// [alpha][numeric][alpha][space][numeric][alpha][numeric]
/// Example A1B 2C3
/// </summary>
[DataMember(Name = "postalCode")]
public string PostalCode { get; set; }

/// <summary>
/// Payment card billing country. Use the two-character ISO Standard Country Codes.
/// </summary>
[DataMember(Name = "country")]
public string Country { get; set; }

/// <summary>
/// Customer's email address, including the full domain name.
/// </summary>
[DataMember(Name = "email")]
public string Email { get; set; }

/// <summary>
/// Customer’s phone number.
/// It is recommended that you include the country code when the order is from outside the U.S.
/// </summary>
[DataMember(Name = "phoneNumber")]
public string PhoneNumber { get; set; }
}
13 changes: 13 additions & 0 deletions src/Models/Request/Common/ClientReferenceInformation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Runtime.Serialization;

namespace Dynamicweb.Ecommerce.CheckoutHandlers.CyberSource.Models.Request.Common;

[DataContract]
internal sealed class ClientReferenceInformation
{
/// <summary>
/// Merchant-generated order reference or tracking number. It is recommended that you send a unique value for each transaction so that you can perform meaningful searches for the transaction.
/// </summary>
[DataMember(Name = "code")]
public string Code { get; set; }
}
14 changes: 14 additions & 0 deletions src/Models/Request/Common/LegacyToken.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Runtime.Serialization;

namespace Dynamicweb.Ecommerce.CheckoutHandlers.CyberSource.Models.Request.Common;

[DataContract]
internal sealed class LegacyToken
{
/// <summary>
/// Unique identifier for the legacy Secure Storage token used in the transaction.
/// When you include this value in your request, many of the fields that are normally required for an authorization or credit become optional.
/// </summary>
[DataMember(Name = "id")]
public string Id { get; set; }
}
13 changes: 13 additions & 0 deletions src/Models/Request/Common/OrderInformation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Runtime.Serialization;

namespace Dynamicweb.Ecommerce.CheckoutHandlers.CyberSource.Models.Request.Common;

[DataContract]
internal sealed class OrderInformation
{
[DataMember(Name = "amountDetails")]
public AmountDetails AmountDetails { get; set; }

[DataMember(Name = "billTo", EmitDefaultValue = false)]
public BillTo BillTo { get; set; }
}
10 changes: 10 additions & 0 deletions src/Models/Request/Common/PaymentInformation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Runtime.Serialization;

namespace Dynamicweb.Ecommerce.CheckoutHandlers.CyberSource.Models.Request.Common;

[DataContract]
internal sealed class PaymentInformation
{
[DataMember(Name = "legacyToken")]
public LegacyToken LegacyToken { get; set; }
}
22 changes: 22 additions & 0 deletions src/Models/Request/Error/CybersourceError.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Collections.Generic;
using System.Runtime.Serialization;

namespace Dynamicweb.Ecommerce.CheckoutHandlers.CyberSource.Models.Request.Error;

internal sealed class CybersourceError
{
[DataMember(Name = "submitTimeUtc")]
public string SubmitTimeUtc { get; set; }

[DataMember(Name = "status")]
public string Status { get; set; }

[DataMember(Name = "reason")]
public string Reason { get; set; }

[DataMember(Name = "message")]
public string Message { get; set; }

[DataMember(Name = "details")]
public IEnumerable<ErrorDetail> Details { get; set; }
}
13 changes: 13 additions & 0 deletions src/Models/Request/Error/ErrorDetail.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Runtime.Serialization;

namespace Dynamicweb.Ecommerce.CheckoutHandlers.CyberSource.Models.Request.Error;

[DataContract]
internal sealed class ErrorDetail
{
[DataMember(Name = "field")]
public string Field { get; set; }

[DataMember(Name = "reason")]
public string Reason { get; set; }
}
Loading
Loading