diff --git a/TransactionProcessing.SchedulerService/ScheduledJobDriver/Program.cs b/TransactionProcessing.SchedulerService/ScheduledJobDriver/Program.cs
new file mode 100644
index 0000000..043c223
--- /dev/null
+++ b/TransactionProcessing.SchedulerService/ScheduledJobDriver/Program.cs
@@ -0,0 +1,12 @@
+namespace ScheduledJobDriver
+{
+ using TransactionProcessing.SchedulerService.Jobs;
+
+ internal class Program
+ {
+ static void Main(string[] args)
+ {
+ GenerateTransactionsJob
+ }
+ }
+}
\ No newline at end of file
diff --git a/TransactionProcessing.SchedulerService/ScheduledJobDriver/ScheduledJobDriver.csproj b/TransactionProcessing.SchedulerService/ScheduledJobDriver/ScheduledJobDriver.csproj
new file mode 100644
index 0000000..78daeda
--- /dev/null
+++ b/TransactionProcessing.SchedulerService/ScheduledJobDriver/ScheduledJobDriver.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.Jobs/GenerateTransactions/GenerateTransactionsJob.cs b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.Jobs/GenerateTransactions/GenerateTransactionsJob.cs
index 0580495..a704338 100644
--- a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.Jobs/GenerateTransactions/GenerateTransactionsJob.cs
+++ b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.Jobs/GenerateTransactions/GenerateTransactionsJob.cs
@@ -3,6 +3,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
+ using System.Net.Http;
+ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using EstateManagement.Client;
@@ -38,6 +40,8 @@ public class GenerateTransactionsJob : IJob
///
private IEstateClient EstateClient;
+ private String TestHostApi;
+
///
/// The security service client
///
@@ -88,6 +92,7 @@ public async Task Execute(IJobExecutionContext context)
Guid merchantId = context.MergedJobDataMap.GetGuidValueFromString("MerchantId");
Boolean requireLogon = context.MergedJobDataMap.GetBooleanValueFromString("requireLogon");
String contractsToSkip = context.MergedJobDataMap.GetString("contractsToSkip");
+ this.TestHostApi = context.MergedJobDataMap.GetString("TestHostApi");
this.SecurityServiceClient = this.Bootstrapper.GetService();
this.TransactionProcessorClient = this.Bootstrapper.GetService();
@@ -109,20 +114,20 @@ public async Task Execute(IJobExecutionContext context)
/// The date time.
/// The cancellation token.
///
- private async Task> CreateSaleRequests(String accessToken,
+ private async Task> CreateSaleRequests(String accessToken,
MerchantResponse merchant,
DateTime dateTime,
String contractsToSkip,
CancellationToken cancellationToken)
{
List contracts = await this.EstateClient.GetMerchantContracts(accessToken, merchant.EstateId, merchant.MerchantId, cancellationToken);
-
+
if (String.IsNullOrEmpty(contractsToSkip) == false) {
String[] skipContracts = contractsToSkip.Split('|');
- contracts = contracts.Where(c => skipContracts.Contains(c.Description) == false).ToList();
+ contracts = contracts.Where(c => skipContracts.Contains(c.Description) == true).ToList();
}
- List saleRequests = new List();
+ List<(SaleTransactionRequest request, Decimal amount)> saleRequests = new List<(SaleTransactionRequest request, Decimal amount)>();
Random r = new Random();
Int32 transactionNumber = 1;
@@ -131,46 +136,86 @@ private async Task> CreateSaleRequests(String acces
for (Int32 i = 0; i < numberOfSales; i++)
{
- // Pick a contract
- ContractResponse contract = contracts[r.Next(0, contracts.Count)];
+ var requests = await this.CreateSaleTransactionRequest(merchant, dateTime, contracts, r, transactionNumber);
- // Pick a product
- ContractProduct product = contract.Products[r.Next(0, contract.Products.Count)];
+ saleRequests.AddRange(requests);
+ transactionNumber++;
+ }
- Decimal amount = 0;
- if (product.Value.HasValue)
- {
- amount = product.Value.Value;
- }
- else
- {
- // generate an amount
- amount = r.Next(9, 250);
- }
+ return saleRequests;
+ }
- // Generate the time
- DateTime transactionDateTime = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, 0);
+ private async Task> CreateSaleTransactionRequest(MerchantResponse merchant, DateTime dateTime, List contracts, Random r, Int32 transactionNumber){
+ // Pick a contract
+ ContractResponse contract = contracts[r.Next(0, contracts.Count)];
- // Build the metadata
- Dictionary requestMetaData = new Dictionary();
- requestMetaData.Add("Amount", amount.ToString());
+ // Pick a product
+ ContractProduct product = contract.Products[r.Next(0, contract.Products.Count)];
- var productType = GenerateTransactionsJob.GetProductType(contract.OperatorName);
+ Decimal amount = 0;
+ if (product.Value.HasValue){
+ amount = product.Value.Value;
+ }
+ else{
+ // generate an amount
+ amount = r.Next(9, 250);
+ }
+
+ // Generate the time
+ DateTime transactionDateTime = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, 0);
+
+ // Build the metadata
+ Dictionary requestMetaData = new Dictionary();
+ requestMetaData.Add("Amount", amount.ToString());
+
+ List<(SaleTransactionRequest request, Decimal amount)> requests = new List<(SaleTransactionRequest request, Decimal amount)>();
+
+ ProductType productType = GenerateTransactionsJob.GetProductType(contract.OperatorName);
+ if (productType != ProductType.BillPayment){
String operatorName = GenerateTransactionsJob.GetOperatorName(contract);
- if (productType == ProductType.MobileTopup)
- {
+ if (productType == ProductType.MobileTopup){
requestMetaData.Add("CustomerAccountNumber", "1234567890");
}
- else if (productType == ProductType.Voucher)
- {
+ else if (productType == ProductType.Voucher){
requestMetaData.Add("RecipientMobile", "1234567890");
}
+
+ String deviceIdentifier = merchant.Devices.Single().Value;
+
+ SaleTransactionRequest request = new SaleTransactionRequest{
+ AdditionalTransactionMetadata = requestMetaData,
+ ContractId = contract.ContractId,
+ CustomerEmailAddress = string.Empty,
+ DeviceIdentifier = deviceIdentifier,
+ MerchantId = merchant.MerchantId,
+ EstateId = merchant.EstateId,
+ TransactionType = "Sale",
+ TransactionDateTime = transactionDateTime.AddSeconds(r.Next(0, 59)),
+ TransactionNumber = transactionNumber.ToString(),
+ OperatorIdentifier = contract.OperatorName,
+ ProductId = product.ProductId
+ };
+ requests.Add((request, amount));
+ }
+ else{
+ (Int32 accountNumber, String accountName, Decimal balance) billDetails = await CreateBill(contract, amount, r);
+ if (billDetails == default)
+ return new List<(SaleTransactionRequest request, Decimal amount)>();
+
+ // Create the requests required
String deviceIdentifier = merchant.Devices.Single().Value;
- SaleTransactionRequest request = new SaleTransactionRequest
+
+ // First request is Get Account
+ Dictionary getAccountRequestMetaData = new Dictionary{
+ { "CustomerAccountNumber", billDetails.accountNumber.ToString() },
+ { "PataPawaPostPaidMessageType", "VerifyAccount" }
+ };
+
+ SaleTransactionRequest getAccountRequest = new SaleTransactionRequest
{
- AdditionalTransactionMetadata = requestMetaData,
+ AdditionalTransactionMetadata = getAccountRequestMetaData,
ContractId = contract.ContractId,
CustomerEmailAddress = string.Empty,
DeviceIdentifier = deviceIdentifier,
@@ -183,11 +228,62 @@ private async Task> CreateSaleRequests(String acces
ProductId = product.ProductId
};
- saleRequests.Add(request);
- transactionNumber++;
+ requests.Add((getAccountRequest,0));
+
+ // Second request is Make Payment
+ var makePaymentRequestMetaData = new Dictionary{
+ { "CustomerAccountNumber", billDetails.accountNumber.ToString() },
+ { "CustomerName", billDetails.accountName},
+ { "MobileNumber", "1234567890"},
+ { "Amount", amount.ToString()},
+ { "PataPawaPostPaidMessageType", "ProcessBill" }
+ };
+
+ SaleTransactionRequest makePaymentRequest = new SaleTransactionRequest
+ {
+ AdditionalTransactionMetadata = makePaymentRequestMetaData,
+ ContractId = contract.ContractId,
+ CustomerEmailAddress = string.Empty,
+ DeviceIdentifier = deviceIdentifier,
+ MerchantId = merchant.MerchantId,
+ EstateId = merchant.EstateId,
+ TransactionType = "Sale",
+ TransactionDateTime = transactionDateTime.AddSeconds(r.Next(0, 59)),
+ TransactionNumber = transactionNumber.ToString(),
+ OperatorIdentifier = contract.OperatorName,
+ ProductId = product.ProductId
+ };
+
+ requests.Add((makePaymentRequest, amount));
+
}
- return saleRequests;
+ return requests;
+ }
+
+ private async Task<(Int32 accountNumber, String accountName, Decimal balance)> CreateBill(ContractResponse contract, Decimal amount, Random r)
+ {
+ if (contract.OperatorName == "PataPawa PostPay"){
+
+ Int32 accountNumber = r.Next(1, 100000);
+
+ HttpClient httpClient = new HttpClient();
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, $"{this.TestHostApi}/api/developer/patapawapostpay/createbill");
+ var body = new{
+ due_date = DateTime.Now.AddDays(1),
+ amount = amount * 100,
+ account_number = accountNumber,
+ account_name = "Test Account 1"
+ };
+ request.Content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json");
+ HttpResponseMessage response = await httpClient.SendAsync(request);
+
+ if (response.IsSuccessStatusCode){
+ return (body.account_number, body.account_name, body.amount);
+ }
+ }
+
+ return default;
}
///
@@ -269,11 +365,10 @@ private async Task DoSaleTransaction(String accessToken,
/// The cancellation token.
///
private async Task GenerateTransactions(Guid estateId,
- Guid merchantId,
- Boolean requiresLogon,
- String contractsToSkip,
- CancellationToken cancellationToken)
- {
+ Guid merchantId,
+ Boolean requiresLogon,
+ String contractsToSkip,
+ CancellationToken cancellationToken){
DateTime transactionDate = DateTime.Now;
// get a token
@@ -284,8 +379,7 @@ private async Task GenerateTransactions(Guid estateId,
Int32 transactionCount = 0;
- if (requiresLogon)
- {
+ if (requiresLogon){
// Do a logon transaction for the merchant
await this.DoLogonTransaction(accessToken, merchant, transactionDate, cancellationToken);
@@ -293,21 +387,19 @@ private async Task GenerateTransactions(Guid estateId,
}
// Now generate some sales
- List saleRequests = await this.CreateSaleRequests(accessToken, merchant, transactionDate, contractsToSkip, cancellationToken);
+ List<(SaleTransactionRequest request, Decimal amount)> saleRequests = await this.CreateSaleRequests(accessToken, merchant, transactionDate, contractsToSkip, cancellationToken);
// Work out how much of a deposit the merchant needs (minus 1 sale)
- IEnumerable> metadata = saleRequests.Select(s => s.AdditionalTransactionMetadata);
- List amounts = metadata.Select(m => m["Amount"]).ToList();
+ IEnumerable amountList = saleRequests.Select(s => s.amount).ToList();
- Decimal depositAmount = amounts.TakeLast(amounts.Count - 1).Sum(a => decimal.Parse(a));
+ Decimal depositAmount = amountList.TakeLast(amountList.Count() - 1).Sum(a => a);
await this.MakeMerchantDeposit(accessToken, merchant, depositAmount, transactionDate, cancellationToken);
// Now send the sales
- saleRequests = saleRequests.OrderBy(s => s.TransactionDateTime).ToList();
- foreach (SaleTransactionRequest saleTransactionRequest in saleRequests)
- {
- await this.DoSaleTransaction(accessToken, saleTransactionRequest, cancellationToken);
+ saleRequests = saleRequests.OrderBy(s => s.request.TransactionDateTime).ToList();
+ foreach ((SaleTransactionRequest request, Decimal amount) saleTransactionRequest in saleRequests){
+ await this.DoSaleTransaction(accessToken, saleTransactionRequest.request, cancellationToken);
Console.WriteLine($"Sale sent for Merchant [{merchant.MerchantName}]");
transactionCount++;
}
@@ -351,6 +443,9 @@ private static ProductType GetProductType(String operatorName)
case "Voucher":
productType = ProductType.Voucher;
break;
+ case "PataPawa PostPay":
+ productType = ProductType.BillPayment;
+ break;
}
return productType;
diff --git a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.dll.config b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.dll.config
index 7118483..08f94bf 100644
--- a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.dll.config
+++ b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.dll.config
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/appsettings.json b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/appsettings.json
index 270a6ab..6b8a386 100644
--- a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/appsettings.json
+++ b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/appsettings.json
@@ -7,6 +7,6 @@
}
},
"ConnectionStrings": {
- "SchedulerReadModel": "server=127.0.0.1;user id=sa;password=sp1ttal;database=Scheduler"
+ "SchedulerReadModel": "server=127.0.0.1;user id=sa;password=sp1ttal;database=Scheduler;Encrypt=True;TrustServerCertificate=True"
}
}
diff --git a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/quartz.config b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/quartz.config
index 616509f..fea4ad1 100644
--- a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/quartz.config
+++ b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/quartz.config
@@ -4,9 +4,10 @@ quartz.dataSource.JobStore.connectionStringName = SchedulerReadModel
quartz.jobStore.useProperties = true
quartz.jobStore.tablePrefix = QRTZ_
quartz.jobStore.type = Quartz.Impl.AdoJobStore.JobStoreTX, Quartz
+quartz.jobStore.performSchemaValidation = false
quartz.serializer.type = json
quartz.jobStore.dataSource = JobStore
quartz.jobStore.driverDelegateType = Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz
quartz.plugin.recentHistory.type = Quartz.Plugins.RecentHistory.ExecutionHistoryPlugin, Quartz.Plugins.RecentHistory
quartz.plugin.recentHistory.storeType = Quartz.Plugins.RecentHistory.Impl.InProcExecutionHistoryStore, Quartz.Plugins.RecentHistory
-quartz.plugin.timeZoneConverter.type = Quartz.Plugin.TimeZoneConverter.TimeZoneConverterPlugin, Quartz.Plugins.TimeZoneConverter
+quartz.plugin.timeZoneConverter.type = Quartz.Plugin.TimeZoneConverter.TimeZoneConverterPlugin, Quartz.Plugins.TimeZoneConverter
\ No newline at end of file