TrxSubmitter.cs 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085
  1. using Edge.Core.Processor;using Edge.Core.IndustryStandardInterface.Pump;
  2. using Dfs.WayneChina.CardTrxManager.Support;
  3. using Dfs.WayneChina.HengshanFPos.FPosDbManager;
  4. using Dfs.WayneChina.PosModelMini;
  5. using Dfs.WayneChina.SpsDbModels.Models;
  6. using Newtonsoft.Json;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Linq;
  10. using System.Net.Http;
  11. using System.Net.Http.Headers;
  12. using System.Text;
  13. using System.Threading;
  14. using System.Threading.Tasks;
  15. namespace Dfs.WayneChina.CardTrxManager.TrxSubmitter
  16. {
  17. public class TrxCreationResponse
  18. {
  19. public Guid Id { get; set; }
  20. }
  21. public class ClientTrxInfo
  22. {
  23. public int Barcode { get; set; }
  24. public int PumpId { get; set; }
  25. public int NozzleId { get; set; }
  26. public int SiteNozzleNo { get; set; }
  27. public ushort FPosSqNo { get; set; }
  28. public int FdcSqNo { get; set; }
  29. public DateTime TimeStamp { get; set; }
  30. public decimal Volume { get; set; }
  31. public decimal Amount { get; set; }
  32. public decimal PayAmount { get; set; }
  33. public decimal UnitPrice { get; set; }
  34. public int SeqNo { get; set; }
  35. public string CardNo { get; set; }
  36. public decimal CurrentCardBalance { get; set; }
  37. public DateTime FuelingStartTime { get; set; }
  38. public DateTime FuelingFinishedTime { get; set; }
  39. public decimal VolumeTotalizer { get; set; }
  40. public string CardHolder { get; set; }
  41. public string AccountName { get; set; }
  42. }
  43. public class ReceiptReceivedEventArgs : EventArgs
  44. {
  45. public int NozzleNo { get; set; }
  46. public string ReceiptData { get; set; }
  47. }
  48. public class TrxSubmitter
  49. {
  50. #region Properties
  51. public string User { get; }
  52. public string Password { get; }
  53. public string AuthServiceBaseUrl { get; }
  54. public string TransactionServiceBaseUrl { get; }
  55. public string DeviceSN { get; }
  56. public string BusinessUnitId { get; set; }
  57. #endregion
  58. #region Fields
  59. public int Id { get; }
  60. private string grantType = "password";
  61. private string tokenAuthPath = "token";
  62. private string authScheme = "bearer";
  63. private HttpClient _client = new HttpClient();
  64. private AuthToken _authToken;
  65. public Dictionary<(int, int), int> SiteNozzles { get; set; }
  66. private FPosDbManager fPosDbManager;
  67. private ushort currentFPosSqNo;
  68. private int currentFdcSqNo;
  69. AuthToken currentAuthToken = null;
  70. private int retryCount = 0;
  71. private TransactionManager transactionManager;
  72. private StoreFowardTransaction storeForwardTransaction;
  73. private bool persistCurrentTransaction;
  74. #endregion
  75. #region Event handlers
  76. public event EventHandler<ReceiptReceivedEventArgs> OnReceiptReceived;
  77. #endregion
  78. #region Logger
  79. NLog.Logger logger = NLog.LogManager.LoadConfiguration("NLog.config").GetLogger("PosTrxSubmitter");
  80. #endregion
  81. #region Constructor
  82. public TrxSubmitter(int id, FPosDbManager fPosDbManager, CloudCredential cloudCredential)
  83. {
  84. Id = id;
  85. this.fPosDbManager = fPosDbManager;
  86. User = cloudCredential.UserName;
  87. Password = cloudCredential.Password;
  88. AuthServiceBaseUrl = cloudCredential.AuthServiceBaseUrl;
  89. TransactionServiceBaseUrl = cloudCredential.TransactionServiceBaseUrl;
  90. DeviceSN = cloudCredential.DeviceSN;
  91. BusinessUnitId = cloudCredential.CurrentBuId;
  92. transactionManager = new TransactionManager();
  93. transactionManager.Init();
  94. }
  95. public TrxSubmitter(int id, CloudCredential cloudCredential)
  96. {
  97. Id = id;
  98. User = cloudCredential.UserName;
  99. Password = cloudCredential.Password;
  100. AuthServiceBaseUrl = cloudCredential.AuthServiceBaseUrl;
  101. TransactionServiceBaseUrl = cloudCredential.TransactionServiceBaseUrl;
  102. DeviceSN = cloudCredential.DeviceSN;
  103. BusinessUnitId = cloudCredential.CurrentBuId;
  104. transactionManager = new TransactionManager();
  105. transactionManager.Init();
  106. }
  107. #endregion
  108. #region Async version of the methods
  109. public async Task<bool> SubmitTrxAsync(StoreFowardTransaction sfTrans)
  110. {
  111. storeForwardTransaction = sfTrans;
  112. var fuelingDoneItem =
  113. (FuelingDoneItem)storeForwardTransaction.TransactionItems.FirstOrDefault(t => t is FuelingDoneItem);
  114. if (fuelingDoneItem != null)
  115. {
  116. var clientTrx = CreateFuelingTransactionFromFuelingDoneItem(fuelingDoneItem);
  117. return await SubmitTrxAsync(clientTrx, false);
  118. }
  119. else
  120. {
  121. return false;
  122. }
  123. }
  124. public async Task<bool> SubmitTrxAsync(ClientTrxInfo clientTrx, bool persist = true)
  125. {
  126. InfoLog("");
  127. InfoLog($"Start to sumbit trx, retry count: {retryCount}, persist: {persist}");
  128. persistCurrentTransaction = persist;
  129. if(storeForwardTransaction == null)
  130. {
  131. if (persist)
  132. {
  133. InfoLog("No sf transaction, create one!");
  134. storeForwardTransaction = new StoreFowardTransaction();
  135. storeForwardTransaction.TransactionItems.Add(CreateFuelingDoneItemFromFuelingTransaction(clientTrx));
  136. }
  137. }
  138. else
  139. {
  140. if (storeForwardTransaction.FuelingId != clientTrx.SeqNo)
  141. {
  142. InfoLog("Last try unsuccessful");
  143. storeForwardTransaction = new StoreFowardTransaction();
  144. storeForwardTransaction.TransactionItems.Add(CreateFuelingDoneItemFromFuelingTransaction(clientTrx));
  145. }
  146. }
  147. //retryCount++;
  148. //if (retryCount > 3)
  149. //{
  150. // InfoLog("Retry count reached limit");
  151. // retryCount = 0;
  152. // if (persistCurrentTransaction)
  153. // {
  154. // storeForwardTransaction = new StoreFowardTransaction();
  155. // storeForwardTransaction.TransactionItems.Add(CreateFuelingDoneItemFromFuelingTransaction(clientTrx));
  156. // transactionManager.SaveTransaction(storeForwardTransaction);
  157. // storeForwardTransaction = null;
  158. // InfoLog($"Persisting current transaction info, for pump: {clientTrx.PumpId}, amount: {clientTrx.Amount}");
  159. // }
  160. // return false;
  161. //}
  162. currentAuthToken = await GetTokenAsync(User, Password, AuthServiceBaseUrl);
  163. if (currentAuthToken != null)
  164. {
  165. InfoLog($"Got valid token");
  166. string productItemUrl = string.Concat(TransactionServiceBaseUrl, "api/products/itemId/");
  167. var posItem = await GetPosItemAsync(productItemUrl, Convert.ToString(clientTrx.Barcode), currentAuthToken);
  168. if (posItem != null)
  169. {
  170. InfoLog($"Got PosItem, id: {posItem.Id}");
  171. var trxId = await CreateTransactionAsync(posItem, clientTrx);
  172. if (trxId != Guid.Empty)
  173. {
  174. InfoLog($"Got TrxId: {trxId}");
  175. var success = await CommitTransactionAsync(trxId, clientTrx);
  176. if (success)
  177. retryCount = 0;
  178. return success;
  179. }
  180. }
  181. else
  182. {
  183. InfoLog("Could not retrieve POS Item info!");
  184. }
  185. }
  186. if (persistCurrentTransaction)
  187. {
  188. storeForwardTransaction = new StoreFowardTransaction();
  189. storeForwardTransaction.TransactionItems.Add(CreateFuelingDoneItemFromFuelingTransaction(clientTrx));
  190. transactionManager.SaveTransaction(storeForwardTransaction);
  191. storeForwardTransaction = null;
  192. InfoLog($"Persisting current transaction info, for pump: {clientTrx.PumpId}, amount: {clientTrx.Amount}");
  193. }
  194. return false;
  195. //else
  196. //{
  197. // await Task.Delay(5000);
  198. // return await SubmitTrxAsync(clientTrx, persistCurrentTransaction);
  199. //}
  200. //return false;
  201. }
  202. private FuelingDoneItem CreateFuelingDoneItemFromFuelingTransaction(ClientTrxInfo clientTrx)
  203. {
  204. FuelingDoneItem item = new FuelingDoneItem();
  205. item.Barcode = clientTrx.Barcode;
  206. item.PumpId = clientTrx.PumpId;
  207. item.NozzleId = clientTrx.NozzleId;
  208. item.SiteNozzleNo = clientTrx.SiteNozzleNo;
  209. item.FPosSqNo = clientTrx.FPosSqNo;
  210. item.FdcSqNo = clientTrx.FdcSqNo;
  211. item.TimeStamp = clientTrx.TimeStamp;
  212. item.Volume = clientTrx.Volume;
  213. item.Amount = clientTrx.Amount;
  214. item.PayAmount = clientTrx.PayAmount;
  215. item.UnitPrice = clientTrx.UnitPrice;
  216. item.SeqNo = clientTrx.SeqNo;
  217. item.CardNo = clientTrx.CardNo;
  218. item.CurrentCardBalance = clientTrx.CurrentCardBalance;
  219. item.FuelingStartTime = clientTrx.FuelingStartTime;
  220. item.FuelingFinishedTime = clientTrx.FuelingFinishedTime;
  221. item.VolumeTotalizer = clientTrx.VolumeTotalizer;
  222. item.CardHolder = clientTrx.CardHolder;
  223. item.AccountName = clientTrx.AccountName;
  224. return item;
  225. }
  226. private ClientTrxInfo CreateFuelingTransactionFromFuelingDoneItem(FuelingDoneItem item)
  227. {
  228. ClientTrxInfo clientTrxInfo = new ClientTrxInfo();
  229. clientTrxInfo.Barcode = item.Barcode;
  230. clientTrxInfo.PumpId = item.PumpId;
  231. clientTrxInfo.NozzleId = item.NozzleId;
  232. clientTrxInfo.SiteNozzleNo = item.SiteNozzleNo;
  233. clientTrxInfo.FPosSqNo = item.FPosSqNo;
  234. clientTrxInfo.FdcSqNo = item.FdcSqNo;
  235. clientTrxInfo.TimeStamp = item.TimeStamp;
  236. clientTrxInfo.Volume = item.Volume;
  237. clientTrxInfo.Amount = item.Amount;
  238. clientTrxInfo.PayAmount = item.PayAmount;
  239. clientTrxInfo.UnitPrice = item.UnitPrice;
  240. clientTrxInfo.SeqNo = item.SeqNo;
  241. clientTrxInfo.CardNo = item.CardNo;
  242. clientTrxInfo.CurrentCardBalance = item.CurrentCardBalance;
  243. clientTrxInfo.FuelingStartTime = item.FuelingStartTime;
  244. clientTrxInfo.FuelingFinishedTime = item.FuelingFinishedTime;
  245. clientTrxInfo.VolumeTotalizer = item.VolumeTotalizer;
  246. clientTrxInfo.CardHolder = item.CardHolder;
  247. clientTrxInfo.AccountName = item.AccountName;
  248. return clientTrxInfo;
  249. }
  250. private async Task<AuthToken> GetTokenAsync(string userName, string password, string baseUrl)
  251. {
  252. InfoLog("GetTokenAsync...");
  253. _client.DefaultRequestHeaders.Clear();
  254. string tokenUrl = string.Concat(baseUrl, tokenAuthPath);
  255. var formParam = new AuthenticationParameter(grantType, userName, password);
  256. CancellationTokenSource cts = new CancellationTokenSource();
  257. cts.CancelAfter(5000);
  258. HttpResponseMessage response;
  259. try
  260. {
  261. response = await _client
  262. .PostAsync(tokenUrl, new FormUrlEncodedContent(formParam.Params), cts.Token)
  263. .ConfigureAwait(false);
  264. }
  265. catch (Exception ex)
  266. {
  267. ErrorLog("Exception in retrieving auth token: " + ex.ToString());
  268. response = null;
  269. currentAuthToken = null;
  270. return null;
  271. }
  272. InfoLog($"Response for getting token, Response==null? {response == null}");
  273. if (response != null && response.IsSuccessStatusCode)
  274. {
  275. await response.Content.ReadAsStringAsync().ContinueWith(x =>
  276. {
  277. currentAuthToken = JsonConvert.DeserializeObject<AuthToken>(x?.Result);
  278. if (!string.IsNullOrEmpty(currentAuthToken.BusinessUnitsJsonString))
  279. {
  280. currentAuthToken.BusinessUnitList = JsonConvert.DeserializeObject<IList<BusinessUnit>>(currentAuthToken.BusinessUnitsJsonString);
  281. }
  282. });
  283. }
  284. else
  285. {
  286. var content = await response.Content.ReadAsStringAsync();
  287. if (response != null)
  288. response.Content?.Dispose();
  289. currentAuthToken = null;
  290. }
  291. return currentAuthToken;
  292. }
  293. private async Task<PosItem> GetPosItemAsync(string url, string itemId, AuthToken token)
  294. {
  295. InfoLog("GetPosItemAsync...");
  296. if (storeForwardTransaction != null)
  297. {
  298. var productCodeIdentifiedItem =
  299. (ProductCodeIdentitiedItem)storeForwardTransaction.TransactionItems.FirstOrDefault(t => t is ProductCodeIdentitiedItem);
  300. if (productCodeIdentifiedItem != null)
  301. {
  302. InfoLog($"ProductCode already identified, id: {productCodeIdentifiedItem.ItemId}");
  303. return new PosItem { Id = productCodeIdentifiedItem.ItemId };
  304. }
  305. }
  306. if (storeForwardTransaction == null && !persistCurrentTransaction)
  307. {
  308. InfoLog("In the middle of retrying, but somehow the sf trans is gone, abort");
  309. return null;
  310. }
  311. PosItem posItem = null;
  312. _client.DefaultRequestHeaders.Clear();
  313. _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, token.AccessToken);
  314. InfoLog($"{_client.DefaultRequestHeaders.Authorization.Scheme}, {_client.DefaultRequestHeaders.Authorization.Parameter}");
  315. BusinessUnitId = currentAuthToken.BusinessUnitList.Count > 0 ? currentAuthToken.BusinessUnitList.First().Id.ToString() : string.Empty;
  316. //Be aware, DeviceSN is `required` after 2019.04 for Smart Fuel v0.5
  317. _client.DefaultRequestHeaders.Add("DeviceSN", DeviceSN);
  318. _client.DefaultRequestHeaders.Add("CurrentBuId", BusinessUnitId);
  319. InfoLog($"DeviceSN: {DeviceSN}");
  320. InfoLog($"BuId: {BusinessUnitId}");
  321. var productUrl = string.Concat(url, itemId);
  322. InfoLog($"Itme url: {productUrl}");
  323. HttpResponseMessage response;
  324. try
  325. {
  326. response = await _client.GetAsync(productUrl).ConfigureAwait(false);
  327. }
  328. catch (Exception ex)
  329. {
  330. ErrorLog("Exception in retrieving pos item info: " + ex.ToString());
  331. if (persistCurrentTransaction)
  332. {
  333. ErrorLog("Exception occurred during get item, save transaction!");
  334. transactionManager.SaveTransaction(storeForwardTransaction);
  335. }
  336. response = null;
  337. return null;
  338. }
  339. InfoLog($"GetItem, StatusCode: {response.StatusCode}");
  340. if (response.IsSuccessStatusCode)
  341. {
  342. await response.Content.ReadAsStringAsync().ContinueWith(x =>
  343. {
  344. posItem = JsonConvert.DeserializeObject<PosItem>(x?.Result);
  345. if (storeForwardTransaction == null)
  346. ErrorLog("SF trans is null");
  347. if (!storeForwardTransaction.TransactionItems.Any(t => t is ProductCodeIdentitiedItem))
  348. {
  349. InfoLog("No existing ProductCodeIdentifiedItem");
  350. storeForwardTransaction.TransactionItems.Add(new ProductCodeIdentitiedItem { ItemId = posItem.Id });
  351. }
  352. else
  353. {
  354. InfoLog("ProductCodeIdentifiedItem already exists!");
  355. }
  356. });
  357. }
  358. else
  359. {
  360. var content = await response.Content.ReadAsStringAsync();
  361. response.Content?.Dispose();
  362. if (persistCurrentTransaction)
  363. {
  364. ErrorLog("Failure occurred during get item, save it!");
  365. transactionManager.SaveTransaction(storeForwardTransaction);
  366. }
  367. }
  368. return posItem;
  369. }
  370. private async Task<Guid> CreateTransactionAsync(PosItem posItem, ClientTrxInfo clientTrxInfo)
  371. {
  372. InfoLog($"CreateTransactionAsync, pump: {clientTrxInfo.PumpId}, amount: {clientTrxInfo.Amount}");
  373. InfoLog($"Volume totalizer: {clientTrxInfo.VolumeTotalizer}");
  374. if (storeForwardTransaction != null)
  375. {
  376. var transCreatedItem =
  377. (TransactionCreatedItem)storeForwardTransaction.TransactionItems.FirstOrDefault(t => t is TransactionCreatedItem);
  378. if (transCreatedItem != null)
  379. {
  380. InfoLog($"Transaction already rung up, id: {transCreatedItem.TransactionId}");
  381. return transCreatedItem.TransactionId;
  382. }
  383. }
  384. Guid trxId = Guid.Empty;
  385. var trxUrl = string.Concat(TransactionServiceBaseUrl, "api/transactions");
  386. var clientPosTrx = new ClientPosTrx
  387. {
  388. RequestingCreationTimeInPosClient = DateTime.Now,
  389. Type = PosTrxType.Sale,
  390. Source = PosTrxSource.Outdoor,
  391. Items = new List<ClientRingUpPosItem>
  392. {
  393. new ClientRingUpPosItem
  394. {
  395. PosItemUniqueId = posItem.Id, //GUID
  396. Qty = clientTrxInfo.Volume,
  397. ClientRingUpTime = DateTime.Now,
  398. FuelItemSoldOnPumpId = clientTrxInfo.PumpId,
  399. FuelItemSoldOnPumpNozzleId = clientTrxInfo.SiteNozzleNo,//SiteNozzles[(clientTrxInfo.PumpId, clientTrxInfo.NozzleId)],//fdcTrx.Nozzle.LogicalId,
  400. FuelItemOriginalGrossAmount = clientTrxInfo.Amount,//(decimal)trdInfo.Mon.Value / 100,
  401. FuelItemFdcTransactionSeqNo = Convert.ToString(clientTrxInfo.SeqNo),
  402. fuelItemTransactionEndTime = clientTrxInfo.FuelingFinishedTime,
  403. TransactionComment = "IC Trx",
  404. FuelItemPumpTotalizerVolume = clientTrxInfo.VolumeTotalizer
  405. }
  406. }
  407. };
  408. _client.DefaultRequestHeaders.Clear();
  409. _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, currentAuthToken.AccessToken);
  410. _client.DefaultRequestHeaders.Add("DeviceSN", DeviceSN);
  411. _client.DefaultRequestHeaders.Add("CurrentBuId", BusinessUnitId);
  412. HttpResponseMessage response;
  413. try
  414. {
  415. response = await _client.PostAsJsonAsync(trxUrl, clientPosTrx).ConfigureAwait(false);
  416. }
  417. catch (Exception ex)
  418. {
  419. ErrorLog("Exception in creating transaction: " + ex.ToString());
  420. ErrorLog("Exception occurred during creat transaction, save it!");
  421. transactionManager.SaveTransaction(storeForwardTransaction);
  422. response = null;
  423. return Guid.Empty;
  424. }
  425. InfoLog($"CreateTransaction, StatusCode: {response.StatusCode}");
  426. if (response.IsSuccessStatusCode)
  427. {
  428. await response.Content.ReadAsStringAsync().ContinueWith(x =>
  429. {
  430. var trxCreationResponse = JsonConvert.DeserializeObject<TrxCreationResponse>(x.Result);
  431. trxId = trxCreationResponse.Id;
  432. if (!storeForwardTransaction.TransactionItems.Any(t => t is TransactionCreatedItem))
  433. {
  434. InfoLog("No existing TransactionCreatedItem");
  435. storeForwardTransaction.TransactionItems.Add(new TransactionCreatedItem { TransactionId = trxId });
  436. }
  437. else
  438. {
  439. InfoLog("TransactionCreatedItem already exists!");
  440. }
  441. });
  442. }
  443. else
  444. {
  445. var content = await response.Content.ReadAsStringAsync();
  446. response.Content?.Dispose();
  447. if (persistCurrentTransaction)
  448. {
  449. ErrorLog("Failure during Create Transaction, save it!");
  450. transactionManager.SaveTransaction(storeForwardTransaction);
  451. }
  452. }
  453. return trxId;
  454. }
  455. private async Task<bool> CommitTransactionAsync(Guid trxId, ClientTrxInfo clientTrxInfo)
  456. {
  457. bool submitted = false;
  458. var payUrlTemplate = string.Concat(TransactionServiceBaseUrl, "api/transactions/{0}/payment");
  459. string realPayUrl = string.Format(payUrlTemplate, trxId);
  460. //Payment detail for receipt
  461. string version = "1.0";
  462. var icPayDetail = new
  463. {
  464. CardNo = clientTrxInfo.CardNo,
  465. PayAmount = clientTrxInfo.PayAmount,
  466. CardHolder = clientTrxInfo.CardHolder,
  467. AccountName = clientTrxInfo.AccountName,
  468. CardBalance = clientTrxInfo.CurrentCardBalance,
  469. Version = version
  470. };
  471. PosTrxMop posTrxMop = new PosTrxMop();
  472. posTrxMop.Paid = clientTrxInfo.PayAmount;
  473. posTrxMop.ICCardNumber = clientTrxInfo.CardNo;
  474. posTrxMop.RawResult = JsonConvert.SerializeObject(icPayDetail);
  475. posTrxMop.PosMopId = Guid.Parse("ff64ab36-658d-40a4-94bf-bee7231ac788");
  476. posTrxMop.Mop = new PosMop
  477. {
  478. Id = Guid.Parse("ff64ab36-658d-40a4-94bf-bee7231ac788"),
  479. Name = "IC",
  480. PaymentId = 4,
  481. CreatedDateTime = DateTime.Now,
  482. ChangesetId = Guid.Parse("84f02b52-6950-4c50-a0b1-9827d6459edc"),
  483. TargetBusinessUnitId = Guid.Parse("6c40e7f6-2b2d-40de-8c5c-f5693a05ab0d"),
  484. DisplayName = "IC卡"
  485. };
  486. _client.DefaultRequestHeaders.Clear();
  487. _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, currentAuthToken.AccessToken);
  488. _client.DefaultRequestHeaders.Add("DeviceSN", DeviceSN);
  489. _client.DefaultRequestHeaders.Add("CurrentBuId", BusinessUnitId);
  490. HttpResponseMessage response;
  491. try
  492. {
  493. response = await _client.PostAsJsonAsync(realPayUrl, posTrxMop).ConfigureAwait(false);
  494. }
  495. catch (Exception ex)
  496. {
  497. ErrorLog("Exception in committing transaction: " + ex.ToString());
  498. ErrorLog("Exception occurred during commit, save transaction!");
  499. transactionManager.SaveTransaction(storeForwardTransaction);
  500. response = null;
  501. return false;
  502. }
  503. InfoLog($"Commit, StatusCode: {response.StatusCode}");
  504. if (response.StatusCode == System.Net.HttpStatusCode.Conflict)
  505. InfoLog("Status code: Conflict, treat it as success");
  506. if (response.IsSuccessStatusCode || response.StatusCode == System.Net.HttpStatusCode.Conflict)
  507. {
  508. submitted = true;
  509. if (storeForwardTransaction != null)
  510. storeForwardTransaction.TransactionItems.Add(new CommittedItem { Success = true });
  511. InfoLog("Since submit is successful, discard the sf trans");
  512. storeForwardTransaction = null;
  513. }
  514. else
  515. {
  516. if (persistCurrentTransaction)
  517. {
  518. ErrorLog("Failure during commit, save transaction!");
  519. transactionManager.SaveTransaction(storeForwardTransaction);
  520. }
  521. }
  522. return submitted;
  523. }
  524. #endregion
  525. #region Public methods
  526. public bool SubmitTrx(TTrdinfo trdInfo, int fdcTrxSqNo, FdcTransaction fdcTrx)
  527. {
  528. InfoLog("\n");
  529. currentFPosSqNo = (ushort)trdInfo.SeqNo;
  530. currentFdcSqNo = fdcTrx.SequenceNumberGeneratedOnPhysicalPump;
  531. //
  532. // Reserve the transaction
  533. //
  534. var reserved = fPosDbManager.ReserveTransaction(currentFPosSqNo, currentFdcSqNo);
  535. if (!reserved)
  536. {
  537. InfoLog($"Unable to reserve the transaction, FPos SqNo: {currentFPosSqNo}, FC SqNo: {currentFdcSqNo}");
  538. return false;
  539. }
  540. InfoLog($"Transaction must be reserved now.");
  541. AuthToken currentAuthToken = null;
  542. if (_authToken != null)
  543. {
  544. InfoLog($"Valid? {_authToken.IsTokenValid()}");
  545. }
  546. // Try first to utilize the existing valid token
  547. // If it's expired, then retrieve a new token from host
  548. if (_authToken != null && _authToken.IsTokenValid())
  549. {
  550. InfoLog("Token is still valid, no need to get a new one.");
  551. currentAuthToken = _authToken;
  552. }
  553. else
  554. {
  555. InfoLog("Token not valid anymore, get a new one.");
  556. //
  557. //Get auth token
  558. //
  559. currentAuthToken = GetAccessToken(User, Password, AuthServiceBaseUrl);
  560. }
  561. if (currentAuthToken != null)
  562. {
  563. InfoLog($"AccessToken: {currentAuthToken.AccessToken}");
  564. string productItemUrl = string.Concat(TransactionServiceBaseUrl, "api/products/itemId/");
  565. //
  566. //Get POS item
  567. //
  568. PosItem posItem = GetPosItemByItemId(productItemUrl, Convert.ToString(fdcTrx.Barcode), currentAuthToken);
  569. InfoLog($"Barcode: {fdcTrx.Barcode}, PosItem, id = {posItem.Id}");
  570. if (posItem == null)
  571. {
  572. InfoLog("Unable to retrieve the PosItem for current transaction");
  573. return false;
  574. }
  575. //
  576. // Create transaction
  577. //
  578. InfoLog("Creating transaction...");
  579. var posTrxId = CreateTransaction(trdInfo, posItem, /* fdcTrxSqNo,*/ currentAuthToken, fdcTrx);
  580. InfoLog($"CreateTransaction returns id: {posTrxId}");
  581. if (posTrxId == Guid.Empty)
  582. {
  583. InfoLog("Unable to create transaction on cloud.");
  584. return false;
  585. }
  586. //
  587. //Commit payment
  588. //
  589. return CommitTransaction(posTrxId, trdInfo, currentAuthToken);
  590. }
  591. else
  592. {
  593. InfoLog($"Error, could not get an access token, abort submitting current transaction...");
  594. return false;
  595. }
  596. }
  597. #endregion
  598. #region Get access token
  599. private AuthToken GetAccessToken(string userName, string password, string baseUrl)
  600. {
  601. InfoLog("Try to get Access Token.");
  602. _client.DefaultRequestHeaders.Clear();
  603. string tokenUrl = string.Concat(baseUrl, tokenAuthPath);
  604. HttpResponseMessage tokenResponse = null;
  605. var formParam = new AuthenticationParameter(grantType, userName, password);
  606. try
  607. {
  608. tokenResponse = _client.PostAsync(tokenUrl, new FormUrlEncodedContent(formParam.Params)).Result;
  609. }
  610. catch (AggregateException aex)
  611. {
  612. InfoLog("Exception in GetAccessToken.");
  613. var e = aex.Flatten();
  614. for (int i = 0; i < e.InnerExceptions.Count; i++)
  615. {
  616. logger.Info(e.InnerExceptions[i].InnerException.Message);
  617. }
  618. }
  619. catch (Exception ex)
  620. {
  621. InfoLog("Exception: " + ex.Message + " Inner exception: " + ex.InnerException.Message);
  622. }
  623. if (tokenResponse != null)
  624. {
  625. InfoLog($"Token response status code: {tokenResponse.StatusCode}");
  626. }
  627. else
  628. {
  629. bool unreserveResult = fPosDbManager.UnreserveTransaction(currentFPosSqNo, currentFdcSqNo);
  630. InfoLog($"Token response null, unreserve at GetToken, success {unreserveResult}");
  631. return null;
  632. }
  633. if (!tokenResponse.IsSuccessStatusCode)
  634. {
  635. InfoLog("Failed to get Access Token for user: " + userName);
  636. bool unreserveResult = fPosDbManager.UnreserveTransaction(currentFPosSqNo, currentFdcSqNo);
  637. InfoLog($"Unreserve at GetToken, success {unreserveResult}");
  638. return null;
  639. }
  640. _authToken = tokenResponse.Content.ReadAsAsync<AuthToken>().Result;
  641. _authToken.TokenRetrievedTime = DateTime.Now;
  642. return _authToken;
  643. }
  644. #endregion
  645. #region Get PosItem by ItemId
  646. /// <summary>
  647. /// Get the PosItem by the barcode
  648. /// </summary>
  649. /// <param name="url">Service URL</param>
  650. /// <param name="itemId">Barcode for now.</param>
  651. /// <param name="token">Auth token.</param>
  652. /// <returns></returns>
  653. private PosItem GetPosItemByItemId(string url, string itemId, AuthToken token)
  654. {
  655. var trx = fPosDbManager.GetCurrentTrx(currentFPosSqNo, currentFdcSqNo);
  656. if (trx.PosItemId != null)
  657. {
  658. if (!trx.PosItemId.Equals(Guid.Empty))
  659. return new PosItem { Id = trx.PosItemId };
  660. }
  661. try
  662. {
  663. _client.DefaultRequestHeaders.Clear();
  664. _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, token.AccessToken);
  665. _client.DefaultRequestHeaders.Add("DeviceSN", DeviceSN);
  666. var productUrl = string.Concat(url, itemId);
  667. var productResponse = _client.GetAsync(productUrl).Result;
  668. if (productResponse == null)
  669. {
  670. ErrorLog($"Error in getting the product by ItemId");
  671. return null;
  672. }
  673. InfoLog($"GetPosItemByItemId, StatusCode: {productResponse.StatusCode}");
  674. //If it's Success, then try to get the item, otherwise, unreserve transaction and return null
  675. if (productResponse.IsSuccessStatusCode)
  676. {
  677. var setting = new JsonSerializerSettings();
  678. setting.Converters.Add(new PosJsonDateConverter());
  679. PosItem item = JsonConvert.DeserializeObject<PosItem>(productResponse.Content.ReadAsStringAsync().Result, setting);
  680. fPosDbManager.SetPosItemId(currentFPosSqNo, currentFdcSqNo, item.Id);
  681. return item;
  682. }
  683. }
  684. catch (AggregateException aex)
  685. {
  686. InfoLog("oooo Exception in GetPosItemByItemId: " + aex.ToString());
  687. }
  688. bool unreserveResult = fPosDbManager.UnreserveTransaction(currentFPosSqNo, currentFdcSqNo);
  689. InfoLog($"Unreserving transaction FPos SqNo: {currentFdcSqNo}, Fdc SqNo: {currentFdcSqNo}, success? {unreserveResult}");
  690. return null;
  691. }
  692. #endregion
  693. #region Create transaction
  694. private Guid CreateTransaction(TTrdinfo trdInfo, PosItem posItem, /*int fdcTrxSqNo,*/ AuthToken token, FdcTransaction fdcTrx)
  695. {
  696. //var testUrl = //"http://ipos.biz:8699/api/transactions";
  697. var currentFPosTrx = fPosDbManager.GetCurrentTrx(currentFPosSqNo, currentFdcSqNo);
  698. if (currentFPosTrx != null)
  699. {
  700. if (!currentFPosTrx.CloudTrxId.Equals(Guid.Empty))
  701. return currentFPosTrx.CloudTrxId;
  702. }
  703. var trxUrl = string.Concat(TransactionServiceBaseUrl, "api/transactions");
  704. var clientPosTrx = new ClientPosTrx
  705. {
  706. RequestingCreationTimeInPosClient = DateTime.Now,
  707. Type = PosTrxType.Sale,
  708. Source = PosTrxSource.Outdoor,
  709. Items = new List<ClientRingUpPosItem>
  710. {
  711. new ClientRingUpPosItem
  712. {
  713. PosItemUniqueId = posItem.Id, //GUID
  714. Qty = (decimal)trdInfo.Vol.Value /100,
  715. ClientRingUpTime = DateTime.Now,
  716. FuelItemSoldOnPumpId = fdcTrx.Nozzle.PumpId,
  717. FuelItemSoldOnPumpNozzleId = SiteNozzles[(fdcTrx.Nozzle.PumpId, fdcTrx.Nozzle.LogicalId)],//fdcTrx.Nozzle.LogicalId,
  718. FuelItemOriginalGrossAmount = currentFPosTrx.FillingAmount,//(decimal)trdInfo.Mon.Value / 100,
  719. FuelItemFdcTransactionSeqNo = Convert.ToString(fdcTrx.SequenceNumberGeneratedOnPhysicalPump),
  720. //fuelItemTransactionEndTime = trdInfo.TtctimeEnd,
  721. TransactionComment = "IC Trx"
  722. }
  723. }
  724. };
  725. InfoLog($"GrossAmount: {clientPosTrx.Items.First().FuelItemOriginalGrossAmount}");
  726. InfoLog($"FdcTrxSqNo: {clientPosTrx.Items.First().FuelItemFdcTransactionSeqNo}");
  727. _client.DefaultRequestHeaders.Clear();
  728. _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, token.AccessToken);
  729. _client.DefaultRequestHeaders.Add("DeviceSN", DeviceSN);
  730. try
  731. {
  732. var trxCreationResponse = _client.PostAsJsonAsync(trxUrl, clientPosTrx);
  733. InfoLog($"CreateTransaction, StatusCode: {trxCreationResponse.Result.StatusCode}");
  734. if (trxCreationResponse.IsCompletedSuccessfully)
  735. {
  736. var trxResp = trxCreationResponse.Result.Content.ReadAsAsync<TrxCreationResponse>();
  737. fPosDbManager.SetTransactionId(currentFPosSqNo, currentFdcSqNo, trxResp.Result.Id);
  738. return trxResp.Result.Id;
  739. }
  740. else
  741. {
  742. return Guid.Empty;
  743. }
  744. }
  745. catch (AggregateException aex)
  746. {
  747. InfoLog("Exception in CreateTransaction: " + aex.ToString());
  748. }
  749. fPosDbManager.UnreserveTransaction(currentFPosSqNo, currentFdcSqNo);
  750. return Guid.Empty;
  751. }
  752. #endregion
  753. #region Transaction commit
  754. private bool CommitTransaction(Guid id, TTrdinfo trdInfo, AuthToken token)
  755. {
  756. InfoLog("Start to commit transaction.");
  757. var trx = fPosDbManager.GetCurrentTrx(currentFPosSqNo, currentFdcSqNo);
  758. if (trx != null && trx.Submitted)
  759. {
  760. InfoLog($"This is interesting, Trx {trx.CloudTrxId} with FPos SqNo {currentFPosSqNo}, FC SqNo {currentFdcSqNo} already submitted!");
  761. return true;
  762. }
  763. //var testUrl = "http://ipos.biz:8699/api/transactions/{0}/payment";
  764. var payUrlTemplate = string.Concat(TransactionServiceBaseUrl, "api/transactions/{0}/payment");
  765. string realPayUrl = string.Format(payUrlTemplate, id);
  766. //Amount of actual payment
  767. decimal payAmount = (decimal)trdInfo.RealMon.Value / 100;
  768. //Payment detail for receipt
  769. string version = "1.0";
  770. var icPayDetail = new
  771. {
  772. CardNo = trdInfo.CardNo,
  773. PayAmount = payAmount,
  774. CardBalance = (decimal)trdInfo.CardBal.Value / 100,
  775. Version = version
  776. };
  777. PosTrxMop posTrxMop = new PosTrxMop();
  778. posTrxMop.Paid = payAmount;
  779. posTrxMop.ICCardNumber = trdInfo.CardNo;
  780. posTrxMop.RawResult = JsonConvert.SerializeObject(icPayDetail);
  781. posTrxMop.PosMopId = Guid.Parse("ff64ab36-658d-40a4-94bf-bee7231ac788");
  782. posTrxMop.Mop = new PosMop
  783. {
  784. Id = Guid.Parse("ff64ab36-658d-40a4-94bf-bee7231ac788"),
  785. Name = "IC",
  786. PaymentId = 4,
  787. CreatedDateTime = DateTime.Now,
  788. ChangesetId = Guid.Parse("84f02b52-6950-4c50-a0b1-9827d6459edc"),
  789. TargetBusinessUnitId = Guid.Parse("6c40e7f6-2b2d-40de-8c5c-f5693a05ab0d"),
  790. DisplayName = "IC卡"
  791. };
  792. _client.DefaultRequestHeaders.Clear();
  793. _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, token.AccessToken);
  794. _client.DefaultRequestHeaders.Add("DeviceSN", DeviceSN);
  795. try
  796. {
  797. var payResponse = _client.PostAsJsonAsync(realPayUrl, posTrxMop);
  798. InfoLog($"Commit Transaction, PayAmount: {payAmount}, StatusCode: {payResponse.Result.StatusCode}");
  799. if (payResponse.Result.IsSuccessStatusCode)
  800. {
  801. fPosDbManager.SetTransactionSubmitted(currentFPosSqNo, currentFdcSqNo, true);
  802. var receipt = GetHtmlReceipt(token, id, 30);
  803. if (!string.IsNullOrEmpty(receipt))
  804. {
  805. OnReceiptReceived?.Invoke(this, new ReceiptReceivedEventArgs
  806. {
  807. NozzleNo = SiteNozzles[(trx.PumpId, trx.NozzleId)],
  808. ReceiptData = receipt
  809. });
  810. }
  811. return true;
  812. }
  813. bool unreserveResult = fPosDbManager.UnreserveTransaction(currentFPosSqNo, currentFdcSqNo);
  814. InfoLog($"Unreserve at Commit, success? {unreserveResult}");
  815. return false;
  816. }
  817. catch (AggregateException aex)
  818. {
  819. InfoLog("oooo Exception in CommitPayment: " + aex.ToString());
  820. }
  821. return false;
  822. }
  823. #endregion
  824. #region Get receipt
  825. private string GetHtmlReceipt(AuthToken token, Guid trxId, int lineWidth = 30)
  826. {
  827. string relativeUrl = "api/receipt/{0}/maxChar/{1}";
  828. InfoLog("Start to get the receipt...");
  829. _client.DefaultRequestHeaders.Clear();
  830. _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, token.AccessToken);
  831. _client.DefaultRequestHeaders.Add("DeviceSN", DeviceSN);
  832. try
  833. {
  834. string receiptUrl = TransactionServiceBaseUrl + string.Format(relativeUrl, trxId, lineWidth);
  835. DebugLog($"ReceiptUrl: {receiptUrl}");
  836. var result = _client.GetAsync(receiptUrl);
  837. if (result.Result.IsSuccessStatusCode)
  838. {
  839. InfoLog($"Getting receipt, StatusCode: {result.Result.StatusCode}");
  840. return result.Result.Content.ReadAsStringAsync().Result;
  841. }
  842. else
  843. {
  844. InfoLog($"Getting receipt, result? {result.Result.StatusCode}");
  845. return string.Empty;
  846. }
  847. }
  848. catch (Exception ex)
  849. {
  850. InfoLog($"Retrieving receipt exception: {ex}");
  851. }
  852. return string.Empty;
  853. }
  854. #endregion
  855. #region Log methods
  856. private void InfoLog(string log)
  857. {
  858. if (logger.IsInfoEnabled)
  859. logger.Info($"{Id} " + log);
  860. }
  861. private void DebugLog(string log)
  862. {
  863. if (logger.IsDebugEnabled)
  864. logger.Debug($"{Id}" + log);
  865. }
  866. private void ErrorLog(string log)
  867. {
  868. if (logger.IsErrorEnabled)
  869. logger.Error($"{Id} " + log);
  870. }
  871. #endregion
  872. }
  873. }