Skip to content

Commit 6ed24e9

Browse files
Merge pull request #527 from TransactionProcessing/task/#311_supportpatapawaprepay
Support PataPawa PrePay
2 parents d303fc4 + 59c07d0 commit 6ed24e9

File tree

18 files changed

+1107
-417
lines changed

18 files changed

+1107
-417
lines changed

TransactionProcessor.BusinessLogic/OperatorInterfaces/PataPawaPostPay/PataPawaPostPayProxy.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ public PataPawaPostPayProxy(PataPawaPostPayServiceClient serviceClient,
4242

4343
public async Task<OperatorResponse> ProcessLogonMessage(String accessToken,
4444
CancellationToken cancellationToken) {
45+
46+
// Check if we need to do a logon with the operator
47+
OperatorResponse operatorResponse = this.MemoryCache.Get<OperatorResponse>("PataPawaPostPayLogon");
48+
if (operatorResponse != null){
49+
return operatorResponse;
50+
}
51+
4552
IPataPawaPostPayService channel = this.ChannelResolver(this.ServiceClient, "PataPawaPostPay", this.Configuration.Url);
4653
login logonResponse = await channel.getLoginRequestAsync(this.Configuration.Username, this.Configuration.Password);
4754
if (logonResponse.status != 0) {
@@ -53,7 +60,7 @@ public async Task<OperatorResponse> ProcessLogonMessage(String accessToken,
5360
};
5461
}
5562

56-
OperatorResponse operatorResponse = new OperatorResponse {
63+
operatorResponse = new OperatorResponse {
5764
IsSuccessful = true,
5865
ResponseCode = "0000",
5966
ResponseMessage = logonResponse.message,
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace TransactionProcessor.BusinessLogic.OperatorInterfaces.PataPawaPrePay;
2+
3+
using System;
4+
5+
public class LogonResponse{
6+
public String Balance{ get; set; }
7+
public String Key{ get; set; }
8+
public String Msg{ get; set; }
9+
public Int32 Status{ get; set; }
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace TransactionProcessor.BusinessLogic.OperatorInterfaces.PataPawaPrePay;
2+
3+
using System;
4+
5+
public class MeterResponse{
6+
public String Code { get; set; }
7+
public String CustomerName { get; set; }
8+
public String Msg { get; set; }
9+
public Int32 Status { get; set; }
10+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace TransactionProcessor.BusinessLogic.OperatorInterfaces.PataPawaPrePay;
2+
3+
using System;
4+
using System.Diagnostics.CodeAnalysis;
5+
using SafaricomPinless;
6+
7+
[ExcludeFromCodeCoverage]
8+
public class PataPawaPrePaidConfiguration : BaseOperatorConfiguration
9+
{
10+
public String Username { get; set; }
11+
public String Password { get; set; }
12+
public String Url { get; set; }
13+
}
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
namespace TransactionProcessor.BusinessLogic.OperatorInterfaces.PataPawaPrePay;
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Net.Http;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Common;
9+
using EstateManagement.DataTransferObjects.Responses;
10+
using Microsoft.Extensions.Caching.Memory;
11+
using Newtonsoft.Json;
12+
using Shared.Logger;
13+
14+
public class PataPawaPrePayProxy : IOperatorProxy{
15+
private readonly PataPawaPrePaidConfiguration Configuration;
16+
17+
private readonly HttpClient HttpClient;
18+
19+
private readonly IMemoryCache MemoryCache;
20+
21+
public PataPawaPrePayProxy(PataPawaPrePaidConfiguration configuration,
22+
HttpClient httpClient,
23+
IMemoryCache memoryCache){
24+
this.Configuration = configuration;
25+
this.HttpClient = httpClient;
26+
this.MemoryCache = memoryCache;
27+
}
28+
29+
public async Task<OperatorResponse> ProcessLogonMessage(String accessToken, CancellationToken cancellationToken){
30+
// Check if we need to do a logon with the operator
31+
OperatorResponse operatorResponse = this.MemoryCache.Get<OperatorResponse>("PataPawaPrePayLogon");
32+
if (operatorResponse != null){
33+
return operatorResponse;
34+
}
35+
36+
HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, this.Configuration.Url);
37+
MultipartFormDataContent content = new MultipartFormDataContent();
38+
content.Add(new StringContent("login"), "request");
39+
content.Add(new StringContent(this.Configuration.Username), "username");
40+
content.Add(new StringContent(this.Configuration.Password), "password");
41+
requestMessage.Content = content;
42+
43+
HttpResponseMessage responseMessage = await this.HttpClient.SendAsync(requestMessage, cancellationToken);
44+
45+
// Check the send was successful
46+
if (responseMessage.IsSuccessStatusCode == false){
47+
throw new Exception($"Error sending logon request to Patapawa. Status Code [{responseMessage.StatusCode}]");
48+
}
49+
50+
// Get the response
51+
String responseContent = await responseMessage.Content.ReadAsStringAsync(cancellationToken);
52+
53+
Logger.LogInformation($"Received response message from Patapawa [{responseContent}]");
54+
55+
return this.CreateFromLogon(responseContent);
56+
}
57+
58+
public async Task<OperatorResponse> ProcessSaleMessage(String accessToken, Guid transactionId, String operatorIdentifier, MerchantResponse merchant, DateTime transactionDateTime, String transactionReference, Dictionary<String, String> additionalTransactionMetadata, CancellationToken cancellationToken){
59+
// Get the logon response for the operator
60+
OperatorResponse logonResponse = this.MemoryCache.Get<OperatorResponse>("PataPawaPrePayLogon");
61+
if (logonResponse == null)
62+
{
63+
throw new ArgumentNullException("PataPawaPrePayLogon", "logonResponse is null");
64+
}
65+
String apiKey = logonResponse.AdditionalTransactionResponseMetadata.ExtractFieldFromMetadata<String>("PataPawaPrePaidAPIKey");
66+
67+
if (String.IsNullOrEmpty(apiKey))
68+
{
69+
throw new ArgumentNullException("PataPawaPrePayAPIKey", "APIKey is a required field for this transaction type");
70+
}
71+
72+
// Check the meta data for the sale message type
73+
String messageType = additionalTransactionMetadata.ExtractFieldFromMetadata<String>("PataPawaPrePayMessageType");
74+
75+
if (String.IsNullOrEmpty(messageType))
76+
{
77+
throw new ArgumentNullException("PataPawaPrePayMessageType", "Message Type is a required field for this transaction type");
78+
}
79+
80+
String meterNumber = additionalTransactionMetadata.ExtractFieldFromMetadata<String>("MeterNumber");
81+
82+
if (String.IsNullOrEmpty(meterNumber))
83+
{
84+
throw new ArgumentNullException("MeterNumber", "Meter Number is a required field for this transaction type");
85+
}
86+
87+
return messageType switch
88+
{
89+
"meter" => await this.PerformMeterTransaction(meterNumber, apiKey, cancellationToken),
90+
"vend" => await this.PerformVendTransaction(meterNumber, apiKey, additionalTransactionMetadata, cancellationToken),
91+
_ => throw new ArgumentOutOfRangeException("PataPawaPrePayMessageType", $"Unsupported Message Type {messageType}")
92+
};
93+
}
94+
95+
private async Task<OperatorResponse> PerformMeterTransaction(String meterNumber, String apiKey, CancellationToken cancellationToken){
96+
HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, this.Configuration.Url);
97+
MultipartFormDataContent content = new MultipartFormDataContent();
98+
content.Add(new StringContent("meter"), "request");
99+
content.Add(new StringContent(this.Configuration.Username), "username");
100+
content.Add(new StringContent(meterNumber), "meter");
101+
content.Add(new StringContent(apiKey), "key");
102+
requestMessage.Content = content;
103+
104+
HttpResponseMessage responseMessage = await this.HttpClient.SendAsync(requestMessage, cancellationToken);
105+
106+
// Get the response
107+
String responseContent = await responseMessage.Content.ReadAsStringAsync(cancellationToken);
108+
109+
Logger.LogInformation($"Received response message from Patapawa [{responseContent}]");
110+
111+
return this.CreateFromMeter(responseContent);
112+
}
113+
114+
private async Task<OperatorResponse> PerformVendTransaction(String meterNumber, String apiKey, Dictionary<String, String> additionalTransactionMetadata, CancellationToken cancellationToken)
115+
{
116+
String customerName = additionalTransactionMetadata.ExtractFieldFromMetadata<String>("CustomerName");
117+
118+
if (String.IsNullOrEmpty(customerName))
119+
{
120+
throw new ArgumentNullException("CustomerName", "Customer Name is a required field for this transaction type");
121+
}
122+
123+
String amount = additionalTransactionMetadata.ExtractFieldFromMetadata<String>("Amount");
124+
125+
if (String.IsNullOrEmpty(meterNumber))
126+
{
127+
throw new ArgumentNullException("Amount", "Amount is a required field for this transaction type");
128+
}
129+
130+
131+
HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, this.Configuration.Url);
132+
MultipartFormDataContent content = new MultipartFormDataContent();
133+
content.Add(new StringContent("vend"), "request");
134+
content.Add(new StringContent(this.Configuration.Username), "username");
135+
content.Add(new StringContent(meterNumber), "meter");
136+
content.Add(new StringContent(apiKey), "key");
137+
content.Add(new StringContent(amount), "amount");
138+
content.Add(new StringContent(customerName), "customerName");
139+
requestMessage.Content = content;
140+
141+
HttpResponseMessage responseMessage = await this.HttpClient.SendAsync(requestMessage, cancellationToken);
142+
143+
// Get the response
144+
String responseContent = await responseMessage.Content.ReadAsStringAsync(cancellationToken);
145+
146+
Logger.LogInformation($"Received response message from Patapawa [{responseContent}]");
147+
148+
return this.CreateFromVend(responseContent);
149+
}
150+
151+
private OperatorResponse CreateFromLogon(String responseContent){
152+
LogonResponse logonResponse = JsonConvert.DeserializeObject<LogonResponse>(responseContent);
153+
154+
if (logonResponse.Status != 0){
155+
156+
return new OperatorResponse{
157+
IsSuccessful = false,
158+
ResponseCode = "-1",
159+
ResponseMessage = "Error logging on with PataPawa Post Paid API",
160+
AdditionalTransactionResponseMetadata = new Dictionary<String, String>()
161+
};
162+
}
163+
164+
OperatorResponse operatorResponse = new OperatorResponse{
165+
IsSuccessful = true,
166+
ResponseCode = "0000",
167+
ResponseMessage = logonResponse.Msg,
168+
AdditionalTransactionResponseMetadata = new Dictionary<String, String>()
169+
};
170+
171+
operatorResponse.AdditionalTransactionResponseMetadata.Add("PataPawaPrePaidAPIKey", logonResponse.Key);
172+
operatorResponse.AdditionalTransactionResponseMetadata.Add("PataPawaprePaidBalance", logonResponse.Balance.ToString());
173+
174+
this.MemoryCache.Set("PataPawaPrePayLogon", operatorResponse, this.MemoryCacheEntryOptions);
175+
176+
return operatorResponse;
177+
}
178+
179+
private OperatorResponse CreateFromMeter(String responseContent)
180+
{
181+
MeterResponse meterResponse = JsonConvert.DeserializeObject<MeterResponse>(responseContent);
182+
183+
if (meterResponse.Status != 0)
184+
{
185+
return new OperatorResponse
186+
{
187+
IsSuccessful = false,
188+
ResponseCode = "-1",
189+
ResponseMessage = "Error during meter transaction",
190+
AdditionalTransactionResponseMetadata = new Dictionary<String, String>()
191+
};
192+
}
193+
194+
OperatorResponse operatorResponse = new OperatorResponse
195+
{
196+
IsSuccessful = true,
197+
ResponseCode = "0000",
198+
ResponseMessage = meterResponse.Msg,
199+
AdditionalTransactionResponseMetadata = new Dictionary<String, String>()
200+
};
201+
202+
operatorResponse.AdditionalTransactionResponseMetadata.Add("PataPawaPrePaidCustomerName", meterResponse.CustomerName);
203+
204+
return operatorResponse;
205+
}
206+
207+
private OperatorResponse CreateFromVend(String responseContent)
208+
{
209+
VendResponse vendResponse = JsonConvert.DeserializeObject<VendResponse>(responseContent);
210+
211+
if (vendResponse.Status != 0)
212+
{
213+
return new OperatorResponse
214+
{
215+
IsSuccessful = false,
216+
ResponseCode = "-1",
217+
ResponseMessage = "Error during vend transaction",
218+
AdditionalTransactionResponseMetadata = new Dictionary<String, String>()
219+
};
220+
}
221+
222+
OperatorResponse operatorResponse = new OperatorResponse
223+
{
224+
IsSuccessful = true,
225+
ResponseCode = "0000",
226+
ResponseMessage = vendResponse.Msg,
227+
AdditionalTransactionResponseMetadata = new Dictionary<String, String>()
228+
};
229+
230+
operatorResponse.AdditionalTransactionResponseMetadata.Add("TransactionId", vendResponse.Transaction.TransactionId.ToString());
231+
operatorResponse.AdditionalTransactionResponseMetadata.Add("Reference", vendResponse.Transaction.Ref);
232+
operatorResponse.AdditionalTransactionResponseMetadata.Add("Units", vendResponse.Transaction.Units);
233+
operatorResponse.AdditionalTransactionResponseMetadata.Add("Token", vendResponse.Transaction.Token);
234+
operatorResponse.AdditionalTransactionResponseMetadata.Add("ReceiptNumber", vendResponse.Transaction.StdTokenRctNum);
235+
operatorResponse.AdditionalTransactionResponseMetadata.Add("TokenAmount", vendResponse.Transaction.StdTokenAmt.ToString());
236+
operatorResponse.AdditionalTransactionResponseMetadata.Add("TokenTax", vendResponse.Transaction.StdTokenTax.ToString());
237+
operatorResponse.AdditionalTransactionResponseMetadata.Add("MeterNumber", vendResponse.Transaction.MeterNo);
238+
operatorResponse.AdditionalTransactionResponseMetadata.Add("TransactionDateTime", vendResponse.Transaction.Date.ToString("yyyy-MM-dd HH:mm:ss"));
239+
operatorResponse.AdditionalTransactionResponseMetadata.Add("CustomerName", vendResponse.Transaction.CustomerName);
240+
241+
242+
return operatorResponse;
243+
}
244+
245+
246+
private MemoryCacheEntryOptions MemoryCacheEntryOptions =>
247+
new MemoryCacheEntryOptions().SetPriority(CacheItemPriority.NeverRemove).SetSlidingExpiration(TimeSpan.FromHours(1))
248+
.RegisterPostEvictionCallback(this.PostEvictionCallback);
249+
250+
private void PostEvictionCallback(Object key,
251+
Object value,
252+
EvictionReason reason,
253+
Object state){
254+
if (key.ToString().Contains("Logon")){
255+
this.ProcessLogonMessage(String.Empty, CancellationToken.None).Wait();
256+
}
257+
}
258+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace TransactionProcessor.BusinessLogic.OperatorInterfaces.PataPawaPrePay;
2+
3+
using System;
4+
5+
public class Transaction{
6+
public Int32 TransactionId { get; set; }
7+
public String Ref{ get; set; }
8+
public String Units { get; set; }
9+
public String Token { get; set; }
10+
public String StdTokenRctNum { get; set; }
11+
public Decimal StdTokenAmt { get; set; }
12+
public Decimal StdTokenTax { get; set; }
13+
public String MeterNo { get; set; }
14+
public DateTime Date { get; set; }
15+
public String CustomerName { get; set; }
16+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace TransactionProcessor.BusinessLogic.OperatorInterfaces.PataPawaPrePay;
2+
3+
using System;
4+
5+
public class VendResponse{
6+
public String Msg { get; set; }
7+
public Int32 Status { get; set; }
8+
public Transaction Transaction{ get; set; }
9+
}

0 commit comments

Comments
 (0)