summaryrefslogtreecommitdiffstats
path: root/src/IOL.VippsEcommerce
diff options
context:
space:
mode:
authorivarlovlie <git@ivarlovlie.no>2021-03-31 18:00:22 +0200
committerivarlovlie <git@ivarlovlie.no>2021-03-31 18:00:22 +0200
commite6ca7a484d9cc213fb00c34ebf7cb55ace892c04 (patch)
treee885d823c224d55503286b1dab207fcc7c5c68d1 /src/IOL.VippsEcommerce
downloaddotnet-vipps-ecommerce-e6ca7a484d9cc213fb00c34ebf7cb55ace892c04.tar.xz
dotnet-vipps-ecommerce-e6ca7a484d9cc213fb00c34ebf7cb55ace892c04.zip
Initial commit
Diffstat (limited to 'src/IOL.VippsEcommerce')
-rw-r--r--src/IOL.VippsEcommerce/Helpers.cs86
-rw-r--r--src/IOL.VippsEcommerce/IOL.VippsEcommerce.csproj13
-rw-r--r--src/IOL.VippsEcommerce/IVippsEcommerceService.cs49
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/EErrorGroupEnum.cs47
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/EStatusEnum.cs25
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/ETransactionStatus.cs22
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/TAdditionalTransactionData.cs42
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/TCustomerInfo.cs14
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/TMerchantInfo.cs65
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/TMerchantInfoPayment.cs15
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/TShippingDetails.cs32
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/TTransaction.cs21
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/TTransactionInfo.cs15
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/TTransactionInfoInitiate.cs57
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/TTransactionSummary.cs42
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/VippsAuthorizationTokenResponse.cs58
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/VippsErrorResponse.cs39
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/VippsForceApproveRequest.cs21
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/VippsGetPaymentDetailsResponse.cs125
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/VippsInitiatePaymentRequest.cs27
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/VippsInitiatePaymentResponse.cs21
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/VippsPaymentActionRequest.cs19
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/VippsPaymentActionResponse.cs34
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/VippsPaymentInitiationCallbackResponse.cs119
-rw-r--r--src/IOL.VippsEcommerce/Models/Api/VippsRequestException.cs18
-rw-r--r--src/IOL.VippsEcommerce/Models/VippsConfiguration.cs130
-rw-r--r--src/IOL.VippsEcommerce/Models/VippsConfigurationKeyName.cs17
-rw-r--r--src/IOL.VippsEcommerce/Models/VippsConfigurationKeyNames.cs18
-rw-r--r--src/IOL.VippsEcommerce/ServiceCollectionExtensions.cs28
-rw-r--r--src/IOL.VippsEcommerce/VippsEcommerceService.cs562
30 files changed, 1781 insertions, 0 deletions
diff --git a/src/IOL.VippsEcommerce/Helpers.cs b/src/IOL.VippsEcommerce/Helpers.cs
new file mode 100644
index 0000000..8d6fb50
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Helpers.cs
@@ -0,0 +1,86 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace IOL.VippsEcommerce
+{
+ internal static class Helpers
+ {
+ private const int AES_BLOCK_BYTE_SIZE = 128 / 8;
+ private static readonly RandomNumberGenerator _random = RandomNumberGenerator.Create();
+
+ public static bool IsNullOrWhiteSpace(this string value) => string.IsNullOrWhiteSpace(value);
+ public static bool IsPresent(this string value) => !string.IsNullOrWhiteSpace(value);
+
+ public static bool IsDirectoryWritable(this string dirPath, bool throwIfFails = false) {
+ try {
+ using (var fs = File.Create(
+ Path.Combine(dirPath, Path.GetRandomFileName()),
+ 1,
+ FileOptions.DeleteOnClose)
+ ) { }
+
+ return true;
+ } catch {
+ if (throwIfFails)
+ throw;
+
+ return false;
+ }
+ }
+
+ //https://tomrucki.com/posts/aes-encryption-in-csharp/
+ public static string EncryptWithAes(this string toEncrypt, string password) {
+ var key = GetKey(password);
+
+ using var aes = CreateAes();
+ var iv = GenerateRandomBytes(AES_BLOCK_BYTE_SIZE);
+ var plainText = Encoding.UTF8.GetBytes(toEncrypt);
+
+ using var encryptor = aes.CreateEncryptor(key, iv);
+ var cipherText = encryptor
+ .TransformFinalBlock(plainText, 0, plainText.Length);
+
+ var result = new byte[iv.Length + cipherText.Length];
+ iv.CopyTo(result, 0);
+ cipherText.CopyTo(result, iv.Length);
+
+ return Convert.ToBase64String(result);
+ }
+
+ private static Aes CreateAes() {
+ var aes = Aes.Create();
+ aes.Mode = CipherMode.CBC;
+ aes.Padding = PaddingMode.PKCS7;
+ return aes;
+ }
+
+ public static string DecryptWithAes(this string input, string password) {
+ var key = GetKey(password);
+ var encryptedData = Convert.FromBase64String(input);
+
+ using var aes = CreateAes();
+ var iv = encryptedData.Take(AES_BLOCK_BYTE_SIZE).ToArray();
+ var cipherText = encryptedData.Skip(AES_BLOCK_BYTE_SIZE).ToArray();
+
+ using var decryptor = aes.CreateDecryptor(key, iv);
+ var decryptedBytes = decryptor
+ .TransformFinalBlock(cipherText, 0, cipherText.Length);
+ return Encoding.UTF8.GetString(decryptedBytes);
+ }
+
+ private static byte[] GetKey(string password) {
+ var keyBytes = Encoding.UTF8.GetBytes(password);
+ using var md5 = MD5.Create();
+ return md5.ComputeHash(keyBytes);
+ }
+
+ private static byte[] GenerateRandomBytes(int numberOfBytes) {
+ var randomBytes = new byte[numberOfBytes];
+ _random.GetBytes(randomBytes);
+ return randomBytes;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/IOL.VippsEcommerce.csproj b/src/IOL.VippsEcommerce/IOL.VippsEcommerce.csproj
new file mode 100644
index 0000000..3ae2596
--- /dev/null
+++ b/src/IOL.VippsEcommerce/IOL.VippsEcommerce.csproj
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net5.0</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
+ <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
+ <PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
+ </ItemGroup>
+
+</Project>
diff --git a/src/IOL.VippsEcommerce/IVippsEcommerceService.cs b/src/IOL.VippsEcommerce/IVippsEcommerceService.cs
new file mode 100644
index 0000000..f300e73
--- /dev/null
+++ b/src/IOL.VippsEcommerce/IVippsEcommerceService.cs
@@ -0,0 +1,49 @@
+using System.Threading;
+using System.Threading.Tasks;
+using IOL.VippsEcommerce.Models.Api;
+
+namespace IOL.VippsEcommerce
+{
+ public interface IVippsEcommerceService
+ {
+ public Task<VippsInitiatePaymentResponse> InitiatePaymentAsync(
+ VippsInitiatePaymentRequest payload,
+ CancellationToken ct = default
+ );
+
+ public Task<VippsPaymentActionResponse> CapturePaymentAsync(
+ string orderId,
+ VippsPaymentActionRequest payload,
+ CancellationToken ct = default
+ );
+
+ public Task<VippsPaymentActionResponse> CancelPaymentAsync(
+ string orderId,
+ VippsPaymentActionRequest payload,
+ CancellationToken ct = default
+ );
+
+ public Task<VippsPaymentActionResponse> AuthorizePaymentAsync(
+ string orderId,
+ VippsPaymentActionRequest payload,
+ CancellationToken ct = default
+ );
+
+ public Task<VippsPaymentActionResponse> RefundPaymentAsync(
+ string orderId,
+ VippsPaymentActionRequest payload,
+ CancellationToken ct = default
+ );
+
+ public Task<bool> ForceApprovePaymentAsync(
+ string orderId,
+ VippsForceApproveRequest payload,
+ CancellationToken ct = default
+ );
+
+ public Task<VippsGetPaymentDetailsResponse> GetPaymentDetailsAsync(
+ string orderId,
+ CancellationToken ct = default
+ );
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/EErrorGroupEnum.cs b/src/IOL.VippsEcommerce/Models/Api/EErrorGroupEnum.cs
new file mode 100644
index 0000000..8c6bf2f
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/EErrorGroupEnum.cs
@@ -0,0 +1,47 @@
+using System.Text.Json.Serialization;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ /// <summary>
+ /// The error group. See: https://github.com/vippsas/vipps-ecom-api/blob/master/vipps-ecom-api.md#error-groups
+ /// </summary>
+ public enum EErrorGroupEnum
+ {
+ /// <summary>
+ /// Enum Authentication for value: Authentication
+ /// </summary>
+ [JsonPropertyName("Authentication")]
+ AUTHENTICATION = 1,
+
+ /// <summary>
+ /// Enum Payments for value: Payments
+ /// </summary>
+ [JsonPropertyName("Payments")]
+ PAYMENTS = 2,
+
+ /// <summary>
+ /// Enum InvalidRequest for value: InvalidRequest
+ /// </summary>
+ [JsonPropertyName("InvalidRequest")]
+ INVALID_REQUEST = 3,
+
+ /// <summary>
+ /// Enum VippsError for value: VippsError
+ /// </summary>
+ [JsonPropertyName("VippsError")]
+ VIPPS_ERROR = 4,
+
+ /// <summary>
+ /// Enum User for value: User
+ /// </summary>
+ [JsonPropertyName("User")]
+ USER = 5,
+
+ /// <summary>
+ /// Enum Merchant for value: Merchant
+ /// </summary>
+ [JsonPropertyName("Merchant")]
+ MERCHANT = 6
+
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/EStatusEnum.cs b/src/IOL.VippsEcommerce/Models/Api/EStatusEnum.cs
new file mode 100644
index 0000000..bf579a4
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/EStatusEnum.cs
@@ -0,0 +1,25 @@
+using System.Text.Json.Serialization;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ public enum EStatusEnum
+ {
+ /// <summary>
+ /// Enum Cancelled for value: Cancelled
+ /// </summary>
+ [JsonPropertyName("Cancelled")]
+ CANCELLED = 1,
+
+ /// <summary>
+ /// Enum Captured for value: Captured
+ /// </summary>
+ [JsonPropertyName("Captured")]
+ CAPTURED = 2,
+
+ /// <summary>
+ /// Enum Refund for value: Refund
+ /// </summary>
+ [JsonPropertyName("Refund")]
+ REFUND = 3
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/ETransactionStatus.cs b/src/IOL.VippsEcommerce/Models/Api/ETransactionStatus.cs
new file mode 100644
index 0000000..303a4e9
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/ETransactionStatus.cs
@@ -0,0 +1,22 @@
+using System.Text.Json.Serialization;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ public enum ETransactionStatus
+ {
+ [JsonPropertyName("RESERVED")]
+ RESERVED,
+ [JsonPropertyName("SALE")]
+ SALE,
+ [JsonPropertyName("CANCELLED")]
+ CANCELLED,
+ [JsonPropertyName("REJECTED")]
+ REJECTED,
+ [JsonPropertyName("AUTO_CANCEL")]
+ AUTO_CANCEL,
+ [JsonPropertyName("SALE_FAILED")]
+ SALE_FAILED,
+ [JsonPropertyName("RESERVE_FAILED")]
+ RESERVE_FAILED
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/TAdditionalTransactionData.cs b/src/IOL.VippsEcommerce/Models/Api/TAdditionalTransactionData.cs
new file mode 100644
index 0000000..692c7be
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/TAdditionalTransactionData.cs
@@ -0,0 +1,42 @@
+using System.Text.Json.Serialization;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ public class TAdditionalTransactionData
+ {
+ /// <summary>
+ /// Passenger name, initials, and a title.
+ /// </summary>
+ /// <value>Passenger name, initials, and a title.</value>
+ [JsonPropertyName("passengerName")]
+ public string PassengerName { get; set; }
+
+ /// <summary>
+ /// IATA 3-digit accounting code (PAX); numeric. It identifies the carrier. eg KLM &#x3D; 074
+ /// </summary>
+ /// <value>IATA 3-digit accounting code (PAX); numeric. It identifies the carrier. eg KLM &#x3D; 074</value>
+ [JsonPropertyName("airlineCode")]
+ public string AirlineCode { get; set; }
+
+ /// <summary>
+ /// IATA 2-letter accounting code (PAX); alphabetical. It identifies the carrier. Eg KLM &#x3D; KL
+ /// </summary>
+ /// <value>IATA 2-letter accounting code (PAX); alphabetical. It identifies the carrier. Eg KLM &#x3D; KL</value>
+ [JsonPropertyName("airlineDesignatorCode")]
+ public string AirlineDesignatorCode { get; set; }
+
+ /// <summary>
+ /// The ticket&#x27;s unique identifier.
+ /// </summary>
+ /// <value>The ticket&#x27;s unique identifier.</value>
+ [JsonPropertyName("ticketNumber")]
+ public string TicketNumber { get; set; }
+
+ /// <summary>
+ /// Reference number for the invoice, issued by the agency.
+ /// </summary>
+ /// <value>Reference number for the invoice, issued by the agency.</value>
+ [JsonPropertyName("agencyInvoiceNumber")]
+ public string AgencyInvoiceNumber { get; set; }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/TCustomerInfo.cs b/src/IOL.VippsEcommerce/Models/Api/TCustomerInfo.cs
new file mode 100644
index 0000000..a558638
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/TCustomerInfo.cs
@@ -0,0 +1,14 @@
+using System.Text.Json.Serialization;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ public class TCustomerInfo
+ {
+ /// <summary>
+ /// Mobile number of the user who has to pay for the transation from Vipps. Allowed format: xxxxxxxx. No country code.
+ /// </summary>
+ /// <value>Mobile number of the user who has to pay for the transation from Vipps. Allowed format: xxxxxxxx. No country code.</value>
+ [JsonPropertyName("mobileNumber")]
+ public string MobileNumber { get; set; }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/TMerchantInfo.cs b/src/IOL.VippsEcommerce/Models/Api/TMerchantInfo.cs
new file mode 100644
index 0000000..881bc16
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/TMerchantInfo.cs
@@ -0,0 +1,65 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ public class TMerchantInfo
+ {
+ /// <summary>
+ /// Authorization token that the merchant could share to make callbacks more secure. If provided this token will be returned as an &#x60;Authorization&#x60; header for our callbacks. This includes shipping details and callback.
+ /// </summary>
+ /// <value>Authorization token that the merchant could share to make callbacks more secure. If provided this token will be returned as an &#x60;Authorization&#x60; header for our callbacks. This includes shipping details and callback.</value>
+ //[JsonPropertyName("authToken")]
+ //public string AuthToken { get; set; }
+
+ /// <summary>
+ /// This is an URL for Vipps to call at the merchant&#x27;s server to provide updated information about the order after the payment request. Domain name and context path should be provided by merchant as the value for this parameter. Vipps will add &#x60;/v2/payments/{orderId}&#x60; to the end or this URL. URLs passed to Vipps must validate with the Apache Commons [UrlValidator](https://commons.apache.org/proper/commons-validator/apidocs/org/apache/commons/validator/routines/UrlValidator.html). We don&#x27;t send requests to all ports, so to be safe use common ports such as: 80, 443, 8080.
+ /// </summary>
+ /// <value>This is an URL for Vipps to call at the merchant&#x27;s server to provide updated information about the order after the payment request. Domain name and context path should be provided by merchant as the value for this parameter. Vipps will add &#x60;/v2/payments/{orderId}&#x60; to the end or this URL. URLs passed to Vipps must validate with the Apache Commons [UrlValidator](https://commons.apache.org/proper/commons-validator/apidocs/org/apache/commons/validator/routines/UrlValidator.html). We don&#x27;t send requests to all ports, so to be safe use common ports such as: 80, 443, 8080.</value>
+ [JsonPropertyName("callbackPrefix")]
+ public string CallbackPrefix { get; set; }
+
+ /// <summary>
+ /// Required for Vipps Hurtigkasse (express checkout) payments. This callback URL will be used by Vipps to inform the merchant that the user has revoked his/her consent: This Vipps user does do not want the merchant to store or use his/her personal information anymore. Required by GDPR. Vipps will add &#x60;/v2/consents/{userId}&#x60; to the end or this URL. URLs passed to Vipps should be URL-encoded, and must validate with the Apache Commons [UrlValidator](https://commons.apache.org/proper/commons-validator/apidocs/org/apache/commons/validator/routines/UrlValidator.html). We don&#x27;t send requests to all ports, so to be safe use common ports such as: 80, 443, 8080.
+ /// </summary>
+ /// <value>Required for Vipps Hurtigkasse (express checkout) payments. This callback URL will be used by Vipps to inform the merchant that the user has revoked his/her consent: This Vipps user does do not want the merchant to store or use his/her personal information anymore. Required by GDPR. Vipps will add &#x60;/v2/consents/{userId}&#x60; to the end or this URL. URLs passed to Vipps should be URL-encoded, and must validate with the Apache Commons [UrlValidator](https://commons.apache.org/proper/commons-validator/apidocs/org/apache/commons/validator/routines/UrlValidator.html). We don&#x27;t send requests to all ports, so to be safe use common ports such as: 80, 443, 8080.</value>
+ //[JsonPropertyName("consentRemovalPrefix")]
+ //public string ConsentRemovalPrefix { get; set; }
+
+ /// <summary>
+ /// Vipps will use the fallBack URL to redirect the Vipps user to the merchant’s confirmation page once the payment is completed in Vipps. This is normally the “success page”, although the “fallback” name is ambiguous (the same URL is also used if payment was not successful). In other words: This is the URL Vipps sends the Vipps user back to. URLs passed to Vipps must validate with the Apache Commons [UrlValidator](https://commons.apache.org/proper/commons-validator/apidocs/org/apache/commons/validator/routines/UrlValidator.html).
+ /// </summary>
+ /// <value>Vipps will use the fallBack URL to redirect the Vipps user to the merchant’s confirmation page once the payment is completed in Vipps. This is normally the “success page”, although the “fallback” name is ambiguous (the same URL is also used if payment was not successful). In other words: This is the URL Vipps sends the Vipps user back to. URLs passed to Vipps must validate with the Apache Commons [UrlValidator](https://commons.apache.org/proper/commons-validator/apidocs/org/apache/commons/validator/routines/UrlValidator.html).</value>
+ [JsonPropertyName("fallBack")]
+ public string FallBack { get; set; }
+
+ /// <summary>
+ /// This parameter indicates whether payment request is triggered from Mobile App or Web browser. Based on this value, response will be redirect URL for Vipps landing page or deeplink URL to connect vipps App. When isApp is set to true, URLs passed to Vipps will not be validated as regular URLs.
+ /// </summary>
+ /// <value>This parameter indicates whether payment request is triggered from Mobile App or Web browser. Based on this value, response will be redirect URL for Vipps landing page or deeplink URL to connect vipps App. When isApp is set to true, URLs passed to Vipps will not be validated as regular URLs.</value>
+ [JsonPropertyName("isApp")]
+ public bool? IsApp { get; set; }
+
+ /// <summary>
+ /// Unique id for this merchant&#x27;s sales channel: website, mobile app etc. Short name: MSN.
+ /// </summary>
+ /// <value>Unique id for this merchant&#x27;s sales channel: website, mobile app etc. Short name: MSN.</value>
+ [JsonPropertyName("merchantSerialNumber")]
+ public string MerchantSerialNumber { get; set; }
+
+
+ /// <summary>
+ /// In case of Vipps Hurtigkasse (express checkout) payment, merchant should pass this prefix to let Vipps fetch shipping cost and method related details. Vipps will add &#x60;/v2/payments/{orderId}/shippingDetails&#x60; to the end or this URL. We don&#x27;t send requests to all ports, so to be safe use common ports such as: 80, 443, 8080.
+ /// </summary>
+ /// <value>In case of Vipps Hurtigkasse (express checkout) payment, merchant should pass this prefix to let Vipps fetch shipping cost and method related details. Vipps will add &#x60;/v2/payments/{orderId}/shippingDetails&#x60; to the end or this URL. We don&#x27;t send requests to all ports, so to be safe use common ports such as: 80, 443, 8080.</value>
+ [JsonPropertyName("shippingDetailsPrefix")]
+ public string ShippingDetailsPrefix { get; set; }
+
+ /// <summary>
+ /// If shipping method and cost are always a fixed value, for example 50 NOK, then the method and price can be provided during the initiate call. The shippingDetailsPrefix callback will not be used if this value is provided. This will result in a faster checkout and a better customer experience.
+ /// </summary>
+ /// <value>If shipping method and cost are always a fixed value, for example 50 NOK, then the method and price can be provided during the initiate call. The shippingDetailsPrefix callback will not be used if this value is provided. This will result in a faster checkout and a better customer experience.</value>
+ [JsonPropertyName("staticShippingDetails")]
+ public List<TShippingDetails> StaticShippingDetails { get; set; }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/TMerchantInfoPayment.cs b/src/IOL.VippsEcommerce/Models/Api/TMerchantInfoPayment.cs
new file mode 100644
index 0000000..ce0cbc3
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/TMerchantInfoPayment.cs
@@ -0,0 +1,15 @@
+using System.Text.Json.Serialization;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ public class TMerchantInfoPayment
+ {
+
+ /// <summary>
+ /// Unique id for this merchant&#x27;s sales channel: website, mobile app etc. Short name: MSN.
+ /// </summary>
+ /// <value>Unique id for this merchant&#x27;s sales channel: website, mobile app etc. Short name: MSN.</value>
+ [JsonPropertyName("merchantSerialNumber")]
+ public string MerchantSerialNumber { get; set; }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/TShippingDetails.cs b/src/IOL.VippsEcommerce/Models/Api/TShippingDetails.cs
new file mode 100644
index 0000000..72ac7f2
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/TShippingDetails.cs
@@ -0,0 +1,32 @@
+using System.Text.Json.Serialization;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ public class TShippingDetails
+ {
+ /// <summary>
+ /// Gets or Sets Priority
+ /// </summary>
+ [JsonPropertyName("priority")]
+ public int? Priority { get; set; }
+
+ /// <summary>
+ /// Gets or Sets ShippingCost
+ /// </summary>
+ [JsonPropertyName("shippingCost")]
+ public double? ShippingCost { get; set; }
+
+ /// <summary>
+ /// Shipping method. Max length: 256 characters. Recommended length for readability on most screens: 25 characters.
+ /// </summary>
+ /// <value>Shipping method. Max length: 256 characters. Recommended length for readability on most screens: 25 characters.</value>
+ [JsonPropertyName("shippingMethod")]
+ public string ShippingMethod { get; set; }
+
+ /// <summary>
+ /// Gets or Sets ShippingMethodId
+ /// </summary>
+ [JsonPropertyName("shippingMethodId")]
+ public string ShippingMethodId { get; set; }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/TTransaction.cs b/src/IOL.VippsEcommerce/Models/Api/TTransaction.cs
new file mode 100644
index 0000000..9e09758
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/TTransaction.cs
@@ -0,0 +1,21 @@
+using System.Text.Json.Serialization;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ public class TTransaction
+ {
+ /// <summary>
+ /// Amount in øre, if amount is 0 or not provided then full capture will be performed. 32 Bit Integer (2147483647)
+ /// </summary>
+ /// <value>Amount in øre, if amount is 0 or not provided then full capture will be performed. 32 Bit Integer (2147483647)</value>
+ [JsonPropertyName("amount")]
+ public int? Amount { get; set; }
+
+ /// <summary>
+ /// Transaction text to be displayed in Vipps
+ /// </summary>
+ /// <value>Transaction text to be displayed in Vipps</value>
+ [JsonPropertyName("transactionText")]
+ public string TransactionText { get; set; }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/TTransactionInfo.cs b/src/IOL.VippsEcommerce/Models/Api/TTransactionInfo.cs
new file mode 100644
index 0000000..30e62f9
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/TTransactionInfo.cs
@@ -0,0 +1,15 @@
+using System.Text.Json.Serialization;
+using IOL.VippsEcommerce.Models.Api;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ public class TTransactionInfo
+ {
+ /// <summary>
+ /// Status which gives the current state of the payment within Vipps. See the [API guide](https://github.com/vippsas/vipps-ecom-api/blob/master/vipps-ecom-api.md#responses-from-requests) for more information.
+ /// </summary>
+ /// <value>Status which gives the current state of the payment within Vipps. See the [API guide](https://github.com/vippsas/vipps-ecom-api/blob/master/vipps-ecom-api.md#responses-from-requests) for more information.</value>
+ [JsonPropertyName("status")]
+ public EStatusEnum Status { get; set; }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/TTransactionInfoInitiate.cs b/src/IOL.VippsEcommerce/Models/Api/TTransactionInfoInitiate.cs
new file mode 100644
index 0000000..7ad989b
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/TTransactionInfoInitiate.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Text.Json.Serialization;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ public class TTransactionInfoInitiate
+ {
+ /// <summary>
+ /// Amount in øre. 32 bit Integer (2147483647). Must be non-zero.
+ /// </summary>
+ /// <value>Amount in øre. 32 bit Integer (2147483647). Must be non-zero.</value>
+ [JsonPropertyName("amount")]
+ public int? Amount { get; set; }
+
+ /// <summary>
+ /// Id which uniquely identifies a payment. Maximum length is 50 alphanumeric characters: a-z, A-Z, 0-9 and &#x27;-&#x27;.
+ /// </summary>
+ /// <value>Id which uniquely identifies a payment. Maximum length is 50 alphanumeric characters: a-z, A-Z, 0-9 and &#x27;-&#x27;.</value>
+ [JsonPropertyName("orderId")]
+ public string OrderId { get; set; }
+
+ /// <summary>
+ /// ISO formatted date time string.
+ /// </summary>
+ /// <value>ISO formatted date time string.</value>
+ [JsonPropertyName("timeStamp")]
+ public string TimeStamp { get; set; }
+
+ /// <summary>
+ /// Transaction text to be displayed in Vipps
+ /// </summary>
+ /// <value>Transaction text to be displayed in Vipps</value>
+ [JsonPropertyName("transactionText")]
+ public string TransactionText { get; set; }
+
+ /// <summary>
+ /// Skips the landing page for whitelisted sale units. Requires a valid customerInfo.mobileNumber.
+ /// </summary>
+ /// <value>Skips the landing page for whitelisted sale units. Requires a valid customerInfo.mobileNumber.</value>
+ [JsonPropertyName("skipLandingPage")]
+ public bool? SkipLandingPage { get; set; }
+
+
+ /// <summary>
+ /// Gets or Sets AdditionalData
+ /// </summary>
+ [JsonPropertyName("additionalData")]
+ public TAdditionalTransactionData AdditionalData { get; set; }
+
+ /// <summary>
+ /// Use the extended UX flow for express checkout which forces users to confirm their address and shipping choices
+ /// </summary>
+ /// <value>Use the extended UX flow for express checkout which forces users to confirm their address and shipping choices</value>
+ [JsonPropertyName("useExplicitCheckoutFlow")]
+ public bool? UseExplicitCheckoutFlow { get; set; }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/TTransactionSummary.cs b/src/IOL.VippsEcommerce/Models/Api/TTransactionSummary.cs
new file mode 100644
index 0000000..786bcaa
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/TTransactionSummary.cs
@@ -0,0 +1,42 @@
+using System.Text.Json.Serialization;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ public class TTransactionSummary
+ {
+ /// <summary>
+ /// Total amount captured
+ /// </summary>
+ /// <value>Total amount captured</value>
+ [JsonPropertyName("capturedAmount")]
+ public int? CapturedAmount { get; set; }
+
+ /// <summary>
+ /// Total refunded amount of the order
+ /// </summary>
+ /// <value>Total refunded amount of the order</value>
+ [JsonPropertyName("refundedAmount")]
+ public int? RefundedAmount { get; set; }
+
+ /// <summary>
+ /// Total remaining amount to capture
+ /// </summary>
+ /// <value>Total remaining amount to capture</value>
+ [JsonPropertyName("remainingAmountToCapture")]
+ public int? RemainingAmountToCapture { get; set; }
+
+ /// <summary>
+ /// Total remaining amount to refund
+ /// </summary>
+ /// <value>Total remaining amount to refund</value>
+ [JsonPropertyName("remainingAmountToRefund")]
+ public int? RemainingAmountToRefund { get; set; }
+
+ /// <summary>
+ /// Bank Identification Number, first 6 digit of card number
+ /// </summary>
+ /// <value>Bank Identification Number, first 6 digit of card number</value>
+ [JsonPropertyName("bankIdentificationNumber")]
+ public string BankIdentificationNumber { get; set; }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/VippsAuthorizationTokenResponse.cs b/src/IOL.VippsEcommerce/Models/Api/VippsAuthorizationTokenResponse.cs
new file mode 100644
index 0000000..b870ddb
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/VippsAuthorizationTokenResponse.cs
@@ -0,0 +1,58 @@
+using System.Text.Json.Serialization;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ /// <summary>
+ /// AuthorizationTokenResponse
+ /// </summary>
+ public class VippsAuthorizationTokenResponse
+ {
+ /// <summary>
+ /// String containing the type for the Access Token.
+ /// </summary>
+ /// <value>String containing the type for the Access Token.</value>
+ [JsonPropertyName("token_type")]
+ public string TokenType { get; set; }
+
+ /// <summary>
+ /// Token expiry time in seconds.
+ /// </summary>
+ /// <value>Token expiry time in seconds.</value>
+ [JsonPropertyName("expires_in")]
+ public string ExpiresIn { get; set; }
+
+ /// <summary>
+ /// Extra time added to expiry time. Currently disabled.
+ /// </summary>
+ /// <value>Extra time added to expiry time. Currently disabled.</value>
+ [JsonPropertyName("ext_expires_in")]
+ public string ExtExpiresIn { get; set; }
+
+ /// <summary>
+ /// Token expiry time in epoch time format.
+ /// </summary>
+ /// <value>Token expiry time in epoch time format.</value>
+ [JsonPropertyName("expires_on")]
+ public string ExpiresOn { get; set; }
+
+ /// <summary>
+ /// Token creation time in epoch time format.
+ /// </summary>
+ /// <value>Token creation time in epoch time format.</value>
+ [JsonPropertyName("not_before")]
+ public string NotBefore { get; set; }
+
+ /// <summary>
+ /// A common resource object. Not used in token validation
+ /// </summary>
+ /// <value>A common resource object. Not used in token validation</value>
+ [JsonPropertyName("resource")]
+ public string Resource { get; set; }
+
+ /// <summary>
+ /// Gets or Sets AccessToken
+ /// </summary>
+ [JsonPropertyName("access_token")]
+ public string AccessToken { get; set; }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/VippsErrorResponse.cs b/src/IOL.VippsEcommerce/Models/Api/VippsErrorResponse.cs
new file mode 100644
index 0000000..6471913
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/VippsErrorResponse.cs
@@ -0,0 +1,39 @@
+using System.Text.Json.Serialization;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ /// <summary>
+ /// VippsErrorResponse
+ /// </summary>
+ public class VippsErrorResponse
+ {
+ /// <summary>
+ /// The error group. See: https://github.com/vippsas/vipps-ecom-api/blob/master/vipps-ecom-api.md#error-groups
+ /// </summary>
+ /// <value>The error group. See: https://github.com/vippsas/vipps-ecom-api/blob/master/vipps-ecom-api.md#error-groups</value>
+ [JsonPropertyName("errorGroup")]
+ public EErrorGroupEnum ErrorGroup { get; }
+
+
+ /// <summary>
+ /// The error code. See: https://github.com/vippsas/vipps-ecom-api/blob/master/vipps-ecom-api.md#error-codes
+ /// </summary>
+ /// <value>The error code. See: https://github.com/vippsas/vipps-ecom-api/blob/master/vipps-ecom-api.md#error-codes</value>
+ [JsonPropertyName("errorCode")]
+ public string ErrorCode { get; }
+
+ /// <summary>
+ /// A description of what went wrong. See https://github.com/vippsas/vipps-ecom-api/blob/master/vipps-ecom-api.md#errors
+ /// </summary>
+ /// <value>A description of what went wrong. See https://github.com/vippsas/vipps-ecom-api/blob/master/vipps-ecom-api.md#errors</value>
+ [JsonPropertyName("errorMessage")]
+ public string ErrorMessage { get; }
+
+ /// <summary>
+ /// A unique id for this error, useful for searching in logs
+ /// </summary>
+ /// <value>A unique id for this error, useful for searching in logs</value>
+ [JsonPropertyName("contextId")]
+ public string ContextId { get; }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/VippsForceApproveRequest.cs b/src/IOL.VippsEcommerce/Models/Api/VippsForceApproveRequest.cs
new file mode 100644
index 0000000..6a539c2
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/VippsForceApproveRequest.cs
@@ -0,0 +1,21 @@
+using System.Text.Json.Serialization;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ public class VippsForceApproveRequest
+ {
+ /// <summary>
+ /// Target customer phone number. 8 digits.
+ /// </summary>
+ /// <value>Target customer phone number. 8 digits.</value>
+ [JsonPropertyName("customerPhoneNumber")]
+ public string CustomerPhoneNumber { get; set; }
+
+ /// <summary>
+ /// The token value recieved in the &#x60;url&#x60; property in the Initiate response
+ /// </summary>
+ /// <value>The token value recieved in the &#x60;url&#x60; property in the Initiate response</value>
+ [JsonPropertyName("token")]
+ public string Token { get; set; }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/VippsGetPaymentDetailsResponse.cs b/src/IOL.VippsEcommerce/Models/Api/VippsGetPaymentDetailsResponse.cs
new file mode 100644
index 0000000..844ac88
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/VippsGetPaymentDetailsResponse.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Text.Json.Serialization;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ public class VippsGetPaymentDetailsResponse
+ {
+ [JsonPropertyName("orderId")]
+ public string OrderId { get; set; }
+
+ [JsonPropertyName("shippingDetails")]
+ public ShippingDetails ShippingDetails { get; set; }
+
+ [JsonPropertyName("transactionLogHistory")]
+ public TransactionLogHistory[] TransactionLogHistory { get; set; }
+
+ [JsonPropertyName("transactionSummary")]
+ public TransactionSummary TransactionSummary { get; set; }
+
+ [JsonPropertyName("userDetails")]
+ public UserDetails UserDetails { get; set; }
+ }
+
+ public class ShippingDetails
+ {
+ [JsonPropertyName("address")]
+ public Address Address { get; set; }
+
+ [JsonPropertyName("shippingCost")]
+ public long ShippingCost { get; set; }
+
+ [JsonPropertyName("shippingMethod")]
+ public string ShippingMethod { get; set; }
+
+ [JsonPropertyName("shippingMethodId")]
+ public string ShippingMethodId { get; set; }
+ }
+
+ public class Address
+ {
+ [JsonPropertyName("addressLine1")]
+ public string AddressLine1 { get; set; }
+
+ [JsonPropertyName("addressLine2")]
+ public string AddressLine2 { get; set; }
+
+ [JsonPropertyName("city")]
+ public string City { get; set; }
+
+ [JsonPropertyName("country")]
+ public string Country { get; set; }
+
+ [JsonPropertyName("postCode")]
+ public string PostCode { get; set; }
+ }
+
+ public class TransactionLogHistory
+ {
+ [JsonPropertyName("amount")]
+ public long Amount { get; set; }
+
+ [JsonPropertyName("operation")]
+ public string Operation { get; set; }
+
+ [JsonPropertyName("operationSuccess")]
+ public bool OperationSuccess { get; set; }
+
+ [JsonPropertyName("requestId")]
+ public string RequestId { get; set; }
+
+ [JsonPropertyName("timeStamp")]
+ public DateTime TimeStamp { get; set; }
+
+ [JsonPropertyName("transactionId")]
+ public string TransactionId { get; set; }
+
+ [JsonPropertyName("transactionText")]
+ public string TransactionText { get; set; }
+ }
+
+ public class TransactionSummary
+ {
+ [JsonPropertyName("capturedAmount")]
+ public long CapturedAmount { get; set; }
+
+ [JsonPropertyName("refundedAmount")]
+ public long RefundedAmount { get; set; }
+
+ [JsonPropertyName("remainingAmountToCapture")]
+ public long RemainingAmountToCapture { get; set; }
+
+ [JsonPropertyName("remainingAmountToRefund")]
+ public long RemainingAmountToRefund { get; set; }
+
+ [JsonPropertyName("bankIdentificationNumber")]
+ public long BankIdentificationNumber { get; set; }
+ }
+
+ public class UserDetails
+ {
+ [JsonPropertyName("bankIdVerified")]
+ public string BankIdVerified { get; set; }
+
+ [JsonPropertyName("dateOfBirth")]
+ public string DateOfBirth { get; set; }
+
+ [JsonPropertyName("email")]
+ public string Email { get; set; }
+
+ [JsonPropertyName("firstName")]
+ public string FirstName { get; set; }
+
+ [JsonPropertyName("lastName")]
+ public string LastName { get; set; }
+
+ [JsonPropertyName("mobileNumber")]
+ public long MobileNumber { get; set; }
+
+ [JsonPropertyName("ssn")]
+ public string Ssn { get; set; }
+
+ [JsonPropertyName("userId")]
+ public string UserId { get; set; }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/VippsInitiatePaymentRequest.cs b/src/IOL.VippsEcommerce/Models/Api/VippsInitiatePaymentRequest.cs
new file mode 100644
index 0000000..2e8a040
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/VippsInitiatePaymentRequest.cs
@@ -0,0 +1,27 @@
+using System.Text.Json.Serialization;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ public class VippsInitiatePaymentRequest
+ {
+ /// <summary>
+ /// Gets or Sets CustomerInfo
+ /// </summary>
+ [JsonPropertyName("customerInfo")]
+ public TCustomerInfo CustomerInfo { get; set; }
+
+ /// <summary>
+ /// Gets or Sets MerchantInfo
+ /// </summary>
+ ///
+ [JsonPropertyName("merchantInfo")]
+ public TMerchantInfo MerchantInfo { get; set; }
+
+ /// <summary>
+ /// Gets or Sets Transaction
+ /// </summary>
+ [JsonPropertyName("transaction")]
+ public TTransactionInfoInitiate Transaction { get; set; }
+
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/VippsInitiatePaymentResponse.cs b/src/IOL.VippsEcommerce/Models/Api/VippsInitiatePaymentResponse.cs
new file mode 100644
index 0000000..471133a
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/VippsInitiatePaymentResponse.cs
@@ -0,0 +1,21 @@
+using System.Text.Json.Serialization;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ public class VippsInitiatePaymentResponse
+ {
+ /// <summary>
+ /// Id which uniquely identifies a payment. Maximum length is 50 alphanumeric characters: a-z, A-Z, 0-9 and &#x27;-&#x27;.
+ /// </summary>
+ /// <value>Id which uniquely identifies a payment. Maximum length is 50 alphanumeric characters: a-z, A-Z, 0-9 and &#x27;-&#x27;.</value>
+ [JsonPropertyName("orderId")]
+ public string OrderId { get; set; }
+
+ /// <summary>
+ /// URL to redirect the user to Vipps landing page or a deeplink URL to open Vipps app, if &#x60;isApp&#x60; was set as true. The landing page will also redirect a user to the app if the user is using a mobile browser. This link will timeout after 5 minutes. This example is a shortened deeplink URL. The URL received fromn Vipps should not be changed, and the format may change without notice.
+ /// </summary>
+ /// <value>URL to redirect the user to Vipps landing page or a deeplink URL to open Vipps app, if &#x60;isApp&#x60; was set as true. The landing page will also redirect a user to the app if the user is using a mobile browser. This link will timeout after 5 minutes. This example is a shortened deeplink URL. The URL received fromn Vipps should not be changed, and the format may change without notice.</value>
+ [JsonPropertyName("url")]
+ public string Url { get; set; }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/VippsPaymentActionRequest.cs b/src/IOL.VippsEcommerce/Models/Api/VippsPaymentActionRequest.cs
new file mode 100644
index 0000000..f336bce
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/VippsPaymentActionRequest.cs
@@ -0,0 +1,19 @@
+using System.Text.Json.Serialization;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ public class VippsPaymentActionRequest
+ {
+ /// <summary>
+ /// Gets or Sets MerchantInfo
+ /// </summary>
+ [JsonPropertyName("merchantInfo")]
+ public TMerchantInfoPayment MerchantInfo { get; set; }
+
+ /// <summary>
+ /// Gets or Sets Transaction
+ /// </summary>
+ [JsonPropertyName("transaction")]
+ public TTransaction Transaction { get; set; }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/VippsPaymentActionResponse.cs b/src/IOL.VippsEcommerce/Models/Api/VippsPaymentActionResponse.cs
new file mode 100644
index 0000000..90b4389
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/VippsPaymentActionResponse.cs
@@ -0,0 +1,34 @@
+using System.Text.Json.Serialization;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ public class VippsPaymentActionResponse
+ {
+ /// <summary>
+ /// Text which describes what instrument was used to complete the payment. Not included until a user has chosen and approved in the app.
+ /// </summary>
+ /// <value>Text which describes what instrument was used to complete the payment. Not included until a user has chosen and approved in the app.</value>
+ [JsonPropertyName("paymentInstrument")]
+ public string PaymentInstrument { get; set; }
+
+ /// <summary>
+ /// Id which uniquely identifies a payment. Maximum length is 50 alphanumeric characters: a-z, A-Z, 0-9 and &#x27;-&#x27;.
+ /// </summary>
+ /// <value>Id which uniquely identifies a payment. Maximum length is 50 alphanumeric characters: a-z, A-Z, 0-9 and &#x27;-&#x27;.</value>
+ [JsonPropertyName("orderId")]
+ public string OrderId { get; set; }
+
+ /// <summary>
+ /// Gets or Sets TransactionInfo
+ /// </summary>
+ [JsonPropertyName("transactionInfo")]
+ public TTransactionInfo TransactionInfo { get; set; }
+
+ /// <summary>
+ /// Gets or Sets TransactionSummary
+ /// </summary>
+ [JsonPropertyName("transactionSummary")]
+ public TTransactionSummary TransactionSummary { get; set; }
+
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/VippsPaymentInitiationCallbackResponse.cs b/src/IOL.VippsEcommerce/Models/Api/VippsPaymentInitiationCallbackResponse.cs
new file mode 100644
index 0000000..3b02536
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/VippsPaymentInitiationCallbackResponse.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Text.Json.Serialization;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ public class VippsPaymentInitiationCallbackResponse
+ {
+ [JsonPropertyName("merchantSerialNumber")]
+ public string MerchantSerialNumber { get; set; }
+
+ [JsonPropertyName("orderId")]
+ public string OrderId { get; set; }
+
+ // [JsonPropertyName("shippingDetails")]
+ // public TShippingDetails? ShippingDetails { get; set; }
+
+ [JsonPropertyName("transactionInfo")]
+ public TTransactionInfo TransactionInfo { get; set; }
+
+ // [JsonPropertyName("userDetails")]
+ // public UserDetails? UserDetails { get; set; }
+ //
+ // [JsonPropertyName("errorInfo")]
+ // public TErrorInfo? ErrorInfo { get; set; }
+
+
+ public class TErrorInfo
+ {
+ [JsonPropertyName("errorGroup")]
+ public string ErrorGroup { get; set; }
+
+ [JsonPropertyName("errorCode")]
+ public string ErrorCode { get; set; }
+
+ [JsonPropertyName("errorMessage")]
+ public string ErrorMessage { get; set; }
+
+ [JsonPropertyName("contextId")]
+ public Guid ContextId { get; set; }
+ }
+
+ public class TShippingDetails
+ {
+ [JsonPropertyName("address")]
+ public TAddress Address { get; set; }
+
+ [JsonPropertyName("shippingCost")]
+ public int ShippingCost { get; set; }
+
+ [JsonPropertyName("shippingMethod")]
+ public string ShippingMethod { get; set; }
+
+ [JsonPropertyName("shippingMethodId")]
+ public string ShippingMethodId { get; set; }
+ }
+
+ public class TAddress
+ {
+ [JsonPropertyName("addressLine1")]
+ public string AddressLine1 { get; set; }
+
+ [JsonPropertyName("addressLine2")]
+ public string AddressLine2 { get; set; }
+
+ [JsonPropertyName("city")]
+ public string City { get; set; }
+
+ [JsonPropertyName("country")]
+ public string Country { get; set; }
+
+ [JsonPropertyName("zipCode")]
+ public string ZipCode { get; set; }
+ }
+
+ public class TTransactionInfo
+ {
+ [JsonPropertyName("amount")]
+ public int Amount { get; set; }
+
+ [JsonPropertyName("status")]
+ public string Status { get; set; }
+
+ public ETransactionStatus StatusEnum() => Enum.Parse<ETransactionStatus>(Status);
+
+ [JsonPropertyName("timeStamp")]
+ public DateTime TimeStamp { get; set; }
+
+ [JsonPropertyName("transactionId")]
+ public string TransactionId { get; set; }
+ }
+
+ public class TUserDetails
+ {
+ [JsonPropertyName("bankIdVerified")]
+ public string BankIdVerified { get; set; }
+
+ [JsonPropertyName("dateOfBirth")]
+ public string DateOfBirth { get; set; }
+
+ [JsonPropertyName("email")]
+ public string Email { get; set; }
+
+ [JsonPropertyName("firstName")]
+ public string FirstName { get; set; }
+
+ [JsonPropertyName("lastName")]
+ public string LastName { get; set; }
+
+ [JsonPropertyName("mobileNumber")]
+ public string MobileNumber { get; set; }
+
+ [JsonPropertyName("ssn")]
+ public string Ssn { get; set; }
+
+ [JsonPropertyName("userId")]
+ public string UserId { get; set; }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/Api/VippsRequestException.cs b/src/IOL.VippsEcommerce/Models/Api/VippsRequestException.cs
new file mode 100644
index 0000000..3a9b62a
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/Api/VippsRequestException.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace IOL.VippsEcommerce.Models.Api
+{
+ [Serializable]
+ public class VippsRequestException : Exception
+ {
+ public VippsRequestException() { }
+
+ public VippsRequestException(string message)
+ : base(message) { }
+
+ public VippsRequestException(string message, Exception inner)
+ : base(message, inner) { }
+
+ public VippsErrorResponse ErrorResponse { get; set; }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/VippsConfiguration.cs b/src/IOL.VippsEcommerce/Models/VippsConfiguration.cs
new file mode 100644
index 0000000..5411ebc
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/VippsConfiguration.cs
@@ -0,0 +1,130 @@
+using System;
+
+namespace IOL.VippsEcommerce.Models
+{
+ /// <summary>
+ /// Configuration fields for the vipps api and integration.
+ /// </summary>
+ public class VippsConfiguration
+ {
+ /// <summary>
+ /// Url for the vipps api.
+ /// <example>https://apitest.vipps.no</example>
+ /// <example>https://api.vipps.no</example>
+ /// <para>Corresponding environment variable name: VIPPS_API_URL</para>
+ /// </summary>
+ [VippsConfigurationKeyName(VippsConfigurationKeyNames.VIPPS_API_URL)]
+ public string ApiUrl { get; set; }
+
+ /// <summary>
+ /// Client ID for the merchant (the "username")
+ /// <para>Corresponding environment variable name: VIPPS_CLIENT_ID</para>
+ /// </summary>
+ [VippsConfigurationKeyName(VippsConfigurationKeyNames.VIPPS_CLIENT_ID)]
+ public string ClientId { get; set; }
+
+ /// <summary>
+ /// Client Secret for the merchant (the "password")
+ /// <para>Corresponding environment variable name: VIPPS_CLIENT_SECRET</para>
+ /// </summary>
+ [VippsConfigurationKeyName(VippsConfigurationKeyNames.VIPPS_CLIENT_SECRET)]
+ public string ClientSecret { get; set; }
+
+ /// <summary>
+ /// Primary subscription key for the API product. The primary subscription key take precedence over the secondary subscription key.
+ /// <para>Corresponding environment variable name: VIPPS_SUBSCRIPTION_KEY_PRIMARY</para>
+ /// </summary>
+ [VippsConfigurationKeyName(VippsConfigurationKeyNames.VIPPS_SUBSCRIPTION_KEY_PRIMARY)]
+ public string PrimarySubscriptionKey { get; set; }
+
+ /// <summary>
+ /// Secondary subscription key for the API product. The primary subscription key take precedence over the secondary subscription key.
+ /// <para>Corresponding environment variable name: VIPPS_SUBSCRIPTION_KEY_SECONDARY</para>
+ /// </summary>
+ [VippsConfigurationKeyName(VippsConfigurationKeyNames.VIPPS_SUBSCRIPTION_KEY_SECONDARY)]
+ public string SecondarySubscriptionKey { get; set; }
+
+ /// <summary>
+ /// The Merchant Serial Number (MSN) is a unique id for the sale unit that this payment is made for.
+ /// <para>Corresponding environment variable name: VIPPS_MSN</para>
+ /// </summary>
+ [VippsConfigurationKeyName(VippsConfigurationKeyNames.VIPPS_MSN)]
+ public string MerchantSerialNumber { get; set; }
+
+ /// <summary>
+ /// The name of the ecommerce solution. One word in lowercase letters is good.
+ /// <para>Corresponding environment variable name: VIPPS_SYSTEM_NAME</para>
+ /// </summary>
+ [VippsConfigurationKeyName(VippsConfigurationKeyNames.VIPPS_SYSTEM_NAME)]
+ public string SystemName { get; set; }
+
+ /// <summary>
+ /// The version number of the ecommerce solution.
+ /// <para>Corresponding environment variable name: VIPPS_SYSTEM_VERSION</para>
+ /// </summary>
+ [VippsConfigurationKeyName(VippsConfigurationKeyNames.VIPPS_SYSTEM_VERSION)]
+ public string SystemVersion { get; set; }
+
+ /// <summary>
+ /// The name of the ecommerce plugin (if applicable). One word in lowercase letters is good.
+ /// <para>Corresponding environment variable name: VIPPS_SYSTEM_PLUGIN_NAME</para>
+ /// </summary>
+ [VippsConfigurationKeyName(VippsConfigurationKeyNames.VIPPS_SYSTEM_PLUGIN_NAME)]
+ public string SystemPluginName { get; set; }
+
+ /// <summary>
+ /// The version number of the ecommerce plugin (if applicable).
+ /// <para>Corresponding environment variable name: VIPPS_SYSTEM_PLUGIN_VERSION</para>
+ /// </summary>
+ [VippsConfigurationKeyName(VippsConfigurationKeyNames.VIPPS_SYSTEM_PLUGIN_VERSION)]
+ public string SystemPluginVersion { get; set; }
+
+ /// <summary>
+ /// Optional path to a writable directory wherein a credential cache file can be placed.
+ /// <para>Corresponding environment variable name: VIPPS_CACHE_PATH</para>
+ /// </summary>
+ [VippsConfigurationKeyName(VippsConfigurationKeyNames.VIPPS_CACHE_PATH)]
+ public string CredentialsCacheFilePath { get; set; }
+
+ /// <summary>
+ /// Optional key for AES encryption of the credential cache file.
+ /// <para>Corresponding environment variable name: VIPPS_CACHE_KEY</para>
+ /// </summary>
+ [VippsConfigurationKeyName(VippsConfigurationKeyNames.VIPPS_CACHE_KEY)]
+ public string CacheEncryptionKey { get; set; }
+
+ /// <summary>
+ /// Use environment variables for configuration.
+ /// <para>If this is true, all requested properties are looked for in the environment.</para>
+ /// </summary>
+ public bool UseEnvironment { get; set; }
+
+ /// <summary>
+ /// Get value from configuration, either from Dependency injection or from the environment.
+ /// </summary>
+ /// <param name="key">Configuration key.</param>
+ /// <param name="fallback">Fallback value if the key is not found or empty.</param>
+ /// <returns>A string containing the configuration value (or a fallback).</returns>
+ public string GetValue(string key, string fallback = default) {
+ if (UseEnvironment) {
+ return Environment.GetEnvironmentVariable(key) ?? fallback;
+ }
+
+ foreach (var prop in typeof(VippsConfiguration).GetProperties()) {
+ foreach (var attribute in prop.CustomAttributes) {
+ foreach (var argument in attribute.ConstructorArguments) {
+ if (argument.Value as string == key) {
+ var value = prop.GetValue(this, null)?.ToString();
+#if DEBUG
+ Console.WriteLine("Key: " + key + " Value: " + value);
+#endif
+ return value;
+ }
+ }
+ }
+ }
+
+ return default;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/VippsConfigurationKeyName.cs b/src/IOL.VippsEcommerce/Models/VippsConfigurationKeyName.cs
new file mode 100644
index 0000000..2ed338b
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/VippsConfigurationKeyName.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace IOL.VippsEcommerce.Models
+{
+ sealed class VippsConfigurationKeyName : Attribute
+ {
+ /// <summary>
+ /// Specifies a name for this configuration value.
+ /// </summary>
+ /// <param name="name">Name of the configuration value.</param>
+ public VippsConfigurationKeyName(string name) {
+ Name = name;
+ }
+
+ public string Name { get; }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/Models/VippsConfigurationKeyNames.cs b/src/IOL.VippsEcommerce/Models/VippsConfigurationKeyNames.cs
new file mode 100644
index 0000000..4b25c7d
--- /dev/null
+++ b/src/IOL.VippsEcommerce/Models/VippsConfigurationKeyNames.cs
@@ -0,0 +1,18 @@
+namespace IOL.VippsEcommerce.Models
+{
+ public static class VippsConfigurationKeyNames
+ {
+ public const string VIPPS_API_URL = "VIPPS_API_URL";
+ public const string VIPPS_CLIENT_ID = "VIPPS_CLIENT_ID";
+ public const string VIPPS_CLIENT_SECRET = "VIPPS_CLIENT_SECRET";
+ public const string VIPPS_SUBSCRIPTION_KEY_PRIMARY = "VIPPS_SUBSCRIPTION_KEY_PRIMARY";
+ public const string VIPPS_SUBSCRIPTION_KEY_SECONDARY = "VIPPS_SUBSCRIPTION_KEY_SECONDARY";
+ public const string VIPPS_MSN = "VIPPS_MSN";
+ public const string VIPPS_SYSTEM_NAME = "VIPPS_SYSTEM_NAME";
+ public const string VIPPS_SYSTEM_VERSION = "VIPPS_SYSTEM_VERSION";
+ public const string VIPPS_SYSTEM_PLUGIN_NAME = "VIPPS_SYSTEM_PLUGIN_NAME";
+ public const string VIPPS_SYSTEM_PLUGIN_VERSION = "VIPPS_SYSTEM_PLUGIN_VERSION";
+ public const string VIPPS_CACHE_PATH = "VIPPS_CACHE_PATH";
+ public const string VIPPS_CACHE_KEY = "VIPPS_CACHE_KEY";
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/ServiceCollectionExtensions.cs b/src/IOL.VippsEcommerce/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..d9c9769
--- /dev/null
+++ b/src/IOL.VippsEcommerce/ServiceCollectionExtensions.cs
@@ -0,0 +1,28 @@
+using System;
+using IOL.VippsEcommerce.Models;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace IOL.VippsEcommerce
+{
+ public static class ServiceCollectionExtensions
+ {
+ /// <summary>
+ /// Configures and adds the VippsEcommerceService to your DI.
+ /// </summary>
+ /// <param name="services">Servicecollection to add VippsEcommerceService to.</param>
+ /// <param name="configuration"></param>
+ /// <returns></returns>
+ public static IServiceCollection AddVippsEcommerceService(
+ this IServiceCollection services,
+ Action<VippsConfiguration> configuration
+ ) {
+ if (services == null) throw new ArgumentNullException(nameof(services));
+ if (configuration == null) throw new ArgumentNullException(nameof(configuration));
+
+ services.Configure(configuration);
+ services.AddHttpClient<IVippsEcommerceService, VippsEcommerceService>();
+ services.AddScoped<IVippsEcommerceService, VippsEcommerceService>();
+ return services;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.VippsEcommerce/VippsEcommerceService.cs b/src/IOL.VippsEcommerce/VippsEcommerceService.cs
new file mode 100644
index 0000000..3a3a5dc
--- /dev/null
+++ b/src/IOL.VippsEcommerce/VippsEcommerceService.cs
@@ -0,0 +1,562 @@
+using System;
+using System.IO;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Net.Http.Json;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using IOL.VippsEcommerce.Models;
+using IOL.VippsEcommerce.Models.Api;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace IOL.VippsEcommerce
+{
+ public class VippsEcommerceService : IVippsEcommerceService
+
+ {
+ private readonly HttpClient _client;
+ private readonly ILogger<VippsEcommerceService> _logger;
+ private readonly string _vippsClientId;
+ private readonly string _vippsClientSecret;
+ private readonly string _vippsMsn;
+ private readonly string _cacheEncryptionKey;
+ private readonly string _cacheFileDirectoryPath;
+
+ private const string CACHE_FILE_NAME = "vipps_ecommerce_credentials.json";
+ private string CacheFilePath => Path.Combine(_cacheFileDirectoryPath, CACHE_FILE_NAME);
+
+ public VippsEcommerceService(
+ HttpClient client,
+ ILogger<VippsEcommerceService> logger,
+ IOptions<VippsConfiguration> options
+ ) {
+ var configuration = options.Value;
+ var vippsApiUrl = configuration.GetValue(VippsConfigurationKeyNames.VIPPS_API_URL);
+ if (vippsApiUrl.IsNullOrWhiteSpace()) {
+ throw new ArgumentException("VippsEcommerceService: Api url is not provided in configuration.");
+ }
+
+ client.BaseAddress = new Uri(vippsApiUrl);
+ _client = client;
+ _logger = logger;
+
+ _vippsClientId = configuration.GetValue(VippsConfigurationKeyNames.VIPPS_CLIENT_ID);
+ if (_vippsClientId.IsNullOrWhiteSpace()) {
+ throw new ArgumentException("VippsEcommerceService: Client id is not provided in configuration.");
+ }
+
+ _vippsClientSecret = configuration.GetValue(VippsConfigurationKeyNames.VIPPS_CLIENT_SECRET);
+ if (_vippsClientSecret.IsNullOrWhiteSpace()) {
+ throw new ArgumentException("VippsEcommerceService: Client secret is not provided in configuration.");
+ }
+
+ var primarySubscriptionKey =
+ configuration.GetValue(VippsConfigurationKeyNames.VIPPS_SUBSCRIPTION_KEY_PRIMARY);
+ var secondarySubscriptionKey =
+ configuration.GetValue(VippsConfigurationKeyNames.VIPPS_SUBSCRIPTION_KEY_SECONDARY);
+ if (primarySubscriptionKey.IsNullOrWhiteSpace() && secondarySubscriptionKey.IsNullOrWhiteSpace()) {
+ throw new
+ ArgumentException("VippsEcommerceService: Neither primary nor secondary subscription key was provided in configuration.");
+ }
+
+ client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key",
+ primarySubscriptionKey ?? secondarySubscriptionKey);
+
+ var msn = configuration.GetValue(VippsConfigurationKeyNames.VIPPS_MSN);
+ if (msn.IsPresent()) {
+ client.DefaultRequestHeaders.Add("Merchant-Serial-Number", msn);
+ _vippsMsn = msn;
+ }
+
+ var systemName = configuration.GetValue(VippsConfigurationKeyNames.VIPPS_SYSTEM_NAME);
+ if (systemName.IsPresent()) {
+ client.DefaultRequestHeaders.Add("Vipps-System-Name", systemName);
+ }
+
+ var systemVersion = configuration.GetValue(VippsConfigurationKeyNames.VIPPS_SYSTEM_VERSION);
+ if (systemVersion.IsPresent()) {
+ client.DefaultRequestHeaders.Add("Vipps-System-Version", systemVersion);
+ }
+
+ var systemPluginName = configuration.GetValue(VippsConfigurationKeyNames.VIPPS_SYSTEM_PLUGIN_NAME);
+ if (systemPluginName.IsPresent()) {
+ client.DefaultRequestHeaders.Add("Vipps-System-Plugin-Name", systemPluginName);
+ }
+
+ var systemPluginVersion = configuration.GetValue(VippsConfigurationKeyNames.VIPPS_SYSTEM_PLUGIN_VERSION);
+ if (systemPluginVersion.IsPresent()) {
+ client.DefaultRequestHeaders.Add("Vipps-System-Plugin-Version", systemPluginVersion);
+ }
+
+ _cacheEncryptionKey = configuration.GetValue(VippsConfigurationKeyNames.VIPPS_CACHE_KEY);
+ _cacheFileDirectoryPath = configuration.GetValue(VippsConfigurationKeyNames.VIPPS_CACHE_PATH);
+ if (_cacheFileDirectoryPath.IsPresent()) {
+ if (!_cacheFileDirectoryPath.IsDirectoryWritable()) {
+ _logger.LogError("Could not write to cache file directory ("
+ + _cacheFileDirectoryPath
+ + "). Disabling caching.");
+ _cacheFileDirectoryPath = default;
+ _cacheEncryptionKey = default;
+ }
+ }
+
+ _logger.LogInformation("VippsEcommerceService was initialized with api url: " + vippsApiUrl);
+ }
+
+ /// <summary>
+ /// The access token endpoint is used to get the JWT (JSON Web Token) that must be passed in every API request in the Authorization header.
+ /// The access token is a base64-encoded string value that must be aquired first before making any Vipps api calls.
+ /// The access token is valid for 1 hour in the test environment and 24 hours in the production environment.
+ /// </summary>
+ /// <returns></returns>
+ /// <exception cref="HttpRequestException">Throws if the api returns unsuccessfully</exception>
+ private async Task<VippsAuthorizationTokenResponse> GetAuthorizationTokenAsync(
+ bool forceRefresh = false,
+ CancellationToken ct = default
+ ) {
+ if (!forceRefresh) {
+ if (_cacheFileDirectoryPath.IsPresent() && File.Exists(CacheFilePath)) {
+ var fileContents = await File.ReadAllTextAsync(CacheFilePath, ct);
+
+ if (fileContents.IsPresent()) {
+ VippsAuthorizationTokenResponse credentials = default;
+ try {
+ credentials = JsonSerializer.Deserialize<VippsAuthorizationTokenResponse>(fileContents);
+ } catch (Exception e) {
+ if (e is JsonException && _cacheEncryptionKey.IsPresent()) {
+ // most likely encrypted, try to decrypt
+ var decryptedContents = fileContents.DecryptWithAes(_cacheEncryptionKey);
+ credentials =
+ JsonSerializer.Deserialize<VippsAuthorizationTokenResponse>(decryptedContents);
+ }
+ }
+
+ if (credentials != default) {
+ var currentEpoch = new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds();
+ if (long.TryParse(credentials.ExpiresOn, out var expires)
+ && credentials.AccessToken.IsPresent()) {
+ if (expires - 600 > currentEpoch) {
+ _logger.LogDebug("VippsEcommerceService: Got tokens from cache");
+ return credentials;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ var requestMessage = new HttpRequestMessage {
+ Headers = {
+ {
+ "client_id", _vippsClientId
+ }, {
+ "client_secret", _vippsClientSecret
+ },
+ },
+ RequestUri = new Uri(_client.BaseAddress + "accesstoken/get"),
+ Method = HttpMethod.Post
+ };
+ var response = await _client.SendAsync(requestMessage, ct);
+
+ try {
+ response.EnsureSuccessStatusCode();
+ var credentials = await response.Content.ReadAsStringAsync(ct);
+
+ if (_cacheFileDirectoryPath.IsPresent()) {
+ await File.WriteAllTextAsync(CacheFilePath,
+ _cacheEncryptionKey.IsPresent()
+ ? credentials.EncryptWithAes(_cacheEncryptionKey)
+ : credentials,
+ ct);
+ }
+
+ _logger.LogDebug("VippsEcommerceService: Got tokens from " + requestMessage.RequestUri);
+ return JsonSerializer.Deserialize<VippsAuthorizationTokenResponse>(credentials);
+ } catch (Exception e) {
+ Console.WriteLine(e);
+ return default;
+ }
+ }
+
+
+ /// <summary>
+ /// This API call allows the merchants to initiate payments.
+ /// The merchantSerialNumber (MSN) specifies which sales unit the payments is for.
+ /// Payments are uniquely identified with the merchantSerialNumber and orderId together.
+ /// The merchant-provided orderId must be unique per sales channel.
+ /// Once the transaction is successfully initiated in Vipps, you will receive a response with a fallBack URL which will direct the customer to the Vipps landing page.
+ /// The landing page detects if the request comes from a mobile or laptop/desktop device, and if on a mobile device automatically switches to the Vipps app if it is intalled.
+ /// The merchant may also pass the 'isApp: true' parameter that will make Vipps respond with a app-switch deeplink that will take the customer directly to the Vipps app.
+ /// URLs passed to Vipps must validate with the Apache Commons UrlValidator.
+ /// </summary>
+ /// <returns></returns>
+ /// <exception cref="HttpRequestException">Throws if the api returns unsuccessfully</exception>
+ public async Task<VippsInitiatePaymentResponse> InitiatePaymentAsync(
+ VippsInitiatePaymentRequest payload,
+ CancellationToken ct = default
+ ) {
+ if (_client.DefaultRequestHeaders.Authorization?.Parameter.IsNullOrWhiteSpace() ?? true) {
+ var credentials = await GetAuthorizationTokenAsync(false, ct);
+ _client.DefaultRequestHeaders.Authorization =
+ new AuthenticationHeaderValue("Bearer", credentials.AccessToken);
+ }
+
+ var response = await _client.PostAsJsonAsync("ecomm/v2/payments",
+ payload,
+ new JsonSerializerOptions {
+ IgnoreNullValues = true
+ },
+ ct);
+
+ try {
+ response.EnsureSuccessStatusCode();
+ _logger.LogDebug("VippsEcommerceService: Sent InitiatePaymentRequest");
+ return await response.Content
+ .ReadFromJsonAsync<VippsInitiatePaymentResponse>(cancellationToken: ct);
+ } catch (Exception e) {
+ var exception =
+ new VippsRequestException("Vipps initiate payment request returned unsuccessfully.", e);
+ if (e is HttpRequestException) {
+ try {
+ exception.ErrorResponse =
+ await response.Content.ReadFromJsonAsync<VippsErrorResponse>(cancellationToken: ct);
+ _logger.LogError("ErrorResponse: " + JsonSerializer.Serialize(response.Content));
+ } catch (Exception e1) {
+ _logger.LogError("Unknown ErrorResponse: " + JsonSerializer.Serialize(response.Content));
+ Console.WriteLine(e1);
+ }
+ }
+
+ Console.WriteLine(e);
+ throw exception;
+ }
+ }
+
+ /// <summary>
+ /// This API call allows merchant to capture the reserved amount.
+ /// Amount to capture cannot be higher than reserved.
+ /// The API also allows capturing partial amount of the reserved amount.
+ /// Partial capture can be called as many times as required so long there is reserved amount to capture.
+ /// Transaction text is not optional and is used as a proof of delivery (tracking code, consignment number etc.).
+ /// In a case of direct capture, both fund reservation and capture are executed in a single operation.
+ /// It is important to check the response, and the capture is only successful when the response is HTTP 200 OK.
+ /// </summary>
+ /// <returns></returns>
+ /// <exception cref="HttpRequestException">Throws if the api returns unsuccessfully</exception>
+ public async Task<VippsPaymentActionResponse> CapturePaymentAsync(
+ string orderId,
+ VippsPaymentActionRequest payload,
+ CancellationToken ct = default
+ ) {
+ if (_client.DefaultRequestHeaders.Authorization?.Parameter.IsNullOrWhiteSpace() ?? true) {
+ var credentials = await GetAuthorizationTokenAsync(false, ct);
+ _client.DefaultRequestHeaders.Authorization =
+ new AuthenticationHeaderValue("Bearer", credentials.AccessToken);
+ }
+
+
+ if (payload.MerchantInfo?.MerchantSerialNumber.IsNullOrWhiteSpace() ?? false) {
+ payload.MerchantInfo = new TMerchantInfoPayment {
+ MerchantSerialNumber = _vippsMsn
+ };
+ }
+
+ var response = await _client.PostAsJsonAsync("ecomm/v2/payments/" + orderId + "/capture",
+ payload,
+ new JsonSerializerOptions {
+ IgnoreNullValues = true
+ },
+ ct);
+
+ try {
+ response.EnsureSuccessStatusCode();
+ _logger.LogDebug("VippsEcommerceService: Sent CapturePaymentRequest");
+ return await response.Content.ReadFromJsonAsync<VippsPaymentActionResponse>(cancellationToken: ct);
+ } catch (Exception e) {
+ var exception =
+ new VippsRequestException("Vipps capture payment request returned unsuccessfully.", e);
+ if (e is HttpRequestException) {
+ try {
+ exception.ErrorResponse =
+ await response.Content.ReadFromJsonAsync<VippsErrorResponse>(cancellationToken: ct);
+ _logger.LogError("ErrorResponse: " + JsonSerializer.Serialize(response.Content));
+ } catch (Exception e1) {
+ _logger.LogError("Unknown ErrorResponse: " + JsonSerializer.Serialize(response.Content));
+ Console.WriteLine(e1);
+ }
+ }
+
+ Console.WriteLine(e);
+ throw exception;
+ }
+ }
+
+
+ /// <summary>
+ /// The API call allows merchant to cancel the reserved or initiated transaction.
+ /// The API will not allow partial cancellation which has the consequence that partially captured transactions cannot be cancelled.
+ /// Please note that in a case of communication errors during initiate payment service call between Vipps and PSP/Acquirer/Issuer; even in a case that customer has confirmed a payment, the payment will be cancelled by Vipps.
+ /// Note this means you can not cancel a captured payment.
+ /// </summary>
+ /// <returns></returns>
+ /// <exception cref="HttpRequestException">Throws if the api returns unsuccessfully</exception>
+ public async Task<VippsPaymentActionResponse> CancelPaymentAsync(
+ string orderId,
+ VippsPaymentActionRequest payload,
+ CancellationToken ct = default
+ ) {
+ if (_client.DefaultRequestHeaders.Authorization?.Parameter.IsNullOrWhiteSpace() ?? true) {
+ var credentials = await GetAuthorizationTokenAsync(false, ct);
+ _client.DefaultRequestHeaders.Authorization =
+ new AuthenticationHeaderValue("Bearer", credentials.AccessToken);
+ }
+
+ if (payload.MerchantInfo?.MerchantSerialNumber.IsNullOrWhiteSpace() ?? false) {
+ payload.MerchantInfo = new TMerchantInfoPayment {
+ MerchantSerialNumber = _vippsMsn
+ };
+ }
+
+ var response = await _client.PutAsJsonAsync("ecomm/v2/payments/" + orderId + "/cancel",
+ payload,
+ new JsonSerializerOptions {
+ IgnoreNullValues = true
+ },
+ ct);
+
+ try {
+ response.EnsureSuccessStatusCode();
+ _logger.LogDebug("VippsEcommerceService: Sent CancelPaymentRequest");
+ return await response.Content.ReadFromJsonAsync<VippsPaymentActionResponse>(cancellationToken: ct);
+ } catch (Exception e) {
+ var exception =
+ new VippsRequestException("Vipps cancel payment request returned unsuccessfully.", e);
+ if (e is HttpRequestException) {
+ try {
+ exception.ErrorResponse =
+ await response.Content.ReadFromJsonAsync<VippsErrorResponse>(cancellationToken: ct);
+ _logger.LogError("ErrorResponse: " + JsonSerializer.Serialize(response.Content));
+ } catch (Exception e1) {
+ _logger.LogError("Unknown ErrorResponse: " + JsonSerializer.Serialize(response.Content));
+ Console.WriteLine(e1);
+ }
+ }
+
+
+ Console.WriteLine(e);
+ throw exception;
+ }
+ }
+
+ /// <summary>
+ /// The API call allows merchant to refresh the authorizations of the payment.
+ /// A reservation's lifetime is defined by the scheme. Typically 7 days for Visa, and 30 days for Mastercard.
+ /// This is currently not live in production and will be added shortly.
+ /// </summary>
+ /// <returns></returns>
+ /// <exception cref="HttpRequestException">Throws if the api returns unsuccessfully</exception>
+ public async Task<VippsPaymentActionResponse> AuthorizePaymentAsync(
+ string orderId,
+ VippsPaymentActionRequest payload,
+ CancellationToken ct = default
+ ) {
+ if (_client.DefaultRequestHeaders.Authorization?.Parameter.IsNullOrWhiteSpace() ?? true) {
+ var credentials = await GetAuthorizationTokenAsync(false, ct);
+ _client.DefaultRequestHeaders.Authorization =
+ new AuthenticationHeaderValue("Bearer", credentials.AccessToken);
+ }
+
+ if (payload.MerchantInfo?.MerchantSerialNumber.IsNullOrWhiteSpace() ?? false) {
+ payload.MerchantInfo = new TMerchantInfoPayment {
+ MerchantSerialNumber = _vippsMsn
+ };
+ }
+
+ var response = await _client.PutAsJsonAsync("ecomm/v2/payments/" + orderId + "/authorize",
+ payload,
+ new JsonSerializerOptions {
+ IgnoreNullValues = true
+ },
+ ct);
+
+ try {
+ response.EnsureSuccessStatusCode();
+ _logger.LogDebug("VippsEcommerceService: Sent AuthorizePaymentRequest");
+ return await response.Content.ReadFromJsonAsync<VippsPaymentActionResponse>(cancellationToken: ct);
+ } catch (Exception e) {
+ var exception =
+ new VippsRequestException("Vipps authorize payment request returned unsuccessfully.", e);
+ if (e is HttpRequestException) {
+ try {
+ exception.ErrorResponse =
+ await response.Content.ReadFromJsonAsync<VippsErrorResponse>(cancellationToken: ct);
+ _logger.LogError("ErrorResponse: " + JsonSerializer.Serialize(response.Content));
+ } catch (Exception e1) {
+ _logger.LogError("Unknown ErrorResponse: " + JsonSerializer.Serialize(response.Content));
+ Console.WriteLine(e1);
+ }
+ }
+
+
+ Console.WriteLine(e);
+ throw exception;
+ }
+ }
+
+ /// <summary>
+ /// The API allows a merchant to do a refund of already captured transaction.
+ /// There is an option to do a partial refund of the captured amount.
+ /// Refunded amount cannot be larger than captured.
+ /// Timeframe for issuing a refund for a payment is 365 days from the date payment has been captured.
+ /// If the refund payment service call is called after the refund timeframe, service call will respond with an error.
+ /// Refunded funds will be transferred from the merchant account to the customer credit card that was used in payment flow.
+ /// Pay attention that in order to perform refund, there must be enough funds at merchant settlements account.
+ /// </summary>
+ /// <returns></returns>
+ /// <exception cref="HttpRequestException">Throws if the api returns unsuccessfully</exception>
+ public async Task<VippsPaymentActionResponse> RefundPaymentAsync(
+ string orderId,
+ VippsPaymentActionRequest payload,
+ CancellationToken ct = default
+ ) {
+ if (_client.DefaultRequestHeaders.Authorization?.Parameter.IsNullOrWhiteSpace() ?? true) {
+ var credentials = await GetAuthorizationTokenAsync(false, ct);
+ _client.DefaultRequestHeaders.Authorization =
+ new AuthenticationHeaderValue("Bearer", credentials.AccessToken);
+ }
+
+ if (payload.MerchantInfo?.MerchantSerialNumber.IsNullOrWhiteSpace() ?? false) {
+ payload.MerchantInfo = new TMerchantInfoPayment {
+ MerchantSerialNumber = _vippsMsn
+ };
+ }
+
+ var response = await _client.PostAsJsonAsync("ecomm/v2/payments/" + orderId + "/refund",
+ payload,
+ new JsonSerializerOptions {
+ IgnoreNullValues = true
+ },
+ ct);
+ try {
+ response.EnsureSuccessStatusCode();
+ _logger.LogDebug("VippsEcommerceService: Sent RefundPaymentRequest");
+ return await response.Content.ReadFromJsonAsync<VippsPaymentActionResponse>(cancellationToken: ct);
+ } catch (Exception e) {
+ var exception =
+ new VippsRequestException("Vipps refund payment request returned unsuccessfully.", e);
+ if (e is HttpRequestException) {
+ try {
+ exception.ErrorResponse =
+ await response.Content.ReadFromJsonAsync<VippsErrorResponse>(cancellationToken: ct);
+ _logger.LogError("ErrorResponse: " + JsonSerializer.Serialize(response.Content));
+ } catch (Exception e1) {
+ _logger.LogError("Unknown ErrorResponse: " + JsonSerializer.Serialize(response.Content));
+ Console.WriteLine(e1);
+ }
+ }
+
+
+ Console.WriteLine(e);
+ throw exception;
+ }
+ }
+
+
+ /// <summary>
+ /// This endpoint allows developers to approve a payment through the Vipps eCom API without the use of the Vipps app.
+ /// This is useful for automated testing.
+ /// Express checkout is not supported for this endpoint.
+ /// The endpoint is only available in our Test environment.
+ /// Attempted use of the endpoint in production is not allowed, and will fail.
+ /// </summary>
+ /// <returns></returns>
+ /// <exception cref="HttpRequestException">Throws if the api returns unsuccessfully</exception>
+ public async Task<bool> ForceApprovePaymentAsync(
+ string orderId,
+ VippsForceApproveRequest payload,
+ CancellationToken ct = default
+ ) {
+ if (_client.DefaultRequestHeaders.Authorization?.Parameter.IsNullOrWhiteSpace() ?? true) {
+ var credentials = await GetAuthorizationTokenAsync(false, ct);
+ _client.DefaultRequestHeaders.Authorization =
+ new AuthenticationHeaderValue("Bearer", credentials.AccessToken);
+ }
+
+
+ var response =
+ await _client.PostAsJsonAsync("ecomm/v2/integration-test/payments/" + orderId + "/approve",
+ payload,
+ new JsonSerializerOptions {
+ IgnoreNullValues = true
+ },
+ ct);
+
+ try {
+ response.EnsureSuccessStatusCode();
+ _logger.LogDebug("VippsEcommerceService: Sent ForceApprovePaymentRequest");
+ return true;
+ } catch (Exception e) {
+ var exception =
+ new VippsRequestException("Vipps force approve payment request returned unsuccessfully.", e);
+ if (e is HttpRequestException) {
+ try {
+ exception.ErrorResponse =
+ await response.Content.ReadFromJsonAsync<VippsErrorResponse>(cancellationToken: ct);
+ _logger.LogError("ErrorResponse: " + JsonSerializer.Serialize(response.Content));
+ } catch (Exception e1) {
+ _logger.LogError("Unknown ErrorResponse: " + JsonSerializer.Serialize(response.Content));
+ Console.WriteLine(e1);
+ }
+ }
+
+ Console.WriteLine(e);
+ throw exception;
+ }
+ }
+
+ /// <summary>
+ /// This API call allows merchant to get the details of a payment transaction.
+ /// Service call returns detailed transaction history of given payment where events are sorted from newest to oldest for when the transaction occurred.
+ /// </summary>
+ /// <returns></returns>
+ /// <exception cref="HttpRequestException">Throws if the api returns unsuccessfully</exception>
+ public async Task<VippsGetPaymentDetailsResponse> GetPaymentDetailsAsync(
+ string orderId,
+ CancellationToken ct = default
+ ) {
+ if (_client.DefaultRequestHeaders.Authorization?.Parameter.IsNullOrWhiteSpace() ?? true) {
+ var credentials = await GetAuthorizationTokenAsync(false, ct);
+ _client.DefaultRequestHeaders.Authorization =
+ new AuthenticationHeaderValue("Bearer", credentials.AccessToken);
+ }
+
+ var response = await _client.GetAsync("ecomm/v2/payments/" + orderId + "/details", ct);
+
+ try {
+ response.EnsureSuccessStatusCode();
+ _logger.LogDebug("VippsEcommerceService: Sent GetPaymentDetailsRequest");
+ return await
+ response.Content.ReadFromJsonAsync<VippsGetPaymentDetailsResponse>(cancellationToken: ct);
+ } catch (Exception e) {
+ var exception =
+ new VippsRequestException("Vipps get payment detailsG request returned unsuccessfully.", e);
+ if (e is HttpRequestException) {
+ try {
+ exception.ErrorResponse =
+ await response.Content.ReadFromJsonAsync<VippsErrorResponse>(cancellationToken: ct);
+ _logger.LogError("ErrorResponse: " + JsonSerializer.Serialize(response.Content));
+ } catch (Exception e1) {
+ _logger.LogError("Unknown ErrorResponse: " + JsonSerializer.Serialize(response.Content));
+ Console.WriteLine(e1);
+ }
+ }
+
+
+ Console.WriteLine(e);
+ throw exception;
+ }
+ }
+ }
+} \ No newline at end of file