diff --git a/.github/workflows/balance-nuget.yml b/.github/workflows/balance-nuget.yml
new file mode 100644
index 0000000..42abd26
--- /dev/null
+++ b/.github/workflows/balance-nuget.yml
@@ -0,0 +1,48 @@
+name: "Deploy to NuGet"
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - main
+
+env:
+ PROJECT_PATH: "src/Balance/SubsBase.SDK.Balance.csproj"
+ PACKAGE_OUTPUT_DIRECTORY: ${{ github.workspace }}/output
+ NUGET_SOURCE_URL: 'https://api.nuget.org/v3/index.json'
+
+jobs:
+ deploy:
+ name: 'Deploy'
+ runs-on: 'ubuntu-latest'
+ steps:
+ - name: 'Checkout'
+ uses: actions/checkout@v2
+
+ - name: 'Install dotnet'
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: '8.x'
+
+ - name: 'Restore packages'
+ run: dotnet restore ${{ env.PROJECT_PATH }}
+
+ - name: 'Build project'
+ run: dotnet build ${{ env.PROJECT_PATH }} --no-restore --configuration Release
+
+ - name: 'Get version'
+ uses: kzrnm/get-net-sdk-project-versions-action@v1.3.0
+ id: get-version
+ with:
+ proj-path: ${{ env.PROJECT_PATH }}
+
+ - name: 'Get package name'
+ id: get-package-name
+ run: echo "::set-output name=package-name::SubsBaseBalance.${{ steps.get-version.outputs.version }}"
+
+ - name: 'Pack project'
+ run: dotnet pack ${{ env.PROJECT_PATH }} --no-restore --no-build --configuration Release --include-symbols -p:PackageVersion=${{ steps.get-version.outputs.version }} --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }}
+
+ - name: 'Push package'
+ run: dotnet nuget push ${{ env.PACKAGE_OUTPUT_DIRECTORY }}/${{ steps.get-package-name.outputs.package-name }}.nupkg -k ${{ secrets.NUGET_AUTH_TOKEN }} -s ${{ env.NUGET_SOURCE_URL }}
+
\ No newline at end of file
diff --git a/src/.idea/.idea.SubsBase.SDK/.idea/vcs.xml b/src/.idea/.idea.SubsBase.SDK/.idea/vcs.xml
index 64713b8..6c0b863 100644
--- a/src/.idea/.idea.SubsBase.SDK/.idea/vcs.xml
+++ b/src/.idea/.idea.SubsBase.SDK/.idea/vcs.xml
@@ -1,7 +1,6 @@
-
\ No newline at end of file
diff --git a/src/Balance/SubsBase.SDK.Balance.Test/BalanceTests.cs b/src/Balance/SubsBase.SDK.Balance.Test/BalanceTests.cs
new file mode 100644
index 0000000..506cd68
--- /dev/null
+++ b/src/Balance/SubsBase.SDK.Balance.Test/BalanceTests.cs
@@ -0,0 +1,315 @@
+using FluentAssertions;
+using SubsBase.SDK.Balance.Contracts;
+
+namespace SubsBase.SDK.Balance.Test;
+
+[NonParallelizable]
+[TestFixture]
+[SingleThreaded]
+public class BalanceTests
+{
+ private static readonly string _publicKey = "publicKey";
+ private static readonly string _privateKey = "privateKey";
+ private static Guid _1StBalanceId;
+ private static Guid _2NdBalanceId;
+ private static string _releasedAmountId;
+ private static string _capturedAmountId;
+ private static BalanceSdk balanceSdk;
+
+ [OneTimeSetUp]
+ public void OneTimeInit()
+ {
+ balanceSdk = new BalanceSdk(_publicKey, _privateKey, environment: Environment.Development);
+ }
+
+ [Test, Order(1)]
+ public void Step_01_AddNewBalanceWithoutMetaData_ShouldCreateNewBalance()
+ {
+ var balance = balanceSdk.Balance.CreateAsync(new BalanceInfoNew()
+ {
+ Unit = "EGP",
+ AllowTotalBalanceToBeNegative = false,
+ Metadata = new Dictionary()
+ {
+ {"customerId" , "subsbase_customer1"},
+ {"age", "25"}
+ },
+ }).GetAwaiter().GetResult();
+
+ balance.IsSuccess.Should().BeTrue();
+ balance.Value.Should().NotBeNull();
+ _1StBalanceId = balance.Value!.BalanceId;
+ }
+
+ [Test, Order(2)]
+ public void Step_02_AddNewBalance_ShouldCreateNewBalance()
+ {
+ var balance = balanceSdk.Balance.CreateAsync(new BalanceInfoNew()
+ {
+ Unit = "EGP",
+ Metadata = new Dictionary()
+ {
+ {"customerId" , "subsbase_customer2"},
+ {"age", "35"}
+ },
+ AllowTotalBalanceToBeNegative = false,
+ }).GetAwaiter().GetResult();
+
+ balance.IsSuccess.Should().BeTrue();
+ balance.Value.Should().NotBeNull();
+ _2NdBalanceId = balance.Value!.BalanceId;
+ }
+
+ [Test, Order(3)]
+ public void Step_03_AddCreditBalanceMovement_ShouldLoadBalanceWithSpecificAmount()
+ {
+ var balance = balanceSdk.BalanceMovement.CreateAsync(new BalanceMovementNew()
+ {
+ BalanceId = _1StBalanceId,
+ Type = MovementType.Credit,
+ Amount = 1000,
+ Description = "Load Balance With 1000 EGP",
+ }).GetAwaiter().GetResult();
+
+ balance.IsSuccess.Should().BeTrue();
+ balance.Value.Should().NotBeNull();
+ balance.Value!.AvailableAmount.Should().Be(1000.0M);
+ balance.Value.TotalAmount.Should().Be(1000.0M);
+ balance.Value.OnHoldAmount.Should().Be(0.0M);
+ }
+
+ [Test, Order(4)]
+ public void Step_04_HoldBalanceAmountToBeCaptured_ShouldHoldAmount()
+ {
+ var balance = balanceSdk.OnHoldAmount.CreateAsync(new HoldAmountNew()
+ {
+ BalanceId = _1StBalanceId,
+ Amount = 1000,
+ Description = "Hold 1000 EGP",
+ ReleaseDate = DateTime.UtcNow.AddSeconds(10)
+ }).GetAwaiter().GetResult();
+
+ balance.IsSuccess.Should().BeTrue();
+ balance.Value.Should().NotBeNull();
+ balance.Value!.OnHoldAmountId.Should().NotBeNullOrWhiteSpace();
+ _capturedAmountId = balance.Value!.OnHoldAmountId;
+ }
+
+ [Test, Order(5)]
+ public void Step_05_CaptureOnHoldAmount_ShouldCaptureOnHoldAmount()
+ {
+ var balance = balanceSdk.OnHoldAmount.DeleteAsync(_capturedAmountId, isCaptured: true).GetAwaiter().GetResult();
+
+ balance.IsSuccess.Should().BeTrue();
+ balance.Value.Should().NotBeNull();
+ balance.Value!.AvailableAmount.Should().Be(0.0M);
+ balance.Value.TotalAmount.Should().Be(0.0M);
+ balance.Value.OnHoldAmount.Should().Be(0.0M);
+ }
+
+ [Test, Order(6)]
+ public void Step_06_AddCreditBalanceMovement_ShouldLoadBalanceWithSpecificAmount()
+ {
+ var balance = balanceSdk.BalanceMovement.CreateAsync(new BalanceMovementNew()
+ {
+ BalanceId = _1StBalanceId,
+ Type = MovementType.Credit,
+ Amount = 1000,
+ Description = "Load Balance With 1000 EGP",
+ }).GetAwaiter().GetResult();
+
+ balance.IsSuccess.Should().BeTrue();
+ balance.Value.Should().NotBeNull();
+ balance.Value!.AvailableAmount.Should().Be(1000.0M);
+ balance.Value.TotalAmount.Should().Be(1000.0M);
+ balance.Value.OnHoldAmount.Should().Be(0.0M);
+ }
+
+
+
+ [Test, Order(7)]
+ public void Step_07_AddDebitBalanceMovementWithAmountLargerThanAvailable_ShouldReturnErrorInsufficientFund()
+ {
+ var balance = balanceSdk.BalanceMovement.CreateAsync(new BalanceMovementNew()
+ {
+ BalanceId = _1StBalanceId,
+ Type = MovementType.Debit,
+ Amount = 1500,
+ Description = "Unload Balance With 1500 EGP",
+ }).GetAwaiter().GetResult();
+
+ balance.IsSuccess.Should().BeFalse();
+ balance.Value.Should().BeNull();
+ balance.Message.Should().NotBeNullOrWhiteSpace();
+ }
+
+ [Test, Order(8)]
+ public void Step_08_AddDebitBalanceMovementWithAmountLessThanAvailable_ShouldUnloadBalanceWithSpecificAmount()
+ {
+ var balance = balanceSdk.BalanceMovement.CreateAsync(new BalanceMovementNew()
+ {
+ BalanceId = _1StBalanceId,
+ Type = MovementType.Debit,
+ Amount = 500,
+ Description = "Unload Balance With 500 EGP",
+ }).GetAwaiter().GetResult();
+
+ balance.IsSuccess.Should().BeTrue();
+ balance.Value.Should().NotBeNull();
+ balance.Value!.AvailableAmount.Should().Be(500.0M);
+ balance.Value!.TotalAmount.Should().Be(500.0M);
+ balance.Value!.OnHoldAmount.Should().Be(0.0M);
+ }
+
+ [Test, Order(9)]
+ public void Step_09_HoldBalanceAmount_ShouldHoldAmount()
+ {
+ var balance = balanceSdk.OnHoldAmount.CreateAsync(new HoldAmountNew()
+ {
+ BalanceId = _1StBalanceId,
+ Amount = 200,
+ Description = "Hold 200 EGP",
+ }).GetAwaiter().GetResult();
+
+ balance.IsSuccess.Should().BeTrue();
+ balance.Value.Should().NotBeNull();
+ balance.Value!.OnHoldAmountId.Should().NotBeNullOrWhiteSpace();
+ _releasedAmountId = balance.Value!.OnHoldAmountId;
+ }
+
+ [Test, Order(10)]
+ public void Step_10_GetOnHoldAmountWithId_ShouldGetOnHoldAmountDetails()
+ {
+ var onHoldAmountResult = balanceSdk.OnHoldAmount.GetAsync(_releasedAmountId).GetAwaiter().GetResult();
+
+ onHoldAmountResult.IsSuccess.Should().BeTrue();
+ onHoldAmountResult.Value.Should().NotBeNull();
+ onHoldAmountResult.Value!.BalanceId.Should().Be(_1StBalanceId);
+ onHoldAmountResult.Value!.Amount.Should().Be(200.0M);
+ }
+
+
+ [Test, Order(10)]
+ public void Step_10_ReleaseOnHoldAmount_ShouldReleaseOnHoldAmount()
+ {
+ var balance = balanceSdk.OnHoldAmount.DeleteAsync(_releasedAmountId, isCaptured: false).GetAwaiter().GetResult();
+
+ balance.IsSuccess.Should().BeTrue();
+ balance.Value.Should().NotBeNull();
+ balance.Value!.AvailableAmount.Should().Be(500.0M);
+ balance.Value!.TotalAmount.Should().Be(500.0M);
+ balance.Value!.OnHoldAmount.Should().Be(0.0M);
+
+ }
+
+ [Test, Order(11)]
+ public void Step_11_HoldBalanceAmountToBeCaptured_ShouldHoldAmount()
+ {
+ var balance = balanceSdk.OnHoldAmount.CreateAsync(new HoldAmountNew()
+ {
+ BalanceId = _1StBalanceId,
+ Amount = 200,
+ Description = "Hold 200 EGP",
+ ReleaseDate = DateTime.UtcNow.AddHours(1)
+ }).GetAwaiter().GetResult();
+
+ balance.IsSuccess.Should().BeTrue();
+ balance.Value.Should().NotBeNull();
+ balance.Value!.OnHoldAmountId.Should().NotBeNullOrWhiteSpace();
+ _capturedAmountId = balance.Value!.OnHoldAmountId;
+ }
+
+ [Test, Order(12)]
+ public void Step_12_CaptureOnHoldAmount_ShouldCaptureOnHoldAmount()
+ {
+ var balance = balanceSdk.OnHoldAmount.DeleteAsync(_capturedAmountId, isCaptured: true).GetAwaiter().GetResult();
+
+ balance.IsSuccess.Should().BeTrue();
+ balance.Value.Should().NotBeNull();
+ balance.Value!.AvailableAmount.Should().Be(300.0M);
+ balance.Value!.TotalAmount.Should().Be(300.0M);
+ balance.Value!.OnHoldAmount.Should().Be(0.0M);
+ }
+
+ [Test, Order(13)]
+ public void Step_13_HoldBalanceAmountWithExpirationDate_ShouldHoldAmount()
+ {
+ var balance = balanceSdk.OnHoldAmount.CreateAsync(new HoldAmountNew()
+ {
+ BalanceId = _1StBalanceId,
+ Amount = 200,
+ Description = "Hold 200 EGP",
+ ReleaseDate = DateTime.UtcNow.AddDays(1)
+ }).GetAwaiter().GetResult();
+
+ balance.IsSuccess.Should().BeTrue();
+ balance.Value.Should().NotBeNull();
+ balance.Value!.OnHoldAmountId.Should().NotBeNullOrWhiteSpace();
+ _releasedAmountId = balance.Value!.OnHoldAmountId;
+
+ var onHoldAmount = balanceSdk.OnHoldAmount.GetAsync(balance.Value!.OnHoldAmountId).GetAwaiter().GetResult();
+ onHoldAmount.IsSuccess.Should().BeTrue();
+ onHoldAmount.Value.Should().NotBeNull();
+ onHoldAmount.Value.Amount.Should().Be(expected: 200.0M);
+ onHoldAmount.Value.ReleaseDate.Should().NotBeNull();
+ onHoldAmount.Value.ReleaseDate.Value.Date.Should().Be(DateTime.UtcNow.AddDays(1).Date);
+ }
+
+
+ [Test, Order(14)]
+ public void Step_14_AddCreditBalanceMovementWithExpirationDate_ShouldLoadBalanceWithSpecificAmount()
+ {
+ var balance = balanceSdk.BalanceMovement.CreateAsync(new BalanceMovementNew()
+ {
+ BalanceId = _1StBalanceId,
+ Type = MovementType.Credit,
+ Amount = 1000,
+ Description = "Load Balance With 1000 EGP",
+ ExpirationDate = DateTime.UtcNow.AddSeconds(5)
+ }).GetAwaiter().GetResult();
+
+ balance.IsSuccess.Should().BeTrue();
+ balance.Value.Should().NotBeNull();
+ balance.Value!.AvailableAmount.Should().Be(1100.0M);
+ balance.Value!.TotalAmount.Should().Be(1300.0M);
+ balance.Value!.OnHoldAmount.Should().Be(200.0M);
+
+ }
+
+ [Test, Order(15)]
+ public void Step_15_GetBalanceDetails_ShouldGetAllBalanceDetails()
+ {
+ Thread.Sleep(20 * 1000);
+ var balance = balanceSdk.Balance.GetAsync(_1StBalanceId).GetAwaiter().GetResult();
+ balance.Should().NotBeNull();
+ balance.IsSuccess.Should().BeTrue();
+ balance.Value!.BalanceSummary.AvailableAmount.Should().Be(100.0M);
+ balance.Value!.BalanceSummary.TotalAmount.Should().Be(300.0M);
+ balance.Value!.BalanceSummary.OnHoldAmount.Should().Be(200.0M);
+ }
+
+ [Test, Order(16)]
+ public void Step_16_GetAllBalancesDetails_ShouldGetAllBalances()
+ {
+ var balance = balanceSdk.Balance.GetAllAsync(new List{_1StBalanceId.ToString(),_2NdBalanceId.ToString()}).GetAwaiter().GetResult();
+ balance.Should().NotBeNull();
+ balance.IsSuccess.Should().BeTrue();
+ balance.Value.Count.Should().Be(expected: 2);
+ balance.Value[0].BalanceId.Should().Be(_1StBalanceId);
+ balance.Value[0].AvailableAmount.Should().BePositive();
+ balance.Value[0].OnHoldAmount.Should().BePositive();
+ balance.Value[0].Unit.Should().NotBeNullOrEmpty();
+ }
+
+ [Test, Order(17)]
+ public void Step_17_GetAllBalancesOnHoldAmountsDetails_ShouldGetAllOnHoldAmountsDetails()
+ {
+ var balance = balanceSdk.OnHoldAmount.GetBalanceOnHoldAmountsAsync(_1StBalanceId).GetAwaiter().GetResult();
+ balance.Should().NotBeNull();
+ balance.IsSuccess.Should().BeTrue();
+ balance.Value.OnHoldAmounts.Should().NotBeEmpty();
+ balance.Value.BalanceId.Should().Be(_1StBalanceId);
+ balance.Value.OnHoldAmounts.Sum(x => x.Amount).Should().Be(200);
+ }
+
+}
\ No newline at end of file
diff --git a/src/Balance/SubsBase.SDK.Balance.Test/SubsBase.SDK.Balance.Test.csproj b/src/Balance/SubsBase.SDK.Balance.Test/SubsBase.SDK.Balance.Test.csproj
new file mode 100644
index 0000000..3acc110
--- /dev/null
+++ b/src/Balance/SubsBase.SDK.Balance.Test/SubsBase.SDK.Balance.Test.csproj
@@ -0,0 +1,30 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Balance/SubsBase.SDK.Balance/BalanceConfiguration.cs b/src/Balance/SubsBase.SDK.Balance/BalanceConfiguration.cs
new file mode 100644
index 0000000..726400d
--- /dev/null
+++ b/src/Balance/SubsBase.SDK.Balance/BalanceConfiguration.cs
@@ -0,0 +1,14 @@
+namespace SubsBase.SDK.Balance;
+
+public class BalanceConfiguration
+{
+ public string PublicKey { get; set; }
+ public string PrivateKey { get; set; }
+
+ public BalanceConfiguration(string publicKey, string privateKey)
+ {
+ PublicKey = publicKey ?? throw new ArgumentNullException(nameof(PublicKey));
+ PrivateKey = privateKey ?? throw new ArgumentNullException(nameof(PrivateKey));
+ }
+
+}
\ No newline at end of file
diff --git a/src/Balance/SubsBase.SDK.Balance/BalanceSdk.cs b/src/Balance/SubsBase.SDK.Balance/BalanceSdk.cs
new file mode 100644
index 0000000..aa81c94
--- /dev/null
+++ b/src/Balance/SubsBase.SDK.Balance/BalanceSdk.cs
@@ -0,0 +1,29 @@
+
+using SubsBase.Common.ApiClientHelper;
+using SubsBase.SDK.Balance.Client;
+
+namespace SubsBase.SDK.Balance;
+
+public class BalanceSdk
+{
+ private readonly BalanceConfiguration _configuration;
+ private readonly IConfigurationConstants _configurationConstants;
+
+ public BalanceSdk(
+ string publicKey,
+ string privateKey,
+ Environment environment = Environment.Development,
+ string? environmentBaseUrl = null
+ )
+ {
+ _configuration = new BalanceConfiguration(publicKey, privateKey);
+ _configurationConstants = environment == Environment.Development
+ ? new DevelopmentConstants(environmentBaseUrl)
+ : new ProductionConstants();
+ }
+
+ public BalanceClient Balance => new BalanceClient(new ApiClient(new HttpClient(), _configurationConstants.BalanceUri), _configuration);
+ public BalanceMovementClient BalanceMovement => new BalanceMovementClient(new ApiClient(new HttpClient(), _configurationConstants.BalanceMovementUri), _configuration);
+ public OnHoldAmountClient OnHoldAmount => new OnHoldAmountClient(new ApiClient(new HttpClient(), _configurationConstants.OnHoldAmountUri), _configuration);
+
+}
\ No newline at end of file
diff --git a/src/Balance/SubsBase.SDK.Balance/Client/BalanceClient.cs b/src/Balance/SubsBase.SDK.Balance/Client/BalanceClient.cs
new file mode 100644
index 0000000..23f7fb6
--- /dev/null
+++ b/src/Balance/SubsBase.SDK.Balance/Client/BalanceClient.cs
@@ -0,0 +1,98 @@
+using System.Text.Json;
+using SubsBase.Common.ApiClientHelper;
+using SubsBase.SDK.Balance.Contracts;
+using SubsBase.SDK.Balance.Services;
+
+namespace SubsBase.SDK.Balance.Client;
+
+public class BalanceClient
+{
+ private readonly ApiClient _apiClient;
+ private readonly SigningService _signingService;
+ private readonly BalanceConfiguration _balanceConfiguration;
+
+ public BalanceClient(ApiClient apiClient, BalanceConfiguration balanceConfiguration)
+ {
+ _apiClient = apiClient;
+ _balanceConfiguration = balanceConfiguration;
+ _signingService = new SigningService();
+ }
+
+ public async Task> CreateAsync(BalanceInfoNew balanceInfoNew)
+ {
+ var signaturePayload = new SortedDictionary()
+ {
+ { "allowTotalBalanceToBeNegative", balanceInfoNew.AllowTotalBalanceToBeNegative },
+ { "metadata", balanceInfoNew.Metadata},
+ { "unit", balanceInfoNew.Unit},
+ };
+
+ return await _apiClient.PostAsync(
+ uri: "",
+ request: balanceInfoNew,
+ headers: new Dictionary()
+ {
+ { "publicKey", _balanceConfiguration.PublicKey },
+ { "signature", _signingService.SignPayload(JsonSerializer.Serialize(signaturePayload), _balanceConfiguration.PrivateKey)}
+ });
+ }
+
+ public async Task>> GetAllAsync(IEnumerable ids)
+ {
+ var signaturePayload = new Dictionary()
+ {
+ { "ids",string.Join(',',ids)}
+ };
+
+ return await _apiClient.GetAsync>(
+ uri: $"?ids={string.Join(',', ids)}",
+ headers: new Dictionary()
+ {
+ { "publicKey", _balanceConfiguration.PublicKey },
+ {
+ "signature", _signingService.SignPayload(JsonSerializer.Serialize(signaturePayload),
+ _balanceConfiguration.PrivateKey)
+ },
+ });
+ }
+
+ public async Task> GetAsync(Guid id)
+ {
+ var signaturePayload = new Dictionary()
+ {
+ { "id", id.ToString()}
+ };
+
+ return await _apiClient.GetAsync(
+ uri: @$"{id}",
+ headers: new Dictionary()
+ {
+ { "publicKey", _balanceConfiguration.PublicKey },
+ {
+ "signature", _signingService.SignPayload(JsonSerializer.Serialize(signaturePayload),
+ _balanceConfiguration.PrivateKey)
+ },
+ });
+ }
+
+ public async Task> UpdateAsync(Guid id, BalanceInfoUpdate balanceInfoToUpdate)
+ {
+ var signaturePayload = new Dictionary()
+ {
+ { "id", id },
+ { "metadata", JsonSerializer.Serialize(balanceInfoToUpdate.Metadata) }
+ };
+
+ return await _apiClient.PutAsync(
+ uri: $"{id}",
+ request: balanceInfoToUpdate,
+ headers: new Dictionary()
+ {
+ { "publicKey", _balanceConfiguration.PublicKey },
+ {
+ "signature", _signingService.SignPayload(JsonSerializer.Serialize(signaturePayload),
+ _balanceConfiguration.PrivateKey)
+ },
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/Balance/SubsBase.SDK.Balance/Client/BalanceMovementClient.cs b/src/Balance/SubsBase.SDK.Balance/Client/BalanceMovementClient.cs
new file mode 100644
index 0000000..776cf48
--- /dev/null
+++ b/src/Balance/SubsBase.SDK.Balance/Client/BalanceMovementClient.cs
@@ -0,0 +1,45 @@
+using System.Text.Json;
+using SubsBase.Common.ApiClientHelper;
+using SubsBase.SDK.Balance.Contracts;
+using SubsBase.SDK.Balance.Services;
+
+namespace SubsBase.SDK.Balance.Client;
+
+public class BalanceMovementClient
+{
+ private readonly ApiClient _apiClient;
+ private readonly SigningService _signingService;
+ private readonly BalanceConfiguration _balanceConfiguration;
+
+ public BalanceMovementClient(ApiClient apiClient, BalanceConfiguration balanceConfiguration)
+ {
+ _signingService = new SigningService();
+ _apiClient = apiClient;
+ _balanceConfiguration = balanceConfiguration;
+ }
+
+ public async Task> CreateAsync(BalanceMovementNew balanceMovementNew)
+ {
+ var signaturePayload = new SortedDictionary()
+ {
+ { "amount", balanceMovementNew.Amount },
+ { "balanceId", balanceMovementNew.BalanceId.ToString() },
+ { "description", balanceMovementNew.Description },
+ { "expirationDate", balanceMovementNew.ExpirationDate ?? null},
+ { "type", balanceMovementNew.Type.ToString() }
+ };
+
+ var result = await _apiClient.PostAsync(
+ uri: "",
+ headers: new Dictionary()
+ {
+ {"publicKey", _balanceConfiguration.PublicKey},
+ {"signature", _signingService.SignPayload(JsonSerializer.Serialize(signaturePayload),_balanceConfiguration.PrivateKey)}
+ },
+ request: balanceMovementNew);
+
+ return result;
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/Balance/SubsBase.SDK.Balance/Client/OnHoldAmountClient.cs b/src/Balance/SubsBase.SDK.Balance/Client/OnHoldAmountClient.cs
new file mode 100644
index 0000000..b030d47
--- /dev/null
+++ b/src/Balance/SubsBase.SDK.Balance/Client/OnHoldAmountClient.cs
@@ -0,0 +1,100 @@
+using System.Text.Json;
+using SubsBase.Common.ApiClientHelper;
+using SubsBase.SDK.Balance.Contracts;
+using SubsBase.SDK.Balance.Services;
+
+namespace SubsBase.SDK.Balance.Client;
+
+public class OnHoldAmountClient
+{
+ private readonly ApiClient _apiClient;
+ private readonly SigningService _signingService;
+ private readonly BalanceConfiguration _balanceConfiguration;
+
+
+ public OnHoldAmountClient(ApiClient apiClient, BalanceConfiguration balanceConfiguration)
+ {
+ _signingService = new SigningService();
+ _apiClient = apiClient;
+ _balanceConfiguration = balanceConfiguration;
+ }
+
+ public async Task> GetAsync(string onHoldAmountId)
+ {
+ var signaturePayload = new Dictionary()
+ {
+ { "id", onHoldAmountId}
+ };
+
+ return await _apiClient.GetAsync(
+ uri: @$"{onHoldAmountId}",
+ headers: new Dictionary()
+ {
+ { "publicKey", _balanceConfiguration.PublicKey },
+ {
+ "signature", _signingService.SignPayload(JsonSerializer.Serialize(signaturePayload),
+ _balanceConfiguration.PrivateKey)
+ },
+ });
+ }
+
+ public async Task> CreateAsync(HoldAmountNew holdAmountNew)
+ {
+ var signaturePayload = new SortedDictionary()
+ {
+ { "balanceId", holdAmountNew.BalanceId },
+ { "amount", holdAmountNew.Amount },
+ { "description", holdAmountNew.Description },
+ { "releaseDate", holdAmountNew.ReleaseDate }
+ };
+
+ var result = await _apiClient.PostAsync(
+ uri: "",
+ request: holdAmountNew,
+ headers: new Dictionary()
+ {
+ { "publicKey", _balanceConfiguration.PublicKey },
+ { "signature", _signingService.SignPayload(JsonSerializer.Serialize(signaturePayload), _balanceConfiguration.PrivateKey) }
+ });
+ return result;
+ }
+
+ public async Task> DeleteAsync(string onHoldAmountId, bool isCaptured = false)
+ {
+ var signaturePayload = new SortedDictionary()
+ {
+ {"onHoldAmountId", onHoldAmountId},
+ {"isCaptured", isCaptured.ToString()}
+ };
+
+ var result = await _apiClient.DeleteAsync(
+ uri: $"?onHoldAmountId={onHoldAmountId}&isCaptured={isCaptured}",
+ headers: new Dictionary()
+ {
+ {"publicKey" , _balanceConfiguration.PublicKey},
+ {"signature", _signingService.SignPayload(JsonSerializer.Serialize(signaturePayload),_balanceConfiguration.PrivateKey)}
+ });
+ return result;
+ }
+
+ public async Task> GetBalanceOnHoldAmountsAsync(Guid balanceId)
+ {
+ var signaturePayload = new Dictionary()
+ {
+ { "balanceId", balanceId.ToString()}
+ };
+
+ return await _apiClient.GetAsync(
+ uri: @$"?balanceId={balanceId}",
+ headers: new Dictionary()
+ {
+ { "publicKey", _balanceConfiguration.PublicKey },
+ {
+ "signature", _signingService.SignPayload(JsonSerializer.Serialize(signaturePayload),
+ _balanceConfiguration.PrivateKey)
+ },
+ });
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/Balance/SubsBase.SDK.Balance/Constants.cs b/src/Balance/SubsBase.SDK.Balance/Constants.cs
new file mode 100644
index 0000000..7cee432
--- /dev/null
+++ b/src/Balance/SubsBase.SDK.Balance/Constants.cs
@@ -0,0 +1,35 @@
+namespace SubsBase.SDK.Balance;
+
+public class IConfigurationConstants
+{
+ public string BalanceUri;
+ public string BalanceMovementUri;
+ public string OnHoldAmountUri;
+}
+
+public class DevelopmentConstants : IConfigurationConstants
+{
+ private const string DefaultBaseUrl = "http://api.dev.subsbase.xyz";
+
+ public DevelopmentConstants(string? baseUrl = null)
+ {
+ if (string.IsNullOrEmpty(baseUrl))
+ {
+ baseUrl = DefaultBaseUrl;
+ }
+
+ BalanceUri = $"{baseUrl}/balance/";
+ BalanceMovementUri = $"{baseUrl}/balance/balance-movement/";
+ OnHoldAmountUri = $"{baseUrl}/balance/on-hold-amount/";
+ }
+}
+
+public class ProductionConstants : IConfigurationConstants
+{
+ public ProductionConstants()
+ {
+ BalanceUri = "http://api.subsbase.io/balance/";
+ BalanceMovementUri = "http://api.subsbase.io/balance/balance-movement/";
+ OnHoldAmountUri = "http://api.subsbase.io/balance/on-hold-amount/";
+ }
+}
\ No newline at end of file
diff --git a/src/Balance/SubsBase.SDK.Balance/Contracts/BalanceDetails.cs b/src/Balance/SubsBase.SDK.Balance/Contracts/BalanceDetails.cs
new file mode 100644
index 0000000..7affe0c
--- /dev/null
+++ b/src/Balance/SubsBase.SDK.Balance/Contracts/BalanceDetails.cs
@@ -0,0 +1,31 @@
+using System.Text.Json.Serialization;
+
+namespace SubsBase.SDK.Balance.Contracts;
+
+public class BalanceDetails
+{
+ [JsonPropertyName("balanceId")] public Guid BalanceId { get; set; }
+ [JsonPropertyName("metadata")] public Dictionary? Metadata { get; set; }
+ [JsonPropertyName("unit")] public string Unit { get; set; }
+ [JsonPropertyName("allowTotalBalanceToBeNegative")] public bool AllowTotalBalanceToBeNegative { get; set; } = false;
+ [JsonPropertyName("balanceSummary")] public BalanceSummary BalanceSummary { get; set; } = new();
+ [JsonPropertyName("movements")] public List? Movements { get; set; }
+ [JsonPropertyName("onHoldAmounts")] public List? OnHoldAmounts { get; set; }
+}
+
+public class BalanceMovement
+{
+ [JsonPropertyName("utcTimestamp")] public DateTime UtcTimestamp { get; set; }
+ [JsonPropertyName("amount")] public decimal Amount { get; set; }
+ [JsonPropertyName("description")] public string Description { get; set; } = string.Empty;
+}
+
+public class OnHoldAmount
+{
+ [JsonPropertyName("id")] public string Id { get; set; } = string.Empty;
+ [JsonPropertyName("balanceId")] public Guid BalanceId { get; set; }
+ [JsonPropertyName("amount")] public decimal Amount { get; set; }
+ [JsonPropertyName("onHoldDate")] public DateTime OnHoldDate { get; set; }
+ [JsonPropertyName("releaseDate")] public DateTime? ReleaseDate { get; set; }
+ [JsonPropertyName("description")] public string Description { get; set; } = string.Empty;
+}
\ No newline at end of file
diff --git a/src/Balance/SubsBase.SDK.Balance/Contracts/BalanceHold.cs b/src/Balance/SubsBase.SDK.Balance/Contracts/BalanceHold.cs
new file mode 100644
index 0000000..9553250
--- /dev/null
+++ b/src/Balance/SubsBase.SDK.Balance/Contracts/BalanceHold.cs
@@ -0,0 +1,22 @@
+using System.Text.Json.Serialization;
+
+namespace SubsBase.SDK.Balance.Contracts;
+
+public class HoldAmountNew
+{
+ [JsonPropertyName("balanceId")] public Guid BalanceId { get; set; }
+ [JsonPropertyName("amount")] public decimal Amount { get; set; }
+ [JsonPropertyName("description")] public string Description { get; set; } = string.Empty;
+ [JsonPropertyName("releaseDate")] public DateTime? ReleaseDate { get; set; }
+}
+
+public class HoldAmountResponse
+{
+ [JsonPropertyName("onHoldAmountId")] public string OnHoldAmountId { get; set; }
+}
+
+public class OnHoldAmountDetails
+{
+ [JsonPropertyName("balanceId")] public Guid BalanceId { get; set; }
+ [JsonPropertyName("onHoldAmounts")] public List OnHoldAmounts { get; set; }
+}
\ No newline at end of file
diff --git a/src/Balance/SubsBase.SDK.Balance/Contracts/BalanceInfo.cs b/src/Balance/SubsBase.SDK.Balance/Contracts/BalanceInfo.cs
new file mode 100644
index 0000000..044013d
--- /dev/null
+++ b/src/Balance/SubsBase.SDK.Balance/Contracts/BalanceInfo.cs
@@ -0,0 +1,25 @@
+using System.Text.Json.Serialization;
+
+namespace SubsBase.SDK.Balance.Contracts;
+
+public class BalanceInfoNew
+{
+ [JsonPropertyName("allowTotalBalanceToBeNegative")] public bool AllowTotalBalanceToBeNegative { get; set; } = false;
+ [JsonPropertyName("metadata")] public Dictionary? Metadata { get; set; }
+ [JsonPropertyName("unit")] public string Unit { get; set; } = string.Empty;
+}
+
+
+public class BalanceInfoUpdate
+{
+ [JsonPropertyName("metadata")] public Dictionary? Metadata { get; set; }
+}
+
+public class BalanceSummary
+{
+ [JsonPropertyName("balanceId")] public Guid BalanceId { get; set; }
+ [JsonPropertyName("unit")] public string Unit { get; set; }
+ [JsonPropertyName("totalAmount")] public decimal TotalAmount { get; set; }
+ [JsonPropertyName("onHoldAmount")] public decimal OnHoldAmount { get; set; }
+ [JsonPropertyName("availableAmount")] public decimal AvailableAmount { get; set; }
+}
\ No newline at end of file
diff --git a/src/Balance/SubsBase.SDK.Balance/Contracts/BalanceMovement.cs b/src/Balance/SubsBase.SDK.Balance/Contracts/BalanceMovement.cs
new file mode 100644
index 0000000..955ad6b
--- /dev/null
+++ b/src/Balance/SubsBase.SDK.Balance/Contracts/BalanceMovement.cs
@@ -0,0 +1,18 @@
+using System.Text.Json.Serialization;
+
+namespace SubsBase.SDK.Balance.Contracts;
+
+public enum MovementType
+{
+ Debit,
+ Credit
+}
+
+public class BalanceMovementNew
+{
+ [JsonPropertyName("balanceId")] public Guid BalanceId { get; set; }
+ [JsonPropertyName("type")] [JsonConverter(typeof(JsonStringEnumConverter))] public MovementType Type { get; set; }
+ [JsonPropertyName("amount")] public decimal Amount { get; set; }
+ [JsonPropertyName("expirationDate") ] public DateTime? ExpirationDate { get; set; }
+ [JsonPropertyName("description")] public string Description { get; set; } = string.Empty;
+}
\ No newline at end of file
diff --git a/src/Balance/SubsBase.SDK.Balance/Environment.cs b/src/Balance/SubsBase.SDK.Balance/Environment.cs
new file mode 100644
index 0000000..e5ed6a0
--- /dev/null
+++ b/src/Balance/SubsBase.SDK.Balance/Environment.cs
@@ -0,0 +1,7 @@
+namespace SubsBase.SDK.Balance;
+
+public enum Environment
+{
+ Development,
+ Production
+}
\ No newline at end of file
diff --git a/src/Balance/SubsBase.SDK.Balance/Install.cs b/src/Balance/SubsBase.SDK.Balance/Install.cs
new file mode 100644
index 0000000..1991a74
--- /dev/null
+++ b/src/Balance/SubsBase.SDK.Balance/Install.cs
@@ -0,0 +1,17 @@
+using Microsoft.Extensions.DependencyInjection;
+using SubsBase.SDK.Balance.Client;
+
+namespace SubsBase.SDK.Balance;
+
+public static class Install
+{
+
+ public static IServiceCollection AddBalanceSdk(this IServiceCollection services, string publicKey, string privateKey, Environment environment, string? testingEnvironmentUrl = null)
+ {
+ return services
+ .AddSingleton( provider => new BalanceSdk(publicKey, privateKey, environment, testingEnvironmentUrl))
+ .AddSingleton( provider => provider.GetRequiredService().Balance)
+ .AddSingleton( provider => provider.GetRequiredService().BalanceMovement)
+ .AddSingleton( provider => provider.GetRequiredService().OnHoldAmount);
+ }
+}
\ No newline at end of file
diff --git a/src/Balance/SubsBase.SDK.Balance/Services/SigningService.cs b/src/Balance/SubsBase.SDK.Balance/Services/SigningService.cs
new file mode 100644
index 0000000..1dc0a35
--- /dev/null
+++ b/src/Balance/SubsBase.SDK.Balance/Services/SigningService.cs
@@ -0,0 +1,15 @@
+using System.Security.Cryptography;
+using System.Text;
+
+namespace SubsBase.SDK.Balance.Services;
+
+public class SigningService
+{
+ public string SignPayload(string payload, string secret)
+ {
+ using var hasher = new HMACSHA256(Encoding.ASCII.GetBytes(secret));
+ var computedHashBytes = hasher.ComputeHash(Encoding.UTF8.GetBytes(payload));
+ var computedHash = string.Join("", computedHashBytes.Select(b => b.ToString("x2")));
+ return computedHash;
+ }
+}
\ No newline at end of file
diff --git a/src/Balance/SubsBase.SDK.Balance/SubsBase.SDK.Balance.csproj b/src/Balance/SubsBase.SDK.Balance/SubsBase.SDK.Balance.csproj
new file mode 100644
index 0000000..bca4a50
--- /dev/null
+++ b/src/Balance/SubsBase.SDK.Balance/SubsBase.SDK.Balance.csproj
@@ -0,0 +1,47 @@
+
+
+
+ net8.0
+ enable
+ enable
+ SubsBase
+ true
+ true
+ true
+ 1.5.1
+ 12
+ Subsbase Balance SDK
+ Subsbase
+ https://github.com/subsbase/balance-service
+ Private
+
+
+
+
+ bin\Debug\
+ true
+
+
+
+
+
+
+
+ $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage
+
+
+
+
+
+ <_ReferenceCopyLocalPaths Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')->WithMetadataValue('PrivateAssets', 'All'))" />
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Balance/SubsBase.SDK.Balance/SubsBase.SDK.Balance.csproj.nuspec b/src/Balance/SubsBase.SDK.Balance/SubsBase.SDK.Balance.csproj.nuspec
new file mode 100644
index 0000000..310192e
--- /dev/null
+++ b/src/Balance/SubsBase.SDK.Balance/SubsBase.SDK.Balance.csproj.nuspec
@@ -0,0 +1,17 @@
+
+
+
+ SubsBase.SDK.Balance.csproj
+ 1.5.1
+ Subsbase
+ false
+ MIT
+
+ Package description
+ Summary of changes made in this release of the package.
+ $copyright$
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Shared/SubsBase.Common.ApiClientHelper/ApiClient.cs b/src/Shared/SubsBase.Common.ApiClientHelper/ApiClient.cs
new file mode 100644
index 0000000..99ddb06
--- /dev/null
+++ b/src/Shared/SubsBase.Common.ApiClientHelper/ApiClient.cs
@@ -0,0 +1,91 @@
+using System.Text;
+using System.Text.Json;
+
+namespace SubsBase.Common.ApiClientHelper;
+
+public partial class ApiClient
+{
+ private readonly HttpClient _httpClient;
+
+ public ApiClient(HttpClient httpClient, string baseAddress)
+ {
+ _httpClient = httpClient;
+ _httpClient.BaseAddress = new Uri(baseAddress);
+ }
+
+ public async Task> ResponseAsync(
+ string uri,
+ HttpMethod httpMethod,
+ TRequest? request = null,
+ string? mediaType = null,
+ Encoding? encoding = null,
+ Dictionary? headers = null,
+ JsonSerializerOptions? jsonSerializerOptions = null)
+ where TResult : class
+ where TRequest : class
+ {
+ mediaType ??= "application/json";
+ encoding ??= Encoding.UTF8;
+
+ var content = request != null ? JsonSerializer.Serialize(request, jsonSerializerOptions) : "";
+
+ var httpRequest = new HttpRequestMessage(httpMethod, uri);
+ httpRequest.Content = new StringContent(content, encoding, mediaType);
+
+ if (headers != null)
+ {
+ foreach (var header in headers)
+ {
+ if (_httpClient.DefaultRequestHeaders.Contains(header.Key))
+ {
+ _httpClient.DefaultRequestHeaders.Remove(header.Key);
+ }
+
+ _httpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
+ }
+ }
+
+ var httpResponseMessage = await _httpClient.SendAsync(httpRequest);
+
+ var stringResponse = await httpResponseMessage.Content.ReadAsStringAsync();
+
+ if (!httpResponseMessage.IsSuccessStatusCode) return Result.Fail(stringResponse);
+
+ var result = JsonSerializer.Deserialize(stringResponse, jsonSerializerOptions);
+
+ return Result.Ok(result);
+ }
+
+ public async Task> ResponseAsync(
+ string uri,
+ HttpMethod httpMethod,
+ Dictionary? headers = null,
+ JsonSerializerOptions? jsonSerializerOptions = null)
+ where TResult : class
+ {
+ var httpRequest = new HttpRequestMessage(httpMethod, uri);
+
+ if (headers != null)
+ {
+ foreach (var header in headers)
+ {
+ if (_httpClient.DefaultRequestHeaders.Contains(header.Key))
+ {
+ _httpClient.DefaultRequestHeaders.Remove(header.Key);
+ }
+
+ _httpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
+ }
+ }
+
+ var httpResponseMessage = await _httpClient.SendAsync(httpRequest);
+
+ var stringResponse = await httpResponseMessage.Content.ReadAsStringAsync();
+
+ if (!httpResponseMessage.IsSuccessStatusCode) return Result.Fail(stringResponse);
+
+ var result = JsonSerializer.Deserialize(stringResponse, jsonSerializerOptions);
+
+ return Result.Ok(result);
+ }
+}
\ No newline at end of file
diff --git a/src/Shared/SubsBase.Common.ApiClientHelper/DeleteHttpMethod.cs b/src/Shared/SubsBase.Common.ApiClientHelper/DeleteHttpMethod.cs
new file mode 100644
index 0000000..a73dc46
--- /dev/null
+++ b/src/Shared/SubsBase.Common.ApiClientHelper/DeleteHttpMethod.cs
@@ -0,0 +1,40 @@
+using System.Text;
+using System.Text.Json;
+
+namespace SubsBase.Common.ApiClientHelper;
+
+public partial class ApiClient
+{
+ public async Task> DeleteAsync(
+ string uri,
+ TRequest? request = null,
+ string? mediaType = null,
+ Encoding? encoding = null,
+ Dictionary? headers = null,
+ JsonSerializerOptions? jsonSerializerOptions = null)
+ where TResult : class
+ where TRequest : class
+ {
+ return await ResponseAsync(
+ uri,
+ httpMethod: HttpMethod.Delete,
+ request,
+ mediaType,
+ encoding,
+ headers,
+ jsonSerializerOptions);
+ }
+
+ public async Task> DeleteAsync(
+ string uri,
+ Dictionary? headers = null,
+ JsonSerializerOptions? jsonSerializerOptions = null)
+ where TResult : class
+ {
+ return await ResponseAsync(
+ uri,
+ httpMethod: HttpMethod.Delete,
+ headers,
+ jsonSerializerOptions);
+ }
+}
\ No newline at end of file
diff --git a/src/Shared/SubsBase.Common.ApiClientHelper/GetHttpMethod.cs b/src/Shared/SubsBase.Common.ApiClientHelper/GetHttpMethod.cs
new file mode 100644
index 0000000..ac0997e
--- /dev/null
+++ b/src/Shared/SubsBase.Common.ApiClientHelper/GetHttpMethod.cs
@@ -0,0 +1,40 @@
+using System.Text;
+using System.Text.Json;
+
+namespace SubsBase.Common.ApiClientHelper;
+
+public partial class ApiClient
+{
+ public async Task> GetAsync(
+ string uri,
+ TRequest? request = null,
+ string? mediaType = null,
+ Encoding? encoding = null,
+ Dictionary? headers = null,
+ JsonSerializerOptions? jsonSerializerOptions = null)
+ where TResult : class
+ where TRequest : class
+ {
+ return await ResponseAsync(
+ uri,
+ httpMethod: HttpMethod.Get,
+ request,
+ mediaType,
+ encoding,
+ headers,
+ jsonSerializerOptions);
+ }
+
+ public async Task> GetAsync(
+ string uri,
+ Dictionary? headers = null,
+ JsonSerializerOptions? jsonSerializerOptions = null)
+ where TResult : class
+ {
+ return await ResponseAsync(
+ uri,
+ httpMethod: HttpMethod.Get,
+ headers,
+ jsonSerializerOptions);
+ }
+}
\ No newline at end of file
diff --git a/src/Shared/SubsBase.Common.ApiClientHelper/PostHttpMethod.cs b/src/Shared/SubsBase.Common.ApiClientHelper/PostHttpMethod.cs
new file mode 100644
index 0000000..4c3f843
--- /dev/null
+++ b/src/Shared/SubsBase.Common.ApiClientHelper/PostHttpMethod.cs
@@ -0,0 +1,40 @@
+using System.Text;
+using System.Text.Json;
+
+namespace SubsBase.Common.ApiClientHelper;
+
+public partial class ApiClient
+{
+ public async Task> PostAsync(
+ string uri,
+ TRequest? request = null,
+ string? mediaType = null,
+ Encoding? encoding = null,
+ Dictionary? headers = null,
+ JsonSerializerOptions? jsonSerializerOptions = null)
+ where TResult : class
+ where TRequest : class
+ {
+ return await ResponseAsync(
+ uri,
+ httpMethod: HttpMethod.Post,
+ request,
+ mediaType,
+ encoding,
+ headers,
+ jsonSerializerOptions);
+ }
+
+ public async Task> PostAsync(
+ string uri,
+ Dictionary? headers = null,
+ JsonSerializerOptions? jsonSerializerOptions = null)
+ where TResult : class
+ {
+ return await ResponseAsync(
+ uri,
+ httpMethod: HttpMethod.Post,
+ headers,
+ jsonSerializerOptions);
+ }
+}
\ No newline at end of file
diff --git a/src/Shared/SubsBase.Common.ApiClientHelper/PutHttpMethod.cs b/src/Shared/SubsBase.Common.ApiClientHelper/PutHttpMethod.cs
new file mode 100644
index 0000000..6fffcae
--- /dev/null
+++ b/src/Shared/SubsBase.Common.ApiClientHelper/PutHttpMethod.cs
@@ -0,0 +1,40 @@
+using System.Text;
+using System.Text.Json;
+
+namespace SubsBase.Common.ApiClientHelper;
+
+public partial class ApiClient
+{
+ public async Task> PutAsync(
+ string uri,
+ TRequest? request = null,
+ string? mediaType = null,
+ Encoding? encoding = null,
+ Dictionary? headers = null,
+ JsonSerializerOptions? jsonSerializerOptions = null)
+ where TResult : class
+ where TRequest : class
+ {
+ return await ResponseAsync(
+ uri,
+ httpMethod: HttpMethod.Put,
+ request,
+ mediaType,
+ encoding,
+ headers,
+ jsonSerializerOptions);
+ }
+
+ public async Task> PutAsync(
+ string uri,
+ Dictionary? headers = null,
+ JsonSerializerOptions? jsonSerializerOptions = null)
+ where TResult : class
+ {
+ return await ResponseAsync(
+ uri,
+ httpMethod: HttpMethod.Put,
+ headers,
+ jsonSerializerOptions);
+ }
+}
\ No newline at end of file
diff --git a/src/Shared/SubsBase.Common.ApiClientHelper/Result.cs b/src/Shared/SubsBase.Common.ApiClientHelper/Result.cs
new file mode 100644
index 0000000..c97083a
--- /dev/null
+++ b/src/Shared/SubsBase.Common.ApiClientHelper/Result.cs
@@ -0,0 +1,53 @@
+namespace SubsBase.Common.ApiClientHelper;
+
+public class Result
+{
+ public bool IsFailed { get; set; }
+ public bool IsSuccess { get; set; }
+ public string? Message { get; set; }
+
+ public static Result Fail(string message)
+ {
+ return new Result
+ {
+ IsFailed = true,
+ Message = message
+ };
+ }
+
+ public static Result Ok()
+ {
+ return new Result
+ {
+ IsSuccess = true
+ };
+ }
+
+ public static Result Ok(T result) where T : class
+ {
+ return new Result
+ {
+ IsSuccess = true,
+ Value = result
+ };
+ }
+
+ public static Result Fail(string message) where T : class
+ {
+ return new Result
+ {
+ IsFailed = true,
+ Message = message
+ };
+ }
+}
+
+
+public class Result
+{
+ public bool IsFailed { get; set; }
+ public bool IsSuccess { get; set; }
+ public string? Message { get; set; }
+ public T? Value { get; set; }
+
+}
\ No newline at end of file
diff --git a/src/Shared/SubsBase.Common.ApiClientHelper/SubsBase.Common.ApiClientHelper.csproj b/src/Shared/SubsBase.Common.ApiClientHelper/SubsBase.Common.ApiClientHelper.csproj
new file mode 100644
index 0000000..8ac9e47
--- /dev/null
+++ b/src/Shared/SubsBase.Common.ApiClientHelper/SubsBase.Common.ApiClientHelper.csproj
@@ -0,0 +1,29 @@
+
+
+
+ net8.0
+ enable
+ enable
+ 12
+
+
+
+
+
+
+
+
+ ApiClient.cs
+
+
+ ApiClient.cs
+
+
+ ApiClient.cs
+
+
+ ApiClient.cs
+
+
+
+
diff --git a/src/SubsBase.SDK.Authentication/SubsBase.SDK.Authentication.csproj b/src/SubsBase.SDK.Authentication/SubsBase.SDK.Authentication.csproj
index 58df15d..80ba924 100644
--- a/src/SubsBase.SDK.Authentication/SubsBase.SDK.Authentication.csproj
+++ b/src/SubsBase.SDK.Authentication/SubsBase.SDK.Authentication.csproj
@@ -1,10 +1,10 @@
- netstandard2.0;net462;net5.0;net6.0
+ net8.0
enable
enable
- 10
+ 12
diff --git a/src/SubsBase.SDK.Common/SubsBase.SDK.Common.csproj b/src/SubsBase.SDK.Common/SubsBase.SDK.Common.csproj
index 564ad21..92a2289 100644
--- a/src/SubsBase.SDK.Common/SubsBase.SDK.Common.csproj
+++ b/src/SubsBase.SDK.Common/SubsBase.SDK.Common.csproj
@@ -1,10 +1,10 @@
- netstandard2.0;net462;net5.0;net6.0
+ net8.0
enable
enable
- 10
+ 12
diff --git a/src/SubsBase.SDK.Subscription/SubsBase.SDK.Subscription.csproj b/src/SubsBase.SDK.Subscription/SubsBase.SDK.Subscription.csproj
index f13bcce..d634bbe 100644
--- a/src/SubsBase.SDK.Subscription/SubsBase.SDK.Subscription.csproj
+++ b/src/SubsBase.SDK.Subscription/SubsBase.SDK.Subscription.csproj
@@ -1,10 +1,10 @@
- netstandard2.0;net462;net5.0;net6.0
+ net8.0
enable
enable
- 10
+ 12
diff --git a/src/SubsBase.SDK.sln b/src/SubsBase.SDK.sln
index b08fb98..31b05ed 100644
--- a/src/SubsBase.SDK.sln
+++ b/src/SubsBase.SDK.sln
@@ -8,6 +8,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubsBase.SDK", "SubsBase.SD
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubsBase.SDK.Authentication", "SubsBase.SDK.Authentication\SubsBase.SDK.Authentication.csproj", "{40599CF0-FFB2-4F95-B67F-CBA1955CDBBC}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{9D4D7C57-3397-45AE-AB3B-566FA31DC7BF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubsBase.Common.ApiClientHelper", "Shared\SubsBase.Common.ApiClientHelper\SubsBase.Common.ApiClientHelper.csproj", "{C2C2B932-89CE-4D7F-9F34-AD2DCDD7ABFB}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Balance", "Balance", "{F8A7443A-C083-4C7C-A1AE-EF4572F36FFA}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubsBase.SDK.Balance.Test", "Balance\SubsBase.SDK.Balance.Test\SubsBase.SDK.Balance.Test.csproj", "{4EB52FBE-B014-4475-876C-CEF550FDF6A2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubsBase.SDK.Balance", "Balance\SubsBase.SDK.Balance\SubsBase.SDK.Balance.csproj", "{F808184E-AA25-4FF4-B703-FB2F19172468}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -30,5 +40,22 @@ Global
{40599CF0-FFB2-4F95-B67F-CBA1955CDBBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{40599CF0-FFB2-4F95-B67F-CBA1955CDBBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{40599CF0-FFB2-4F95-B67F-CBA1955CDBBC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C2C2B932-89CE-4D7F-9F34-AD2DCDD7ABFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C2C2B932-89CE-4D7F-9F34-AD2DCDD7ABFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C2C2B932-89CE-4D7F-9F34-AD2DCDD7ABFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C2C2B932-89CE-4D7F-9F34-AD2DCDD7ABFB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4EB52FBE-B014-4475-876C-CEF550FDF6A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4EB52FBE-B014-4475-876C-CEF550FDF6A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4EB52FBE-B014-4475-876C-CEF550FDF6A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4EB52FBE-B014-4475-876C-CEF550FDF6A2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F808184E-AA25-4FF4-B703-FB2F19172468}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F808184E-AA25-4FF4-B703-FB2F19172468}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F808184E-AA25-4FF4-B703-FB2F19172468}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F808184E-AA25-4FF4-B703-FB2F19172468}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {C2C2B932-89CE-4D7F-9F34-AD2DCDD7ABFB} = {9D4D7C57-3397-45AE-AB3B-566FA31DC7BF}
+ {4EB52FBE-B014-4475-876C-CEF550FDF6A2} = {F8A7443A-C083-4C7C-A1AE-EF4572F36FFA}
+ {F808184E-AA25-4FF4-B703-FB2F19172468} = {F8A7443A-C083-4C7C-A1AE-EF4572F36FFA}
EndGlobalSection
EndGlobal
diff --git a/src/SubsBase.SDK/SubsBase.SDK.csproj b/src/SubsBase.SDK/SubsBase.SDK.csproj
index 1989c2d..1f2953e 100644
--- a/src/SubsBase.SDK/SubsBase.SDK.csproj
+++ b/src/SubsBase.SDK/SubsBase.SDK.csproj
@@ -1,17 +1,18 @@
- netstandard2.0;net462;net5.0;net6.0
+ net8.0
enable
enable
- 10
+ 12
1.0.2
-
-
-
+
+
+
+