using Edge.Core.Processor;using Edge.Core.IndustryStandardInterface.Pump; using Dfs.WayneChina.CardTrxManager.Support; using Dfs.WayneChina.HengshanFPos.FPosDbManager; using Dfs.WayneChina.PosModelMini; using Dfs.WayneChina.SpsDbModels.Models; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Dfs.WayneChina.CardTrxManager.TrxSubmitter { public class TrxCreationResponse { public Guid Id { get; set; } } public class ClientTrxInfo { public int Barcode { get; set; } public int PumpId { get; set; } public int NozzleId { get; set; } public int SiteNozzleNo { get; set; } public ushort FPosSqNo { get; set; } public int FdcSqNo { get; set; } public DateTime TimeStamp { get; set; } public decimal Volume { get; set; } public decimal Amount { get; set; } public decimal PayAmount { get; set; } public decimal UnitPrice { get; set; } public int SeqNo { get; set; } public string CardNo { get; set; } public decimal CurrentCardBalance { get; set; } public DateTime FuelingStartTime { get; set; } public DateTime FuelingFinishedTime { get; set; } public decimal VolumeTotalizer { get; set; } public string CardHolder { get; set; } public string AccountName { get; set; } } public class ReceiptReceivedEventArgs : EventArgs { public int NozzleNo { get; set; } public string ReceiptData { get; set; } } public class TrxSubmitter { #region Properties public string User { get; } public string Password { get; } public string AuthServiceBaseUrl { get; } public string TransactionServiceBaseUrl { get; } public string DeviceSN { get; } public string BusinessUnitId { get; set; } #endregion #region Fields public int Id { get; } private string grantType = "password"; private string tokenAuthPath = "token"; private string authScheme = "bearer"; private HttpClient _client = new HttpClient(); private AuthToken _authToken; public Dictionary<(int, int), int> SiteNozzles { get; set; } private FPosDbManager fPosDbManager; private ushort currentFPosSqNo; private int currentFdcSqNo; AuthToken currentAuthToken = null; private int retryCount = 0; private TransactionManager transactionManager; private StoreFowardTransaction storeForwardTransaction; private bool persistCurrentTransaction; #endregion #region Event handlers public event EventHandler OnReceiptReceived; #endregion #region Logger NLog.Logger logger = NLog.LogManager.LoadConfiguration("NLog.config").GetLogger("PosTrxSubmitter"); #endregion #region Constructor public TrxSubmitter(int id, FPosDbManager fPosDbManager, CloudCredential cloudCredential) { Id = id; this.fPosDbManager = fPosDbManager; User = cloudCredential.UserName; Password = cloudCredential.Password; AuthServiceBaseUrl = cloudCredential.AuthServiceBaseUrl; TransactionServiceBaseUrl = cloudCredential.TransactionServiceBaseUrl; DeviceSN = cloudCredential.DeviceSN; BusinessUnitId = cloudCredential.CurrentBuId; transactionManager = new TransactionManager(); transactionManager.Init(); } public TrxSubmitter(int id, CloudCredential cloudCredential) { Id = id; User = cloudCredential.UserName; Password = cloudCredential.Password; AuthServiceBaseUrl = cloudCredential.AuthServiceBaseUrl; TransactionServiceBaseUrl = cloudCredential.TransactionServiceBaseUrl; DeviceSN = cloudCredential.DeviceSN; BusinessUnitId = cloudCredential.CurrentBuId; transactionManager = new TransactionManager(); transactionManager.Init(); } #endregion #region Async version of the methods public async Task SubmitTrxAsync(StoreFowardTransaction sfTrans) { storeForwardTransaction = sfTrans; var fuelingDoneItem = (FuelingDoneItem)storeForwardTransaction.TransactionItems.FirstOrDefault(t => t is FuelingDoneItem); if (fuelingDoneItem != null) { var clientTrx = CreateFuelingTransactionFromFuelingDoneItem(fuelingDoneItem); return await SubmitTrxAsync(clientTrx, false); } else { return false; } } public async Task SubmitTrxAsync(ClientTrxInfo clientTrx, bool persist = true) { InfoLog(""); InfoLog($"Start to sumbit trx, retry count: {retryCount}, persist: {persist}"); persistCurrentTransaction = persist; if(storeForwardTransaction == null) { if (persist) { InfoLog("No sf transaction, create one!"); storeForwardTransaction = new StoreFowardTransaction(); storeForwardTransaction.TransactionItems.Add(CreateFuelingDoneItemFromFuelingTransaction(clientTrx)); } } else { if (storeForwardTransaction.FuelingId != clientTrx.SeqNo) { InfoLog("Last try unsuccessful"); storeForwardTransaction = new StoreFowardTransaction(); storeForwardTransaction.TransactionItems.Add(CreateFuelingDoneItemFromFuelingTransaction(clientTrx)); } } //retryCount++; //if (retryCount > 3) //{ // InfoLog("Retry count reached limit"); // retryCount = 0; // if (persistCurrentTransaction) // { // storeForwardTransaction = new StoreFowardTransaction(); // storeForwardTransaction.TransactionItems.Add(CreateFuelingDoneItemFromFuelingTransaction(clientTrx)); // transactionManager.SaveTransaction(storeForwardTransaction); // storeForwardTransaction = null; // InfoLog($"Persisting current transaction info, for pump: {clientTrx.PumpId}, amount: {clientTrx.Amount}"); // } // return false; //} currentAuthToken = await GetTokenAsync(User, Password, AuthServiceBaseUrl); if (currentAuthToken != null) { InfoLog($"Got valid token"); string productItemUrl = string.Concat(TransactionServiceBaseUrl, "api/products/itemId/"); var posItem = await GetPosItemAsync(productItemUrl, Convert.ToString(clientTrx.Barcode), currentAuthToken); if (posItem != null) { InfoLog($"Got PosItem, id: {posItem.Id}"); var trxId = await CreateTransactionAsync(posItem, clientTrx); if (trxId != Guid.Empty) { InfoLog($"Got TrxId: {trxId}"); var success = await CommitTransactionAsync(trxId, clientTrx); if (success) retryCount = 0; return success; } } else { InfoLog("Could not retrieve POS Item info!"); } } if (persistCurrentTransaction) { storeForwardTransaction = new StoreFowardTransaction(); storeForwardTransaction.TransactionItems.Add(CreateFuelingDoneItemFromFuelingTransaction(clientTrx)); transactionManager.SaveTransaction(storeForwardTransaction); storeForwardTransaction = null; InfoLog($"Persisting current transaction info, for pump: {clientTrx.PumpId}, amount: {clientTrx.Amount}"); } return false; //else //{ // await Task.Delay(5000); // return await SubmitTrxAsync(clientTrx, persistCurrentTransaction); //} //return false; } private FuelingDoneItem CreateFuelingDoneItemFromFuelingTransaction(ClientTrxInfo clientTrx) { FuelingDoneItem item = new FuelingDoneItem(); item.Barcode = clientTrx.Barcode; item.PumpId = clientTrx.PumpId; item.NozzleId = clientTrx.NozzleId; item.SiteNozzleNo = clientTrx.SiteNozzleNo; item.FPosSqNo = clientTrx.FPosSqNo; item.FdcSqNo = clientTrx.FdcSqNo; item.TimeStamp = clientTrx.TimeStamp; item.Volume = clientTrx.Volume; item.Amount = clientTrx.Amount; item.PayAmount = clientTrx.PayAmount; item.UnitPrice = clientTrx.UnitPrice; item.SeqNo = clientTrx.SeqNo; item.CardNo = clientTrx.CardNo; item.CurrentCardBalance = clientTrx.CurrentCardBalance; item.FuelingStartTime = clientTrx.FuelingStartTime; item.FuelingFinishedTime = clientTrx.FuelingFinishedTime; item.VolumeTotalizer = clientTrx.VolumeTotalizer; item.CardHolder = clientTrx.CardHolder; item.AccountName = clientTrx.AccountName; return item; } private ClientTrxInfo CreateFuelingTransactionFromFuelingDoneItem(FuelingDoneItem item) { ClientTrxInfo clientTrxInfo = new ClientTrxInfo(); clientTrxInfo.Barcode = item.Barcode; clientTrxInfo.PumpId = item.PumpId; clientTrxInfo.NozzleId = item.NozzleId; clientTrxInfo.SiteNozzleNo = item.SiteNozzleNo; clientTrxInfo.FPosSqNo = item.FPosSqNo; clientTrxInfo.FdcSqNo = item.FdcSqNo; clientTrxInfo.TimeStamp = item.TimeStamp; clientTrxInfo.Volume = item.Volume; clientTrxInfo.Amount = item.Amount; clientTrxInfo.PayAmount = item.PayAmount; clientTrxInfo.UnitPrice = item.UnitPrice; clientTrxInfo.SeqNo = item.SeqNo; clientTrxInfo.CardNo = item.CardNo; clientTrxInfo.CurrentCardBalance = item.CurrentCardBalance; clientTrxInfo.FuelingStartTime = item.FuelingStartTime; clientTrxInfo.FuelingFinishedTime = item.FuelingFinishedTime; clientTrxInfo.VolumeTotalizer = item.VolumeTotalizer; clientTrxInfo.CardHolder = item.CardHolder; clientTrxInfo.AccountName = item.AccountName; return clientTrxInfo; } private async Task GetTokenAsync(string userName, string password, string baseUrl) { InfoLog("GetTokenAsync..."); _client.DefaultRequestHeaders.Clear(); string tokenUrl = string.Concat(baseUrl, tokenAuthPath); var formParam = new AuthenticationParameter(grantType, userName, password); CancellationTokenSource cts = new CancellationTokenSource(); cts.CancelAfter(5000); HttpResponseMessage response; try { response = await _client .PostAsync(tokenUrl, new FormUrlEncodedContent(formParam.Params), cts.Token) .ConfigureAwait(false); } catch (Exception ex) { ErrorLog("Exception in retrieving auth token: " + ex.ToString()); response = null; currentAuthToken = null; return null; } InfoLog($"Response for getting token, Response==null? {response == null}"); if (response != null && response.IsSuccessStatusCode) { await response.Content.ReadAsStringAsync().ContinueWith(x => { currentAuthToken = JsonConvert.DeserializeObject(x?.Result); if (!string.IsNullOrEmpty(currentAuthToken.BusinessUnitsJsonString)) { currentAuthToken.BusinessUnitList = JsonConvert.DeserializeObject>(currentAuthToken.BusinessUnitsJsonString); } }); } else { var content = await response.Content.ReadAsStringAsync(); if (response != null) response.Content?.Dispose(); currentAuthToken = null; } return currentAuthToken; } private async Task GetPosItemAsync(string url, string itemId, AuthToken token) { InfoLog("GetPosItemAsync..."); if (storeForwardTransaction != null) { var productCodeIdentifiedItem = (ProductCodeIdentitiedItem)storeForwardTransaction.TransactionItems.FirstOrDefault(t => t is ProductCodeIdentitiedItem); if (productCodeIdentifiedItem != null) { InfoLog($"ProductCode already identified, id: {productCodeIdentifiedItem.ItemId}"); return new PosItem { Id = productCodeIdentifiedItem.ItemId }; } } if (storeForwardTransaction == null && !persistCurrentTransaction) { InfoLog("In the middle of retrying, but somehow the sf trans is gone, abort"); return null; } PosItem posItem = null; _client.DefaultRequestHeaders.Clear(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, token.AccessToken); InfoLog($"{_client.DefaultRequestHeaders.Authorization.Scheme}, {_client.DefaultRequestHeaders.Authorization.Parameter}"); BusinessUnitId = currentAuthToken.BusinessUnitList.Count > 0 ? currentAuthToken.BusinessUnitList.First().Id.ToString() : string.Empty; //Be aware, DeviceSN is `required` after 2019.04 for Smart Fuel v0.5 _client.DefaultRequestHeaders.Add("DeviceSN", DeviceSN); _client.DefaultRequestHeaders.Add("CurrentBuId", BusinessUnitId); InfoLog($"DeviceSN: {DeviceSN}"); InfoLog($"BuId: {BusinessUnitId}"); var productUrl = string.Concat(url, itemId); InfoLog($"Itme url: {productUrl}"); HttpResponseMessage response; try { response = await _client.GetAsync(productUrl).ConfigureAwait(false); } catch (Exception ex) { ErrorLog("Exception in retrieving pos item info: " + ex.ToString()); if (persistCurrentTransaction) { ErrorLog("Exception occurred during get item, save transaction!"); transactionManager.SaveTransaction(storeForwardTransaction); } response = null; return null; } InfoLog($"GetItem, StatusCode: {response.StatusCode}"); if (response.IsSuccessStatusCode) { await response.Content.ReadAsStringAsync().ContinueWith(x => { posItem = JsonConvert.DeserializeObject(x?.Result); if (storeForwardTransaction == null) ErrorLog("SF trans is null"); if (!storeForwardTransaction.TransactionItems.Any(t => t is ProductCodeIdentitiedItem)) { InfoLog("No existing ProductCodeIdentifiedItem"); storeForwardTransaction.TransactionItems.Add(new ProductCodeIdentitiedItem { ItemId = posItem.Id }); } else { InfoLog("ProductCodeIdentifiedItem already exists!"); } }); } else { var content = await response.Content.ReadAsStringAsync(); response.Content?.Dispose(); if (persistCurrentTransaction) { ErrorLog("Failure occurred during get item, save it!"); transactionManager.SaveTransaction(storeForwardTransaction); } } return posItem; } private async Task CreateTransactionAsync(PosItem posItem, ClientTrxInfo clientTrxInfo) { InfoLog($"CreateTransactionAsync, pump: {clientTrxInfo.PumpId}, amount: {clientTrxInfo.Amount}"); InfoLog($"Volume totalizer: {clientTrxInfo.VolumeTotalizer}"); if (storeForwardTransaction != null) { var transCreatedItem = (TransactionCreatedItem)storeForwardTransaction.TransactionItems.FirstOrDefault(t => t is TransactionCreatedItem); if (transCreatedItem != null) { InfoLog($"Transaction already rung up, id: {transCreatedItem.TransactionId}"); return transCreatedItem.TransactionId; } } Guid trxId = Guid.Empty; var trxUrl = string.Concat(TransactionServiceBaseUrl, "api/transactions"); var clientPosTrx = new ClientPosTrx { RequestingCreationTimeInPosClient = DateTime.Now, Type = PosTrxType.Sale, Source = PosTrxSource.Outdoor, Items = new List { new ClientRingUpPosItem { PosItemUniqueId = posItem.Id, //GUID Qty = clientTrxInfo.Volume, ClientRingUpTime = DateTime.Now, FuelItemSoldOnPumpId = clientTrxInfo.PumpId, FuelItemSoldOnPumpNozzleId = clientTrxInfo.SiteNozzleNo,//SiteNozzles[(clientTrxInfo.PumpId, clientTrxInfo.NozzleId)],//fdcTrx.Nozzle.LogicalId, FuelItemOriginalGrossAmount = clientTrxInfo.Amount,//(decimal)trdInfo.Mon.Value / 100, FuelItemFdcTransactionSeqNo = Convert.ToString(clientTrxInfo.SeqNo), fuelItemTransactionEndTime = clientTrxInfo.FuelingFinishedTime, TransactionComment = "IC Trx", FuelItemPumpTotalizerVolume = clientTrxInfo.VolumeTotalizer } } }; _client.DefaultRequestHeaders.Clear(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, currentAuthToken.AccessToken); _client.DefaultRequestHeaders.Add("DeviceSN", DeviceSN); _client.DefaultRequestHeaders.Add("CurrentBuId", BusinessUnitId); HttpResponseMessage response; try { response = await _client.PostAsJsonAsync(trxUrl, clientPosTrx).ConfigureAwait(false); } catch (Exception ex) { ErrorLog("Exception in creating transaction: " + ex.ToString()); ErrorLog("Exception occurred during creat transaction, save it!"); transactionManager.SaveTransaction(storeForwardTransaction); response = null; return Guid.Empty; } InfoLog($"CreateTransaction, StatusCode: {response.StatusCode}"); if (response.IsSuccessStatusCode) { await response.Content.ReadAsStringAsync().ContinueWith(x => { var trxCreationResponse = JsonConvert.DeserializeObject(x.Result); trxId = trxCreationResponse.Id; if (!storeForwardTransaction.TransactionItems.Any(t => t is TransactionCreatedItem)) { InfoLog("No existing TransactionCreatedItem"); storeForwardTransaction.TransactionItems.Add(new TransactionCreatedItem { TransactionId = trxId }); } else { InfoLog("TransactionCreatedItem already exists!"); } }); } else { var content = await response.Content.ReadAsStringAsync(); response.Content?.Dispose(); if (persistCurrentTransaction) { ErrorLog("Failure during Create Transaction, save it!"); transactionManager.SaveTransaction(storeForwardTransaction); } } return trxId; } private async Task CommitTransactionAsync(Guid trxId, ClientTrxInfo clientTrxInfo) { bool submitted = false; var payUrlTemplate = string.Concat(TransactionServiceBaseUrl, "api/transactions/{0}/payment"); string realPayUrl = string.Format(payUrlTemplate, trxId); //Payment detail for receipt string version = "1.0"; var icPayDetail = new { CardNo = clientTrxInfo.CardNo, PayAmount = clientTrxInfo.PayAmount, CardHolder = clientTrxInfo.CardHolder, AccountName = clientTrxInfo.AccountName, CardBalance = clientTrxInfo.CurrentCardBalance, Version = version }; PosTrxMop posTrxMop = new PosTrxMop(); posTrxMop.Paid = clientTrxInfo.PayAmount; posTrxMop.ICCardNumber = clientTrxInfo.CardNo; posTrxMop.RawResult = JsonConvert.SerializeObject(icPayDetail); posTrxMop.PosMopId = Guid.Parse("ff64ab36-658d-40a4-94bf-bee7231ac788"); posTrxMop.Mop = new PosMop { Id = Guid.Parse("ff64ab36-658d-40a4-94bf-bee7231ac788"), Name = "IC", PaymentId = 4, CreatedDateTime = DateTime.Now, ChangesetId = Guid.Parse("84f02b52-6950-4c50-a0b1-9827d6459edc"), TargetBusinessUnitId = Guid.Parse("6c40e7f6-2b2d-40de-8c5c-f5693a05ab0d"), DisplayName = "IC卡" }; _client.DefaultRequestHeaders.Clear(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, currentAuthToken.AccessToken); _client.DefaultRequestHeaders.Add("DeviceSN", DeviceSN); _client.DefaultRequestHeaders.Add("CurrentBuId", BusinessUnitId); HttpResponseMessage response; try { response = await _client.PostAsJsonAsync(realPayUrl, posTrxMop).ConfigureAwait(false); } catch (Exception ex) { ErrorLog("Exception in committing transaction: " + ex.ToString()); ErrorLog("Exception occurred during commit, save transaction!"); transactionManager.SaveTransaction(storeForwardTransaction); response = null; return false; } InfoLog($"Commit, StatusCode: {response.StatusCode}"); if (response.StatusCode == System.Net.HttpStatusCode.Conflict) InfoLog("Status code: Conflict, treat it as success"); if (response.IsSuccessStatusCode || response.StatusCode == System.Net.HttpStatusCode.Conflict) { submitted = true; if (storeForwardTransaction != null) storeForwardTransaction.TransactionItems.Add(new CommittedItem { Success = true }); InfoLog("Since submit is successful, discard the sf trans"); storeForwardTransaction = null; } else { if (persistCurrentTransaction) { ErrorLog("Failure during commit, save transaction!"); transactionManager.SaveTransaction(storeForwardTransaction); } } return submitted; } #endregion #region Public methods public bool SubmitTrx(TTrdinfo trdInfo, int fdcTrxSqNo, FdcTransaction fdcTrx) { InfoLog("\n"); currentFPosSqNo = (ushort)trdInfo.SeqNo; currentFdcSqNo = fdcTrx.SequenceNumberGeneratedOnPhysicalPump; // // Reserve the transaction // var reserved = fPosDbManager.ReserveTransaction(currentFPosSqNo, currentFdcSqNo); if (!reserved) { InfoLog($"Unable to reserve the transaction, FPos SqNo: {currentFPosSqNo}, FC SqNo: {currentFdcSqNo}"); return false; } InfoLog($"Transaction must be reserved now."); AuthToken currentAuthToken = null; if (_authToken != null) { InfoLog($"Valid? {_authToken.IsTokenValid()}"); } // Try first to utilize the existing valid token // If it's expired, then retrieve a new token from host if (_authToken != null && _authToken.IsTokenValid()) { InfoLog("Token is still valid, no need to get a new one."); currentAuthToken = _authToken; } else { InfoLog("Token not valid anymore, get a new one."); // //Get auth token // currentAuthToken = GetAccessToken(User, Password, AuthServiceBaseUrl); } if (currentAuthToken != null) { InfoLog($"AccessToken: {currentAuthToken.AccessToken}"); string productItemUrl = string.Concat(TransactionServiceBaseUrl, "api/products/itemId/"); // //Get POS item // PosItem posItem = GetPosItemByItemId(productItemUrl, Convert.ToString(fdcTrx.Barcode), currentAuthToken); InfoLog($"Barcode: {fdcTrx.Barcode}, PosItem, id = {posItem.Id}"); if (posItem == null) { InfoLog("Unable to retrieve the PosItem for current transaction"); return false; } // // Create transaction // InfoLog("Creating transaction..."); var posTrxId = CreateTransaction(trdInfo, posItem, /* fdcTrxSqNo,*/ currentAuthToken, fdcTrx); InfoLog($"CreateTransaction returns id: {posTrxId}"); if (posTrxId == Guid.Empty) { InfoLog("Unable to create transaction on cloud."); return false; } // //Commit payment // return CommitTransaction(posTrxId, trdInfo, currentAuthToken); } else { InfoLog($"Error, could not get an access token, abort submitting current transaction..."); return false; } } #endregion #region Get access token private AuthToken GetAccessToken(string userName, string password, string baseUrl) { InfoLog("Try to get Access Token."); _client.DefaultRequestHeaders.Clear(); string tokenUrl = string.Concat(baseUrl, tokenAuthPath); HttpResponseMessage tokenResponse = null; var formParam = new AuthenticationParameter(grantType, userName, password); try { tokenResponse = _client.PostAsync(tokenUrl, new FormUrlEncodedContent(formParam.Params)).Result; } catch (AggregateException aex) { InfoLog("Exception in GetAccessToken."); var e = aex.Flatten(); for (int i = 0; i < e.InnerExceptions.Count; i++) { logger.Info(e.InnerExceptions[i].InnerException.Message); } } catch (Exception ex) { InfoLog("Exception: " + ex.Message + " Inner exception: " + ex.InnerException.Message); } if (tokenResponse != null) { InfoLog($"Token response status code: {tokenResponse.StatusCode}"); } else { bool unreserveResult = fPosDbManager.UnreserveTransaction(currentFPosSqNo, currentFdcSqNo); InfoLog($"Token response null, unreserve at GetToken, success {unreserveResult}"); return null; } if (!tokenResponse.IsSuccessStatusCode) { InfoLog("Failed to get Access Token for user: " + userName); bool unreserveResult = fPosDbManager.UnreserveTransaction(currentFPosSqNo, currentFdcSqNo); InfoLog($"Unreserve at GetToken, success {unreserveResult}"); return null; } _authToken = tokenResponse.Content.ReadAsAsync().Result; _authToken.TokenRetrievedTime = DateTime.Now; return _authToken; } #endregion #region Get PosItem by ItemId /// /// Get the PosItem by the barcode /// /// Service URL /// Barcode for now. /// Auth token. /// private PosItem GetPosItemByItemId(string url, string itemId, AuthToken token) { var trx = fPosDbManager.GetCurrentTrx(currentFPosSqNo, currentFdcSqNo); if (trx.PosItemId != null) { if (!trx.PosItemId.Equals(Guid.Empty)) return new PosItem { Id = trx.PosItemId }; } try { _client.DefaultRequestHeaders.Clear(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, token.AccessToken); _client.DefaultRequestHeaders.Add("DeviceSN", DeviceSN); var productUrl = string.Concat(url, itemId); var productResponse = _client.GetAsync(productUrl).Result; if (productResponse == null) { ErrorLog($"Error in getting the product by ItemId"); return null; } InfoLog($"GetPosItemByItemId, StatusCode: {productResponse.StatusCode}"); //If it's Success, then try to get the item, otherwise, unreserve transaction and return null if (productResponse.IsSuccessStatusCode) { var setting = new JsonSerializerSettings(); setting.Converters.Add(new PosJsonDateConverter()); PosItem item = JsonConvert.DeserializeObject(productResponse.Content.ReadAsStringAsync().Result, setting); fPosDbManager.SetPosItemId(currentFPosSqNo, currentFdcSqNo, item.Id); return item; } } catch (AggregateException aex) { InfoLog("oooo Exception in GetPosItemByItemId: " + aex.ToString()); } bool unreserveResult = fPosDbManager.UnreserveTransaction(currentFPosSqNo, currentFdcSqNo); InfoLog($"Unreserving transaction FPos SqNo: {currentFdcSqNo}, Fdc SqNo: {currentFdcSqNo}, success? {unreserveResult}"); return null; } #endregion #region Create transaction private Guid CreateTransaction(TTrdinfo trdInfo, PosItem posItem, /*int fdcTrxSqNo,*/ AuthToken token, FdcTransaction fdcTrx) { //var testUrl = //"http://ipos.biz:8699/api/transactions"; var currentFPosTrx = fPosDbManager.GetCurrentTrx(currentFPosSqNo, currentFdcSqNo); if (currentFPosTrx != null) { if (!currentFPosTrx.CloudTrxId.Equals(Guid.Empty)) return currentFPosTrx.CloudTrxId; } var trxUrl = string.Concat(TransactionServiceBaseUrl, "api/transactions"); var clientPosTrx = new ClientPosTrx { RequestingCreationTimeInPosClient = DateTime.Now, Type = PosTrxType.Sale, Source = PosTrxSource.Outdoor, Items = new List { new ClientRingUpPosItem { PosItemUniqueId = posItem.Id, //GUID Qty = (decimal)trdInfo.Vol.Value /100, ClientRingUpTime = DateTime.Now, FuelItemSoldOnPumpId = fdcTrx.Nozzle.PumpId, FuelItemSoldOnPumpNozzleId = SiteNozzles[(fdcTrx.Nozzle.PumpId, fdcTrx.Nozzle.LogicalId)],//fdcTrx.Nozzle.LogicalId, FuelItemOriginalGrossAmount = currentFPosTrx.FillingAmount,//(decimal)trdInfo.Mon.Value / 100, FuelItemFdcTransactionSeqNo = Convert.ToString(fdcTrx.SequenceNumberGeneratedOnPhysicalPump), //fuelItemTransactionEndTime = trdInfo.TtctimeEnd, TransactionComment = "IC Trx" } } }; InfoLog($"GrossAmount: {clientPosTrx.Items.First().FuelItemOriginalGrossAmount}"); InfoLog($"FdcTrxSqNo: {clientPosTrx.Items.First().FuelItemFdcTransactionSeqNo}"); _client.DefaultRequestHeaders.Clear(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, token.AccessToken); _client.DefaultRequestHeaders.Add("DeviceSN", DeviceSN); try { var trxCreationResponse = _client.PostAsJsonAsync(trxUrl, clientPosTrx); InfoLog($"CreateTransaction, StatusCode: {trxCreationResponse.Result.StatusCode}"); if (trxCreationResponse.IsCompletedSuccessfully) { var trxResp = trxCreationResponse.Result.Content.ReadAsAsync(); fPosDbManager.SetTransactionId(currentFPosSqNo, currentFdcSqNo, trxResp.Result.Id); return trxResp.Result.Id; } else { return Guid.Empty; } } catch (AggregateException aex) { InfoLog("Exception in CreateTransaction: " + aex.ToString()); } fPosDbManager.UnreserveTransaction(currentFPosSqNo, currentFdcSqNo); return Guid.Empty; } #endregion #region Transaction commit private bool CommitTransaction(Guid id, TTrdinfo trdInfo, AuthToken token) { InfoLog("Start to commit transaction."); var trx = fPosDbManager.GetCurrentTrx(currentFPosSqNo, currentFdcSqNo); if (trx != null && trx.Submitted) { InfoLog($"This is interesting, Trx {trx.CloudTrxId} with FPos SqNo {currentFPosSqNo}, FC SqNo {currentFdcSqNo} already submitted!"); return true; } //var testUrl = "http://ipos.biz:8699/api/transactions/{0}/payment"; var payUrlTemplate = string.Concat(TransactionServiceBaseUrl, "api/transactions/{0}/payment"); string realPayUrl = string.Format(payUrlTemplate, id); //Amount of actual payment decimal payAmount = (decimal)trdInfo.RealMon.Value / 100; //Payment detail for receipt string version = "1.0"; var icPayDetail = new { CardNo = trdInfo.CardNo, PayAmount = payAmount, CardBalance = (decimal)trdInfo.CardBal.Value / 100, Version = version }; PosTrxMop posTrxMop = new PosTrxMop(); posTrxMop.Paid = payAmount; posTrxMop.ICCardNumber = trdInfo.CardNo; posTrxMop.RawResult = JsonConvert.SerializeObject(icPayDetail); posTrxMop.PosMopId = Guid.Parse("ff64ab36-658d-40a4-94bf-bee7231ac788"); posTrxMop.Mop = new PosMop { Id = Guid.Parse("ff64ab36-658d-40a4-94bf-bee7231ac788"), Name = "IC", PaymentId = 4, CreatedDateTime = DateTime.Now, ChangesetId = Guid.Parse("84f02b52-6950-4c50-a0b1-9827d6459edc"), TargetBusinessUnitId = Guid.Parse("6c40e7f6-2b2d-40de-8c5c-f5693a05ab0d"), DisplayName = "IC卡" }; _client.DefaultRequestHeaders.Clear(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, token.AccessToken); _client.DefaultRequestHeaders.Add("DeviceSN", DeviceSN); try { var payResponse = _client.PostAsJsonAsync(realPayUrl, posTrxMop); InfoLog($"Commit Transaction, PayAmount: {payAmount}, StatusCode: {payResponse.Result.StatusCode}"); if (payResponse.Result.IsSuccessStatusCode) { fPosDbManager.SetTransactionSubmitted(currentFPosSqNo, currentFdcSqNo, true); var receipt = GetHtmlReceipt(token, id, 30); if (!string.IsNullOrEmpty(receipt)) { OnReceiptReceived?.Invoke(this, new ReceiptReceivedEventArgs { NozzleNo = SiteNozzles[(trx.PumpId, trx.NozzleId)], ReceiptData = receipt }); } return true; } bool unreserveResult = fPosDbManager.UnreserveTransaction(currentFPosSqNo, currentFdcSqNo); InfoLog($"Unreserve at Commit, success? {unreserveResult}"); return false; } catch (AggregateException aex) { InfoLog("oooo Exception in CommitPayment: " + aex.ToString()); } return false; } #endregion #region Get receipt private string GetHtmlReceipt(AuthToken token, Guid trxId, int lineWidth = 30) { string relativeUrl = "api/receipt/{0}/maxChar/{1}"; InfoLog("Start to get the receipt..."); _client.DefaultRequestHeaders.Clear(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, token.AccessToken); _client.DefaultRequestHeaders.Add("DeviceSN", DeviceSN); try { string receiptUrl = TransactionServiceBaseUrl + string.Format(relativeUrl, trxId, lineWidth); DebugLog($"ReceiptUrl: {receiptUrl}"); var result = _client.GetAsync(receiptUrl); if (result.Result.IsSuccessStatusCode) { InfoLog($"Getting receipt, StatusCode: {result.Result.StatusCode}"); return result.Result.Content.ReadAsStringAsync().Result; } else { InfoLog($"Getting receipt, result? {result.Result.StatusCode}"); return string.Empty; } } catch (Exception ex) { InfoLog($"Retrieving receipt exception: {ex}"); } return string.Empty; } #endregion #region Log methods private void InfoLog(string log) { if (logger.IsInfoEnabled) logger.Info($"{Id} " + log); } private void DebugLog(string log) { if (logger.IsDebugEnabled) logger.Debug($"{Id}" + log); } private void ErrorLog(string log) { if (logger.IsErrorEnabled) logger.Error($"{Id} " + log); } #endregion } }