using Dfs.WayneChina.HengshanPayTerminal;
using Dfs.WayneChina.HengshanPayTerminal.MessageEntity;
using Dfs.WayneChina.HengshanPayTerminal.MessageEntity.Incoming;
using Dfs.WayneChina.HengshanPayTerminal.MessageEntity.Outgoing;
using Dfs.WayneChina.SpsDbManager;
using Dfs.WayneChina.SpsDbManager.ResultSet;
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Wayne.FDCPOSLibrary;
using Edge.Core.Processor;using Edge.Core.IndustryStandardInterface.Pump;
using Dfs.WayneChina.HengshanPayTerminal.Support;
using System.Timers;
using Dfs.WayneChina.IPosPlus.ServiceClient;
using System.Threading.Tasks;
using System.Collections.Concurrent;
namespace Dfs.WayneChina.IPosPlus
{
///
/// An entity that manages the IC card terminal.
///
public class TerminalManager
{
#region Fields
private readonly byte STX = 0xFA;
///
/// Sps_db database manager.
///
private readonly SpsManager _dbManager;
///
/// The terminal handler.
///
private readonly HengshanPayTermHandler _terminal;
///
/// IPosPlusApp instance.
///
private readonly IPosPlusApp _posApp;
///
/// Internally maintained pump state.
///
private HengshanPumpState _pumpState;
private HengshanPumpState _lastPumpState;
///
/// All listed cards (Newly-added blacklist, base blacklist, newly-deleted blacklist, whitelist)
///
public VersionedListedCard VersionedListedCard;
///
/// Connection detection timer.
///
private Timer timer;
private int interval;
private HengshanPayTerminal.Parser parser = new HengshanPayTerminal.Parser();
private CardAppType _cardAppType = CardAppType.CpuCard;
private WorkMode currentWorkMode = WorkMode.Disabled;
private IList currentDataVersions = null;
#endregion
#region Filling control state data
private FillingInfo currentFilling;
private FillingInfo lastFilling;
#endregion
#region Logger
private static NLog.Logger logger = NLog.LogManager.LoadConfiguration("NLog.config").GetLogger("IPosPlusApp");
#endregion
#region Queue
private Queue activeSendQueue = new Queue();
private object sendQueueSyncObj = new object();
#endregion
#region Payment queue
private System.Threading.Thread thread;
private ConcurrentQueue paymentRequestQueue = new ConcurrentQueue();
private System.Threading.ManualResetEvent mre = new System.Threading.ManualResetEvent(false);
#endregion
#region Reserved balance
private readonly int reservedBalance = 0;
#endregion
#region Constructor
public TerminalManager(IPosPlusApp posApp, int pumpId, int subAddress, SpsManager spsManager, HengshanPayTermHandler terminal,
int interval, int cardAppType)
{
_posApp = posApp;
PumpId = pumpId;
SubAddress = subAddress;
_dbManager = spsManager;
_terminal = terminal;
_pumpState = HengshanPumpState.Idle;
_lastPumpState = HengshanPumpState.Idle;
this.interval = interval;
if (cardAppType == 1)
_cardAppType = CardAppType.RfCard;
else if (cardAppType == 2)
_cardAppType = CardAppType.CpuCard;
reservedBalance = posApp.ReservedBalance;
RegisterEncodingProvider();
InitializeTimer();
thread = new System.Threading.Thread(() => HandleCardPayment());
thread.Start();
}
private void RegisterEncodingProvider()
{
EncodingProvider provider = CodePagesEncodingProvider.Instance;
Encoding.RegisterProvider(provider);
}
#endregion
#region Properties
public string TerminalId { get; private set; }
public string Identifier => _terminal.PumpIdList;
public int PumpId { get; private set; }
public int SubAddress { get; }
public DiscountServiceClient DiscountClient => _posApp.DiscountServiceClient;
#endregion
#region Timer
private void InitializeTimer()
{
timer = new Timer();
timer.Interval = interval * 1000;
timer.Elapsed += Timer_Elapsed;
timer.Start();
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
logger.Error($"*** Terminal {PumpId}, no incoming command from terminal/pump for {interval} seconds! Probably communication lost.");
_pumpState = HengshanPumpState.Offline;
if (_pumpState != _lastPumpState)
{
_terminal.UpdatePumpState(PumpId, 1, LogicalDeviceState.FDC_OFFLINE);
_lastPumpState = _pumpState;
}
// When pump is offline, it's offline.
if (_lastPumpState == HengshanPumpState.Offline)
{
timer.Stop();
}
}
private void ResetTimer()
{
timer.Stop();
logger.Debug($"terminal {PumpId.ToString()} timer stopped");
timer.Start();
logger.Debug($"terminal {PumpId.ToString()} timer started");
}
#endregion
#region Methods
#region Register 油机上电注册
public void HandleRegiser(RegisterRequest request)
{
logger.Info($"Terminal registration from pump {PumpId}, Terminal Id: {request.TerminalId}");
CheckTerminalId(request.TerminalId);
var registerResults = _dbManager.RegisterTerminal(request.TerminalId);
RegisterTerminalResult currentResult = null;
if (registerResults.Count > 0)
{
currentResult = registerResults[0];
}
SendRegisterResult(request, currentResult);
}
private void SendRegisterResult(RegisterRequest request, RegisterTerminalResult registerResult)
{
if (registerResult == null)
{
}
RegisterResult result = new RegisterResult
{
Prefix = STX,
SourceAddress = request.SourceAddress,
DestinationAddress = request.DestinationAddress,
FrameSqNoByte = request.FrameSqNoByte,
Handle = Convert.ToByte(Command.RegisterResult),
Result = Convert.ToByte(registerResult.RegisterResultCode),
TerminalId = request.TerminalId,
SystemKey = registerResult.SystemKey
};
if (!string.IsNullOrEmpty(registerResult.StationName))
{
result.StationNameLength = Convert.ToInt32(registerResult.StationNameLength);
result.StationName = Encoding.GetEncoding("GB2312").GetBytes(registerResult.StationName).ToList();
}
_terminal.Write(result);
}
#endregion
#region REGURLAR CHECK 油机普通查询命令处理
///
/// Handles the Check_cmd request.
/// 处理油机普通查询命令。
///
/// The Check_cmd request.
public void HandleCheckCmdRequest(CheckCmdRequest request)
{
CheckTerminalId(request.TerminalId);
ResetTimer();
if (request.WorkMode != (byte)currentWorkMode)
{
currentWorkMode = (WorkMode)_dbManager.GetPumpWorkMode(Convert.ToByte(PumpId));
if (request.WorkMode != 0x05)
{
ChangeAuthMode(request, currentWorkMode);
}
if (IsMessagePendingForSend())
{
var message = PickAndSendCardMessage();
logger.Info($"Active send queue, pending message exists, send it, MessageHandle={message.Handle}");
message.FrameSqNoByte = request.FrameSqNoByte;
_terminal.Write(message);
return;
}
}
//Reset the `Versioned listed cards`
VersionedListedCard = null;
logger.Debug($"Pump {PumpId}, Amount: {request.Amount}, Volume: {request.Volume}, Price: {request.Price}");
_terminal.StoreLatestFrameSqNo(PumpId, request.FrameSqNoByte);
if (_terminal.TrySendNextMessage())
{
//Really sent something, return!
logger.Debug("Will not send the CheckCmdResponse");
return;
}
var versions = _dbManager.GetDataVersions();
if (currentDataVersions == null)
{
currentDataVersions = versions;
}
if (currentDataVersions != null && versions != null)
{
var storedGeneralInfo = versions.FirstOrDefault(v => v.VersionType == VersionType.GeneralInfoVersion);
var currentGeneralInfo = currentDataVersions.FirstOrDefault(v => v.VersionType == VersionType.GeneralInfoVersion);
if (storedGeneralInfo != null && currentGeneralInfo != null)
{
if (currentGeneralInfo.VersionNo != storedGeneralInfo.VersionNo)
{
byte currentMode = _dbManager.GetPumpWorkMode(Convert.ToByte(PumpId));
if (currentMode != (byte)currentWorkMode)
{
ChangeAuthMode(request, (WorkMode)currentMode);
logger.Warn($"Auth mode changed!, {currentWorkMode} -> {currentMode}");
}
if (IsMessagePendingForSend())
{
var message = PickAndSendCardMessage();
logger.Info($"Active send queue, pending message exists, send it, MessageHandle={message.Handle}");
message.FrameSqNoByte = request.FrameSqNoByte;
_terminal.Write(message);
return;
}
}
}
}
SendCheckCmdResult(versions, request);
var currentNozzleNo = request.FuelingPoint.NozzleNo;
int currentLogicId;
bool getLogicIdResult = _terminal.NozzleLogicIdDict.TryGetValue(currentNozzleNo, out currentLogicId);
if (getLogicIdResult == false)
currentLogicId = 1;
//Get dispenser station from check cmd
if (request.DispenserState == 0x08)
{
_pumpState = HengshanPumpState.Fueling;
if (request.WorkMode == 0x05 && _lastPumpState != _pumpState)
{
EmulatePumpStateSequentialChange();
}
if (_lastPumpState != _pumpState)
{
logger.Info($"Pump {PumpId} aleady in fuelling, but last state was not, trigger a state change");
_terminal.UpdatePumpState(PumpId, currentLogicId, LogicalDeviceState.FDC_FUELLING);
}
_lastPumpState = _pumpState;
}
else if (request.DispenserState == 0x03)
{
_pumpState = HengshanPumpState.Idle;
if (_lastPumpState == HengshanPumpState.Fueling && _lastPumpState != _pumpState)
{
logger.Info($"Pump {PumpId}, Nozzle {currentNozzleNo}, fueling => ready");
_terminal.UpdatePumpState(PumpId, currentLogicId, LogicalDeviceState.FDC_READY);
}
else if (_lastPumpState == HengshanPumpState.Offline && _pumpState == HengshanPumpState.Idle)
{
logger.Info($"Pump {PumpId}, Nozzle {currentNozzleNo}, offline => ready");
_terminal.UpdatePumpState(PumpId, currentLogicId, LogicalDeviceState.FDC_READY);
}
}
if (_pumpState == HengshanPumpState.Idle)
{
//If the pump is idle, it's idle.
_lastPumpState = _pumpState;
}
else if (_pumpState == HengshanPumpState.Offline)
{
_pumpState = HengshanPumpState.Idle;
_terminal.UpdatePumpState(PumpId, currentLogicId, LogicalDeviceState.FDC_READY);
_lastPumpState = _pumpState;
}
else if (_pumpState == HengshanPumpState.Authorized)
{
if (request.Amount >= 0 || request.Volume >= 0)
{
logger.Info($"Pump {PumpId}, Nozzle {currentNozzleNo}, authorized => fuelling");
_pumpState = HengshanPumpState.Fueling;
_terminal.UpdatePumpState(currentNozzleNo, currentLogicId, LogicalDeviceState.FDC_FUELLING);
_lastPumpState = _pumpState;
}
}
else if (_pumpState == HengshanPumpState.Fueling)
{
logger.Info($"Pump {PumpId}, Nozzle {currentNozzleNo}, fueling in progress, Amount: {request.Amount}, Volume: {request.Volume}, Price: {request.Price}");
_terminal.UpdateFuelingStatus(PumpId, new FdcTransaction
{
Nozzle = new LogicalNozzle(PumpId, 1, Convert.ToByte(currentLogicId), null),
Amount = request.Amount,
Volumn = request.Volume,
Price = request.Price,
Finished = false
});
_lastPumpState = _pumpState;
}
}
private void EmulatePumpStateSequentialChange()
{
logger.Info("Emulating pump state sequential changes...");
_terminal.UpdatePumpState(PumpId, 1, LogicalDeviceState.FDC_CALLING);
System.Threading.Thread.Sleep(50);
_terminal.UpdatePumpState(PumpId, 1, LogicalDeviceState.FDC_AUTHORISED);
System.Threading.Thread.Sleep(50);
_terminal.UpdatePumpState(PumpId, 1, LogicalDeviceState.FDC_STARTED);
System.Threading.Thread.Sleep(50);
_terminal.UpdatePumpState(PumpId, 1, LogicalDeviceState.FDC_FUELLING);
System.Threading.Thread.Sleep(50);
}
///
/// Sends result of the Check_cmd.
/// 返回油机普通查询命令结果。
///
private void SendCheckCmdResult(IList dataVersions, CheckCmdRequest request)
{
if(dataVersions == null)
{
logger.Error("Retrieved empty version information");
return;
}
InquiryResult result = new InquiryResult
{
Prefix = STX,
SourceAddress = request.SourceAddress,
DestinationAddress = request.DestinationAddress,
FrameSqNoByte = request.FrameSqNoByte,
Handle = Convert.ToByte(Command.Ver_info),
BaseBlacklistVersion = dataVersions.First(d => d.VersionType == VersionType.BaseBlacklistVersion).VersionNo,
NewlyAddedBlacklistVersion = dataVersions.First(d => d.VersionType == VersionType.NewlyAddedBlacklistVersion).VersionNo,
NewlyDeletedBlacklistVersion = dataVersions.First(d => d.VersionType == VersionType.NewlyDeletedBlacklistVersion).VersionNo,
WhitelistVersion = dataVersions.First(d => d.VersionType == VersionType.WhitelistVersion).VersionNo,
FuelPriceVersion = dataVersions.First(d => d.VersionType == VersionType.FuelPriceChangeVersion).VersionNo,
StationGeneralInfoVersion = dataVersions.First(d => d.VersionType == VersionType.GeneralInfoVersion).VersionNo,
PrinterReceiptInfoVersion = 0x00,
SoftwareDownloadFlag = 0x00,
UnspecifiedField1 = 0x00,
};
result.SetTime(DateTime.Now);
_terminal.Write(result);
}
#endregion
#region Validate card 验卡
public void HandleCardValidation(ValidateCardRequest request)
{
ResetTimer();
var results = _dbManager.GetCheckCardResult(GetCardNo(request.Asn),
long.Parse(request.PhysicalCardNo, System.Globalization.NumberStyles.HexNumber), request.FuelingPoint.PumpNo);
var blacklistedCard = _dbManager.IsCardBlackListed(GetCardNo(request.Asn));
if (results!= null && results.Count == 1)
{
var currentResult = results[0];
var cardInfo = _dbManager.GetCardInfoByCardNo(GetCardNo(request.Asn));
if (cardInfo != null)
{
byte cardClass = cardInfo.CardClass.Value;
byte cardType = cardInfo.CardType.Value;
SendValidateCardResult(currentResult, request, blacklistedCard, cardClass, cardType, cardInfo.Holder, cardInfo.Carno);
}
else
{
SendValidateCardResult(currentResult, request, blacklistedCard, 0, 0, string.Empty, string.Empty);
}
}
}
private void SendValidateCardResult(CardResult checkResult, ValidateCardRequest request, bool blacklistedCard, byte cardClass,
byte cardType, string holder, string carNo)
{
var cardState = checkResult.CardStatus;
if (blacklistedCard)
cardState = SpsDbManager.ResultSet.CardState.AccountFrosen;
ValidateCardResult result = new ValidateCardResult();
result.Prefix = STX;
result.SourceAddress = request.SourceAddress;
result.DestinationAddress = request.DestinationAddress;
result.Handle = Convert.ToByte(Command.ValidateCardResult);
result.FrameSqNoByte = request.FrameSqNoByte;
result.Asn = request.Asn;
result.DiscountNo = Convert.ToUInt16(checkResult.DiscountNo);
result.CardState = (HengshanPayTerminal.Support.CardState)Convert.ToByte(cardState);
result.AdditionalInfo = GenerateAdditionalInfo(cardState);
result.AdditionalInfoLength = Convert.ToByte(result.AdditionalInfo.Count);
if (result.AdditionalInfoLength == 0)
{
List addInfo = new List();
if (cardClass == 1 || cardClass == 2)
{
addInfo.Add(1);
if (!string.IsNullOrEmpty(carNo))
addInfo.AddRange(Encoding.GetEncoding("GB2312").GetBytes(carNo));
else
addInfo.AddRange(Encoding.GetEncoding("GB2312").GetBytes(holder));
}
if (_cardAppType == CardAppType.CpuCard)
{
if (cardClass == 3 && cardType == 1)
{
addInfo.Add(1);
addInfo.AddRange(Encoding.GetEncoding("GB2312").GetBytes(holder));
}
}
else if (_cardAppType == CardAppType.RfCard)
{
if (cardClass == 3 && cardType == 3)
{
addInfo.Add(1);
addInfo.AddRange(Encoding.GetEncoding("GB2312").GetBytes(holder));
}
}
result.AdditionalInfo = addInfo;
result.AdditionalInfoLength = Convert.ToByte(addInfo.Count);
}
int reserveAmount = reservedBalance * 100;
result.CardBalance = request.Balance - reserveAmount;
int maxAllowedAmount = Convert.ToInt32(checkResult.MaxAllowedAmount);
result.MaxAllowedAmount = request.Balance - reserveAmount > maxAllowedAmount ? maxAllowedAmount : request.Balance - reserveAmount;
result.PhysicalCardNo = long.Parse(
ByteArrayToString(
StringToByteArray(request.PhysicalCardNo).Reverse().ToArray()), System.Globalization.NumberStyles.HexNumber);
_terminal.Write(result);
}
private List GenerateAdditionalInfo(SpsDbManager.ResultSet.CardState cardState)
{
if (cardState == SpsDbManager.ResultSet.CardState.NoInfo)
{
return Encoding.GetEncoding("GB2312").GetBytes("验卡失败,无此卡").ToList();
}
else if (cardState == SpsDbManager.ResultSet.CardState.Lost)
{
return Encoding.GetEncoding("GB2312").GetBytes("卡已锁定!").ToList();
}
return new List();
}
#endregion
#region Authorization 授权处理
public void HandlAuthorization(AuthRequest request)
{
ResetTimer();
var currentNozzleNo = request.FuelingPoint.NozzleNo;
int logicId;
bool getResult = _terminal.NozzleLogicIdDict.TryGetValue(currentNozzleNo, out logicId);
if (!getResult)
logicId = 1;
//var logicId = _terminal.NozzleLogicIdDict[currentNozzleNo];
_pumpState = HengshanPumpState.Calling;
_terminal.UpdatePumpState(PumpId, logicId, LogicalDeviceState.FDC_CALLING);
_lastPumpState = _pumpState;
SendAuthResult(request);
_pumpState = HengshanPumpState.Authorized;
_terminal.UpdatePumpState(PumpId, logicId, LogicalDeviceState.FDC_AUTHORISED);
_lastPumpState = _pumpState;
}
private void SendAuthResult(AuthRequest request)
{
var result = new AuthResult
{
Prefix = STX,
SourceAddress = request.SourceAddress,
DestinationAddress = request.DestinationAddress,
FrameSqNoByte = request.FrameSqNoByte,
Handle = Convert.ToByte(Command.AuthResult),
Asn = request.Asn,
PosTtc = request.PosTtc,
SeqNo = request.SeqNo,
FuelCode = request.FPCode.ToUInt16(),
ResultCode = HengshanPayTerminal.Support.AuthResultCode.Passed,
AdditionalInfoLength = 0
};
_terminal.Write(result);
}
#endregion
#region Fueling Data 加油数据
public void HandleFuelingData(FuelingDataRequest request)
{
ResetTimer();
var currentNozzleNo = request.FuelingPoint.NozzleNo;
int logicId;
bool getResult = _terminal.NozzleLogicIdDict.TryGetValue(currentNozzleNo, out logicId);
if (!getResult)
logicId = 1;
//var logicId = _terminal.NozzleLogicIdDict[currentNozzleNo];
var cardNo = GetCardNo(request.Asn);
var cardAccount = _dbManager.GetCardAccountInfo(cardNo);
var correctFuelPrice =
GetCorrectFuelPrice(Convert.ToString(request.ProductCode), request.Price, request.Volume, request.Amount);
if (request.CurrentCardInfo == null)
logger.Error("Card info is null!!!");
if (cardAccount == null)
{
logger.Error("card account is null");
_terminal.UpdateFuelingStatus(PumpId, new FdcTransaction
{
Nozzle = new LogicalNozzle(PumpId, 1, Convert.ToByte(logicId), null),
Amount = request.Amount,
Volumn = request.Volume,
Price = correctFuelPrice,//request.Price,
VolumeTotalizer = request.VolumeTotal,
SequenceNumberGeneratedOnPhysicalPump = request.PosTtc//, //I would like to use SeqNo, but TQC pumps always use 0 for it.
//Finished = true //Set FDC transaction as FINISHED
});
}
currentFilling = new FillingInfo
{
PumpId = this.PumpId,
NozzleId = request.FuelingPoint.NozzleNo,//1,
SequenceNo = request.PosTtc,
CardNo = cardNo,
CardType = request.CurrentCardInfo.CardType,
CardBalance = cardAccount == null ? 0 : Convert.ToInt32(cardAccount.Money),
FillingAmount = request.Amount,
Volume = request.Volume,
UnitPrice = correctFuelPrice,
//UnitPrice = request.Price,
FuelProductCode = request.ProductCode,
StartTime =GetDateTime(request.DispenserTime),
EndTime = GetDateTime(request.TransactionEndTime)
};
if (request.CurrentCardInfo.CardType != 0)
{
bool result = _dbManager.InsertAuthInfo(_posApp.PosId, 0, cardNo, request.Amount, request.Volume,
request.DispenserTime, request.TransactionEndTime, request.PosTtc, request.SeqNo, 0x50,
request.ProductCode, correctFuelPrice, request.CurrentCardInfo.CardCtc, 00, Convert.ToByte(PumpId),
Convert.ToByte(PumpId), Convert.ToInt32(cardAccount.Money), request.CurrentCardInfo.CardType,
cardAccount.DiscountNo, 1, request.VolumeTotal);
logger.Debug($"Insert auth info, success? {result}");
}
SendFuelingDataAck(request);
logger.Info($"Filling done, Amount: {request.Amount}, Volume: {request.Volume}, " +
$"corrected price: {correctFuelPrice}, pump price: {request.Price}, ready for payment");
}
private void SendFuelingDataAck(FuelingDataRequest request)
{
var result = new FuelingDataProcessingResult
{
Prefix = STX,
SourceAddress = request.SourceAddress,
DestinationAddress = request.DestinationAddress,
FrameSqNoByte = request.FrameSqNoByte,
Handle = Convert.ToByte(Command.FuelingDataResult),
PosTtc = request.PosTtc,
SeqNo = request.SeqNo,
FPCode = request.FPCode.ToUInt16()
};
_terminal.Write(result);
}
#endregion
#region Payment 支付
private void HandleCardPayment()
{
while (true)
{
logger.Info($"Terminal {PumpId}, in the loop");
if (paymentRequestQueue.Count > 0)
{
logger.Info($"Terminal {PumpId}, Payment request exists, send it! Queue item count: {paymentRequestQueue.Count}");
PaymentRequest request;
if (paymentRequestQueue.TryDequeue(out request))
{
SendPaymentData(request);
}
logger.Info($"Terminal {PumpId}, Payment request queue, item count: {paymentRequestQueue.Count}");
}
else
{
logger.Info($"Terminal {PumpId}, nothing in payment queue, put thread on hold");
mre.Reset();
mre.WaitOne();
}
}
}
public void HandlePaymentRequest(PaymentRequest request)
{
ResetTimer();
if (paymentRequestQueue.Count > 0)
{
if (paymentRequestQueue.Any(r => r.Asn == request.Asn && r.PosTtc == request.PosTtc))
{
logger.Info("Payment request already in the queue");
return;
}
}
else
{
paymentRequestQueue.Enqueue(request);
}
logger.Info($"Terminal {PumpId}, releasing payment request processing thread");
mre.Set();
//SendPaymentData(request);
}
private void SendPaymentData(PaymentRequest request)
{
int paymentAmount = 0;
int fillingAmount = 0;
int fillingPrice = 0;
int productCode = 0;
int volume = 0;
if (DiscountClient != null && currentFilling != null &&
(_posApp.CardAppType == 1 && currentFilling.CardType == 3 || _posApp.CardAppType == 2 && currentFilling.CardType == 1))
{
fillingAmount = currentFilling.FillingAmount;
fillingPrice = currentFilling.UnitPrice;
productCode = currentFilling.FuelProductCode;
volume = currentFilling.Volume;
logger.Info($"Applying fuel discount against customer card, amount: {fillingAmount}, price: {fillingPrice}, code: {productCode}");
DiscountRequest discountRequest = new DiscountRequest();
discountRequest.Barcode = _posApp.GetBarcode(currentFilling.FuelProductCode).ToString();
discountRequest.FillingAmount = Convert.ToDecimal(currentFilling.FillingAmount) / 100;
discountRequest.UnitPrice =
currentFilling.UnitPrice == 151 ?
Convert.ToDecimal( _posApp.CurrentFuelPrices[currentFilling.FuelProductCode.ToString()]) / 100
: Convert.ToDecimal(currentFilling.UnitPrice) / 100;
discountRequest.Volume = Convert.ToDecimal(currentFilling.Volume) / 100;
discountRequest.TimeStamp = DateTime.Now;
discountRequest.CardNo = GetCardNo(request.Asn);
var discountResponse = DiscountClient.CalculatDiscount(discountRequest).Result;
if (discountResponse != null)
{
paymentAmount = Convert.ToInt32(discountResponse.PayAmount * 100);
}
}
else
{
logger.Info($"Possible crash 1");
//var amountAfterDiscount = _dbManager.GetDiscountedAmount(request.Asn, fillingAmount, volume, fillingPrice, productCode);
if (currentFilling != null)
paymentAmount = currentFilling.FillingAmount;//amountAfterDiscount.Real_Mon;
}
if (currentFilling == null)
{
logger.Info($"Current Filling missing, could be reset");
return;
}
if (paymentAmount > currentFilling.FillingAmount)
{
logger.Warn($"This is absurd, payment amount {paymentAmount.ToString()} larger than filling amount");
paymentAmount = fillingAmount;
}
logger.Info($"payment amount is: {paymentAmount}");
PaymentData data = new PaymentData
{
Prefix = STX,
SourceAddress = request.SourceAddress,
DestinationAddress = request.DestinationAddress,
FrameSqNoByte = request.FrameSqNoByte,
Handle = Convert.ToByte(Command.PaymentData),
Asn = request.Asn,
PosTtc = request.PosTtc,
FillingAmount = fillingAmount,
PayAmount = paymentAmount == 0? fillingAmount : paymentAmount,
FillingVolume = volume,
Price = Convert.ToUInt16(fillingPrice),
ProductCode = Convert.ToUInt16(productCode),
C_Name = "00000000000000000000000000000000",
FPCode = request.FPCode.ToUInt16(),
Result = HengshanPayTerminal.Support.PaymentProcessingResult.Ok,
AdditionalInfoLength = 0
};
_terminal.Write(data);
mre.WaitOne();
}
#endregion
#region Transaction Data 交易数据
public async Task HandleTransactionData(TransactionDataRequest request)
{
ResetTimer();
var currentNozzleNo = request.FuelingPoint.NozzleNo;
int logicId;
bool getResult = _terminal.NozzleLogicIdDict.TryGetValue(currentNozzleNo, out logicId);
if (!getResult)
logicId = 1;
//var logicId = _terminal.NozzleLogicIdDict[currentNozzleNo];
logger.Info($"*** Handle TransactionData {request.Handle}, Pump {PumpId} ***");
byte trxType = request.TrxType;
var cardNo = GetCardNo(request.Asn);
var cardAccount = _dbManager.GetCardAccountInfo(cardNo);
if (cardAccount == null)
{
logger.Info($"Terminal {PumpId}, card account queried for card no: {cardNo}");
}
var creditResult = _dbManager.GetCredits(request.ProductCode, cardNo, request.FillingAmount, request.Volume);
logger.Debug("Credit queried");
bool trxExists = _dbManager.CheckIfTransactionExists(request.TrxTime, request.DitTTC, request.FuelingPoint.PumpNo, trxType);
logger.Debug($"Transaction Existence checked, Exists? {trxExists}");
var operatorCardNo = GetCardNo(request.PsamAsn);
bool ackSent = false;
var correctedFuelPrice =
GetCorrectFuelPrice(Convert.ToString(request.ProductCode), request.Price, request.Volume, request.FillingAmount);
//Make sure there is not an existing payment trx record.
if (!trxExists)
{
logger.Info("No existing record");
int payModeId = request.PaymentMethodLocation; //100;
string payModeNo = string.Empty;
int stationNo = 1;
if (_posApp.StationInfo != null)
stationNo = _posApp.StationInfo.StationNo;
uint volumeTotal = request.VolumeTotal < 0 ? 0 : Convert.ToUInt32(request.VolumeTotal);
var newGid = _dbManager.AddTrade(
stationNo,
cardNo,
operatorCardNo,
payModeId,
payModeNo,
trxType,
Convert.ToString(request.ProductCode),
correctedFuelPrice,//request.Price,
request.Volume,
request.FillingAmount,
request.PaymentAmount,
request.CardBalance,
request.CurrentCardInfo.CardType,
request.CurrentCardInfo.CardCtc,
GetDateTime(request.TrxTime),
GetDateTime(request.TrxEndTime),
request.DitTTC,
request.SeqNo,
_posApp.GetNextBillNo(), //billNo
request.FuelingPoint.NozzleNo,
request.FuelingPoint.PumpNo,
Convert.ToInt32(request.TerminalId.TrimStart('0')),
volumeTotal,
0, //discountNo
1001,
1,
request.PsamAsn, //PsamAsn
uint.Parse(request.PsamTac, System.Globalization.NumberStyles.HexNumber),//PsamTac
request.PsamTid,//psam tid
uint.Parse(request.PsamTtc, System.Globalization.NumberStyles.HexNumber),//psam ttc
uint.Parse(request.Tac, System.Globalization.NumberStyles.HexNumber),//tac
uint.Parse(request.Gmac, System.Globalization.NumberStyles.HexNumber),//gmac
uint.Parse(request.Tmac, System.Globalization.NumberStyles.HexNumber),//tmac
0, //Integral
Convert.ToInt32(_posApp.CurrentShiftNo),
"000000",
"000000",
"",
0);
logger.Info($"Trdinfo added, new Gid: {newGid}, TrdType: {trxType}, CardNo: {request.Asn}, CTC: {request.CurrentCardInfo.CardCtc}");
if (trxType == 0x01)
{
_dbManager.InsertGrayInfo(
cardNo,
payModeId,
trxType,
Convert.ToString(request.ProductCode),
correctedFuelPrice,//request.Price
request.Volume,
request.FillingAmount,
request.PaymentAmount,
request.CardBalance,
request.CurrentCardInfo.CardCtc,
request.TrxTime,
request.TrxEndTime,
Convert.ToUInt32(request.DitTTC),
request.SeqNo,
request.FuelingPoint.NozzleNo,
request.FuelingPoint.PumpNo,
request.TerminalId,
Convert.ToUInt64(request.VolumeTotal),
0,
request.PsamAsn,
uint.Parse(request.PsamTac, System.Globalization.NumberStyles.HexNumber),//PsamTac//request.PsamTac,
request.PsamTid,
uint.Parse(request.PsamTtc, System.Globalization.NumberStyles.HexNumber),//psam ttc
uint.Parse(request.Tac, System.Globalization.NumberStyles.HexNumber),//tac
uint.Parse(request.Gmac, System.Globalization.NumberStyles.HexNumber),//gmac
uint.Parse(request.Tmac, System.Globalization.NumberStyles.HexNumber),//tmac
0);
logger.Info("Gray info inserted");
}
else if (trxType == 0x02)
{
var releaseResult = _dbManager.ReleaseGrayCard(cardNo, request.CurrentCardInfo.CardCtc, request.TrxTime);
logger.Info($"Gray info deleted, card released success? {releaseResult}");
}
_dbManager.UpdateCardInfo(cardNo, request.FillingAmount, request.CardBalance, request.CurrentCardInfo.CardType,
creditResult.IntegralResult, 1);
logger.Debug("Card Info updated");
_dbManager.DeleteAuthInfo(request.FuelingPoint.PumpNo, request.DitTTC, request.TrxTime);
logger.Debug("Auth Info deleted");
if (trxType == 2)
{
logger.Info($"Trying to find the gray trade, ASN: {request.Asn}, CTC: {request.CurrentCardInfo.CardCtc}");
var grayTrd = _dbManager.GetGrayTrdInfo(request.Asn, request.CurrentCardInfo.CardCtc, request.FuelingPoint.PumpNo);
if (grayTrd != null)
{
logger.Info($"Found gray trade for card: {request.Asn}");
currentFilling = new FillingInfo
{
PumpId = this.PumpId,
NozzleId = request.FuelingPoint.NozzleNo,//1,
SequenceNo = request.DitTTC,
CardNo = cardNo,
CardBalance = request.CardBalance,//Convert.ToInt32(cardAccount.Money),
FillingAmount = grayTrd.Mon.HasValue ? grayTrd.Mon.Value : request.FillingAmount,
PayAmount = request.PaymentAmount,
Volume = request.Volume,
UnitPrice = correctedFuelPrice,//request.Price,
FuelProductCode = int.Parse(grayTrd.CommId), //request.ProductCode,
StartTime = GetDateTime(request.TrxTime),
EndTime = GetDateTime(request.TrxEndTime),
VolumeTotal = Convert.ToInt32(grayTrd.EndPumpId)//request.VolumeTotal
};
}
else
{
currentFilling = new FillingInfo
{
PumpId = this.PumpId,
NozzleId = request.FuelingPoint.NozzleNo,//1,
SequenceNo = request.DitTTC,
CardNo = cardNo,
CardBalance = request.CardBalance,//Convert.ToInt32(cardAccount.Money),
FillingAmount = request.FillingAmount,
PayAmount = request.PaymentAmount,
Volume = request.Volume,
UnitPrice = correctedFuelPrice,//request.Price,
FuelProductCode = request.ProductCode,
StartTime = GetDateTime(request.TrxTime),
EndTime = GetDateTime(request.TrxEndTime),
VolumeTotal = request.VolumeTotal
};
}
}
else
{
currentFilling = new FillingInfo
{
PumpId = this.PumpId,
NozzleId = request.FuelingPoint.NozzleNo,//1,
SequenceNo = request.DitTTC,
CardNo = cardNo,
CardBalance = request.CardBalance,//Convert.ToInt32(cardAccount.Money),
FillingAmount = request.FillingAmount,
PayAmount = request.PaymentAmount,
Volume = request.Volume,
UnitPrice = correctedFuelPrice,//request.Price,
FuelProductCode = request.ProductCode,
StartTime = GetDateTime(request.TrxTime),
EndTime = GetDateTime(request.TrxEndTime),
VolumeTotal = request.VolumeTotal
};
}
//Customer card, submit it.
if ((_posApp.CardAppType == 1 && request.CurrentCardInfo.CardType == 3
|| _posApp.CardAppType == 2 && request.CurrentCardInfo.CardType == 1) && request.TrxType != 2)
{
logger.Info($"Pump {PumpId}, there is a customer card transaction pending for upload, ttc: {currentFilling.SequenceNo}");
if (request.FillingAmount != 0)
_posApp.AddCustomerCardFilling(currentFilling);
}
if (request.FillingAmount != 0 && request.TrxType != 2)
{
if (_posApp != null)
{
//2023-08-22
SendTrasactionDataAck(request);
ackSent = true;
logger.Info("TransactionData ack sent");
//Should first upload the record to cloud...
logger.Info("Start force upload");
//_ = _posApp?.SpsDataCourier.TriggerTransacationLookup(newGid);
await _posApp?.SpsDataCourier.ForceTransactionUploadAsync(newGid).ContinueWith(t =>
{
logger.Info("Push transaction to FDC Server");
_terminal.UpdateFuelingStatus(PumpId, new FdcTransaction
{
Nozzle = new LogicalNozzle(PumpId, 1, Convert.ToByte(logicId), null),
Amount = request.FillingAmount,
Volumn = request.Volume,
Price = correctedFuelPrice,//request.Price,
VolumeTotalizer = request.VolumeTotal,
SequenceNumberGeneratedOnPhysicalPump = request.DitTTC, //I would like to use SeqNo, but TQC pumps always use 0 for it.
Finished = true //Set FDC transaction as FINISHED
});
_pumpState = HengshanPumpState.Idle;
_terminal.UpdatePumpState(PumpId, logicId, LogicalDeviceState.FDC_READY);
_lastPumpState = _pumpState;
});
}
//logger.Info("Push transaction to FDC Server");
//_terminal.UpdateFuelingStatus(PumpId, new FdcTransaction
//{
// Nozzle = new LogicalNozzle(PumpId, 1, 1, null),
// Amount = request.FillingAmount,
// Volumn = request.Volume,
// Price = request.Price,
// VolumeTotalizer = request.VolumeTotal,
// SequenceNumberGeneratedOnPhysicalPump = request.DitTTC, //I would like to use SeqNo, but TQC pumps always use 0 for it.
// Finished = true //Set FDC transaction as FINISHED
//});
}
//_pumpState = HengshanPumpState.Idle;
//_terminal.UpdatePumpState(PumpId, LogicalDeviceState.FDC_READY);
//_lastPumpState = _pumpState;
}
if (!ackSent)
{
logger.Info($"Pump {PumpId}, TransactionData ack not sent, send it now");
SendTrasactionDataAck(request);
}
if (currentFilling != null)
{
lastFilling = new FillingInfo
{
FillingAmount = currentFilling.FillingAmount,
Volume = currentFilling.Volume,
UnitPrice = currentFilling.UnitPrice,
FuelProductCode = currentFilling.FuelProductCode
};
}
else
{
logger.Info($"Pump {PumpId}, currentFilling is null");
}
if (currentFilling != null)
{
//重置当前的Filling
if (currentFilling.SequenceNo == request.DitTTC)
{
logger.Info($"Pump {PumpId}, reset current filling, ttc: {currentFilling.SequenceNo}");
currentFilling = null;
}
}
}
private void CheckTransactionType(byte trxType)
{
if (trxType == 0x00)
{
logger.Info("Normal card transaction");
}
else if (trxType == 0x01)
{
logger.Info("Gray card transaction");
}
else if (trxType == 0x02)
{
logger.Info("Ungray card transaction");
}
else if (trxType == 0x03)
{
logger.Info("Indoor card payment transaction");
}
else if (trxType == 0x04)
{
logger.Info("POS auth transaction (cash)");
}
else if (trxType == 0x05)
{
logger.Info("Cancel auth card transaction");
}
else if (trxType == 0x06)
{
logger.Info("Non card transaction");
}
else if (trxType == 0x07)
{
logger.Info("Cancel auth non card transaction");
}
else if (trxType == 0x08)
{
logger.Info("Fuel price download transaction");
}
else if (trxType == 0x09)
{
logger.Info("Other card release transaction");
}
else if (trxType == 0x0A)
{
logger.Info("Change fuel type transaction");
}
else if (trxType == 0x0B)
{
logger.Info("POS bank card transaction");
}
else
{
logger.Info("Unknown transaction type");
}
}
private void SendTrasactionDataAck(TransactionDataRequest request)
{
TransactionDataAck ack = new TransactionDataAck
{
Prefix = STX,
SourceAddress = request.SourceAddress,
DestinationAddress = request.DestinationAddress,
FrameSqNoByte = request.FrameSqNoByte,
Handle = Convert.ToByte(Command.TransactionDataAck),
TerminalId = request.TerminalId,
Result = 0,
UnspecifiedField1 = 0
};
_terminal.Write(ack);
}
private ushort GetCorrectFuelPrice(string fuelNo, int price, int volume, int amount)
{
try
{
var recordedPrice = Convert.ToInt32(_posApp.CurrentFuelPrices[fuelNo]);
if (recordedPrice != price)
{
logger.Info($"Price: {price} on pump: {PumpId}, is different from system recorded price: {recordedPrice}");
int calculatedAmount = price * volume;
int candidateAmount = recordedPrice * volume;
double calculatedDiff = Math.Abs(calculatedAmount - amount * 100);
double candidateDiff = Math.Abs(candidateAmount - amount * 100);
if (candidateDiff < calculatedDiff)
{
logger.Info($"Returning system recorded price: {recordedPrice}");
return Convert.ToUInt16(recordedPrice);
}
return Convert.ToUInt16(price);
}
}
catch (Exception ex)
{
logger.Error("Error in getting correct fuel price ", ex.ToString());
}
return Convert.ToUInt16(price);
}
#endregion
#region Lock/Unlock Pump 锁定/解锁油机
public void LockUnlockPump(LockUnlockOperation operation)
{
var request = new LockOrUnlockPumpRequest
{
Prefix = STX,
Handle = Convert.ToByte(Command.LockOrUnlockPump),
FPCode = EncodeFPCodeString(PumpId, PumpId),
OperationType = operation
};
PrepareSendMessage(request);
}
public void HandleLockUnlockPumpResult(LockOrUnlockPumpAck lockUnlockPumpResult)
{
if (lockUnlockPumpResult.DispenserState == DispenserState.Closed)
{
logger.Info($"Current pump {PumpId} is locked!");
//... should notify POS then.
}
else if (lockUnlockPumpResult.DispenserState == DispenserState.Idle)
{
logger.Info($"Current pump {PumpId} is idle (unlocked)!");
//... should notify POS then.
}
}
#endregion
#region Request Data 加油机请求下载数据
public void HandleDataDownloadRequest(DataDownloadRequest request)
{
ResetTimer();
//Reset
VersionedListedCard = null;
if (request.DataContentType == DataContentType.FuelPriceList)
{
logger.Info($"Terminal {PumpId} initiates fuel price list download");
}
else if (request.DataContentType == DataContentType.StationGeneralInfo)
{
logger.Info($"Terminal {PumpId} initiates station general info download");
}
else if (request.DataContentType == DataContentType.Whitelist)
{
logger.Info($"Terminal {PumpId} initiates whitelist download");
}
else if (request.DataContentType == DataContentType.NewlyAddedBlacklist)
{
logger.Info($"Terminal {PumpId} initiates newly added blacklist download");
}
else if (request.DataContentType == DataContentType.NewlyDeletedBlacklist)
{
logger.Info($"Terminal {PumpId} initiates newly deleted blacklist download");
}
else if (request.DataContentType == DataContentType.BaseBlacklist)
{
logger.Info($"Terminal {PumpId} initiates base blacklist download");
}
else if (request.DataContentType == DataContentType.StationGeneralInfo)
{
logger.Info($"Terminal {PumpId} initiates station general info download");
}
SendDataLength(request);
}
public void SendDataLength(DataDownloadRequest request)
{
DataBytesLength response = new DataBytesLength();
response.Prefix = STX;
response.SourceAddress = request.SourceAddress;
response.DestinationAddress = request.DestinationAddress;
response.Handle = (byte)Command.DataBytesLength;
response.FrameSqNoByte = request.FrameSqNoByte;
if (request.DataContentType == DataContentType.FuelPriceList)
{
response.DataContentType = request.DataContentType;
response.DataLength = 0x32;
logger.Info($"Fuel Price Update, Data Length: {response.DataLength}");
}
else if (request.DataContentType == DataContentType.Whitelist)
{
response.DataContentType = DataContentType.Whitelist;
var result = _dbManager.GetWhitelistedCards(_cardAppType);
if (result != null)
{
response.DataLength = 16 + 10 * result.Count;
logger.Info($"WhiteList, Data Length: {response.DataLength}");
}
}
else if (request.DataContentType == DataContentType.NewlyAddedBlacklist)
{
response.DataContentType = DataContentType.NewlyAddedBlacklist;
var result = _dbManager.GetNewlyAddedBlacklistedCards(_cardAppType);
if (result != null)
{
response.DataLength = 16 + 10 * result.Count;
logger.Info($"Incremental Black List, Data Length: {response.DataLength}");
}
}
else if (request.DataContentType == DataContentType.NewlyDeletedBlacklist)
{
response.DataContentType = DataContentType.NewlyDeletedBlacklist;
var result = _dbManager.GetNewlyDeletedBlacklistedCards(_cardAppType);
if (result != null)
{
response.DataLength = 16 + 10 * result.Count;
logger.Info($"Decremental Black List, Data Length: {response.DataLength}");
}
}
else if (request.DataContentType == DataContentType.BaseBlacklist)
{
response.DataContentType = DataContentType.BaseBlacklist;
var result = _dbManager.GetBaseBlacklistedCards(_cardAppType);
if (result != null)
{
response.DataLength = 16 + 10 * result.Count;
logger.Info($"Base Black List, Data Length: {response.DataLength}");
}
}
else if (request.DataContentType == DataContentType.StationGeneralInfo)
{
response.DataContentType = DataContentType.StationGeneralInfo;
var result = _dbManager.GetPumpInfo(Convert.ToByte(PumpId));
if (result != null && result.Count > 0)
{
response.DataLength = 6;
}
logger.Info($"Station General Info, Data Length: {response.DataLength}");
}
_terminal.Write(response);
}
#endregion
#region Request Data Content 加油机申请下载数据的内容
public void HandleDataContentRequest(DataContentRequest request)
{
ResetTimer();
if (request.DataContentType == DataContentType.FuelPriceList)
{
logger.Info($"Terminal {PumpId} downloads fuel price list content");
}
SendDataContent(request);
}
public void SendDataContent(DataContentRequest request)
{
DataContent response = new DataContent();
response.Prefix = STX;
response.SourceAddress = request.SourceAddress;
response.DestinationAddress = request.DestinationAddress;
response.FrameSqNoByte = request.FrameSqNoByte;
response.Handle = (byte)Command.DataContent;
//Fuel price, 油品油价列表
if (request.DataContentType == DataContentType.FuelPriceList)
{
logger.Info($"Terminal downloads fuel price, Source: {request.SourceAddress}");
response.DataContentType = DataContentType.FuelPriceList;
response.SegmentOffset = request.SegmentOffset;
var result = _dbManager.GetFuelPriceChangeConfig(PumpId);
if (result.Count > 0)
{
var priceChangeRecordCount = result.Count;
if (priceChangeRecordCount <= 1)
{
var priceChangeRecord = result.First();
FuelPriceRecord record = new FuelPriceRecord();
record.Version = Convert.ToByte(priceChangeRecord.Ver);
record.EffectiveTime = priceChangeRecord.EffeTime;
record.RecordCount = Convert.ToByte(priceChangeRecordCount);
record.NozzleNo = Convert.ToByte(priceChangeRecord.LogicId);
record.FuelProductCode = Convert.ToUInt16(priceChangeRecord.OilId);
record.FuelProductName =
ByteArrayToString(Encoding.GetEncoding("GB2312").GetBytes(priceChangeRecord.OilName)).PadRight(64, '0');
record.Density = priceChangeRecord.Density == "" ? 0 : Convert.ToInt32(priceChangeRecord.Density);
record.ValidPriceCount = 1;
record.Price1 = priceChangeRecord.Price > UInt16.MaxValue ? UInt16.MaxValue : Convert.ToUInt16(priceChangeRecord.Price);
_terminal.SetRealPrice(PumpId, Convert.ToInt32(priceChangeRecord.Price));
response.Content = parser.SerializeInnerElement(record).ToList();
if (response.Content.Count % 16 == 0)
{
response.SegmentCount = Convert.ToByte(response.Content.Count / 16);
}
else
{
response.SegmentCount = Convert.ToByte(response.Content.Count / 16 + 1);
}
}
else
{
logger.Info("There are multiple price change records, seriously? ...");
var first = result.First();
MultiFuelPriceRecord record = new MultiFuelPriceRecord();
record.Version = Convert.ToByte(first.Ver);
record.EffectiveTime = first.EffeTime;
record.RecordCount = Convert.ToByte(result.Count);
record.FirstNozzleNo = Convert.ToByte(result[0].LogicId);
record.FirstFuelProductCode = Convert.ToUInt16(result[0].OilId);
record.FirstFuelProductName =
ByteArrayToString(Encoding.GetEncoding("GB2312").GetBytes(result[0].OilName)).PadRight(64, '0');
record.FirstDensity = result[0].Density == "" ? 0 : Convert.ToInt32(result[0].Density);
record.FirstValidPriceCount = 1;
record.FirstPrice1 = Convert.ToUInt16(result[0].Price);
record.SecondNozzleNo = Convert.ToByte(result[1].LogicId);
record.SecondFuelProductCode = Convert.ToUInt16(result[1].OilId);
record.SecondFuelProductName =
ByteArrayToString(Encoding.GetEncoding("GB2312").GetBytes(result[1].OilName)).PadRight(64, '0');
record.SecondDensity = result[1].Density == "" ? 0 : Convert.ToInt32(result[1].Density);
record.SecondValidPriceCount = 1;
record.SecondPrice1 = Convert.ToUInt16(result[1].Price);
response.Content = new List();
response.Content.AddRange(parser.SerializeInnerElement(record).ToList());
if (response.Content.Count % 16 == 0)
{
response.SegmentCount = Convert.ToByte(response.Content.Count / 16);
}
else
{
response.SegmentCount = Convert.ToByte(response.Content.Count / 16 + 1);
}
}
}
}
//Incremental blacklist, 新增(增量)黑名单
else if (request.DataContentType == DataContentType.NewlyAddedBlacklist)
{
logger.Info($"Terminal downloading newly added (incremental) blacklist, Source: {request.SourceAddress}, " +
$"Segment offset: {request.SegmentOffset}, Segment count: {request.SegmentCount}");
response.DataContentType = DataContentType.NewlyAddedBlacklist;
response.SegmentOffset = request.SegmentOffset;
response.SegmentCount = request.SegmentCount;
var cards = _dbManager.GetNewlyAddedBlacklistedCards(_cardAppType);
var versions = _dbManager.GetDataVersions();
if (versions.Count > 0 && cards.Count > 0)
{
var versionInfo = versions.First(v => v.VersionType == VersionType.NewlyAddedBlacklistVersion);
ListedCardRecord listedCardRecord = null;
if (versionInfo != null)
{
listedCardRecord = new ListedCardRecord();
listedCardRecord.Version = versionInfo.VersionNo;
listedCardRecord.ValidStartDate = versionInfo.Effectivetime.ToString("yyyyMMdd");
listedCardRecord.ExpiryDate = versionInfo.ExpiredTime.ToString("yyyyMMdd");
listedCardRecord.Region = Convert.ToString(ushort.MaxValue);
listedCardRecord.CardCount = cards.Count;
}
if (VersionedListedCard == null)
{
logger.Info("Formatting card list (newly added)");
VersionedListedCard = new VersionedListedCard(versionInfo.VersionNo, versionInfo.Effectivetime.ToString("yyyyMMdd"),
versionInfo.ExpiredTime.ToString("yyyyMMdd"), cards.Count, cards.ToList());
}
if (request.SegmentOffset == 0)
{
if (request.SegmentCount == 1)
{
logger.Info("Terminal requesting just one segment of newly added blacklist data");
response.Content = parser.SerializeInnerElement(listedCardRecord).ToList();
}
else
{
logger.Info($"Terminal requests Newly-added blacklist, Segment offset: 0000, Segment count: {request.SegmentCount}");
var versionInfoBytes = parser.SerializeInnerElement(listedCardRecord);
var cardBytes = StringToByteArray(VersionedListedCard.AllCards);
if (versionInfoBytes.Length + cardBytes.Length >= 160)
{
var bytes = new byte[160];
Buffer.BlockCopy(versionInfoBytes, 0, bytes, 0, versionInfoBytes.Length);
Buffer.BlockCopy(cardBytes, 0, bytes, versionInfoBytes.Length, 160 - versionInfoBytes.Length);
response.Content = bytes.ToList();
}
else
{
var bytes = new byte[160];
Buffer.BlockCopy(versionInfoBytes, 0, bytes, 0, versionInfoBytes.Length);
Buffer.BlockCopy(cardBytes, 0, bytes, versionInfoBytes.Length, cardBytes.Length);
response.Content = bytes.ToList();
}
}
}
else
{
var bytes = new byte[160];
int cardsToBeSkipped = request.SegmentOffset > 10 ? request.SegmentOffset / 10 : 0;
logger.Info($"Newly-added blacklist, Segment Offset: {request.SegmentOffset}, Cards to be skipped: {cardsToBeSkipped * 16}");
if (VersionedListedCard.CardCount <= 16)
{
var cardBytes = StringToByteArray(VersionedListedCard.AllCards);
Buffer.BlockCopy(cardBytes, 0, bytes, 0, cardBytes.Length);
response.Content = bytes.ToList();
}
else
{
IEnumerable currentCards = VersionedListedCard.ListedCards.Skip(cardsToBeSkipped * 16).Take(16);
StringBuilder sb = new StringBuilder();
foreach (var card in currentCards)
{
sb.Append(card.CardNo.PadLeft(20, '0'));
logger.Info(card.CardNo);
}
var cardBytes = StringToByteArray(sb.ToString());
Buffer.BlockCopy(cardBytes, 0, bytes, 0, cardBytes.Length);
response.Content = bytes.ToList();
}
}
}
}
//Decremental blacklist, 新删(减量)黑名单
else if (request.DataContentType == DataContentType.NewlyDeletedBlacklist)
{
logger.Info($"Terminal downloading newly deleted blacklist, Source: {request.SourceAddress}");
response.DataContentType = DataContentType.NewlyDeletedBlacklist;
response.SegmentOffset = request.SegmentOffset;
response.SegmentCount = request.SegmentCount;
var cards = _dbManager.GetNewlyDeletedBlacklistedCards(_cardAppType);
var versions = _dbManager.GetDataVersions();
if (versions.Count > 0 && cards.Count > 0)
{
var versionInfo = versions.First(v => v.VersionType == VersionType.NewlyDeletedBlacklistVersion);
ListedCardRecord record = null;
if (versionInfo != null)
{
record = new ListedCardRecord();
record.Version = versionInfo.VersionNo;
record.ValidStartDate = versionInfo.Effectivetime.ToString("yyyyMMdd");
record.ExpiryDate = versionInfo.ExpiredTime.ToString("yyyyMMdd");
record.Region = Convert.ToString(ushort.MaxValue);
record.CardCount = cards.Count;
}
if (VersionedListedCard == null)
{
logger.Info("Formatting card list (newly deleted)");
VersionedListedCard = new VersionedListedCard(versionInfo.VersionNo, versionInfo.Effectivetime.ToString("yyyyMMdd"),
versionInfo.ExpiredTime.ToString("yyyyMMdd"), cards.Count, cards.ToList());
}
if (request.SegmentOffset == 0)
{
if (request.SegmentCount == 1)
{
response.Content = parser.SerializeInnerElement(record).ToList();
}
else
{
var versionInfoBytes = parser.SerializeInnerElement(record);
var cardBytes = StringToByteArray(VersionedListedCard.AllCards);
if (versionInfoBytes.Length + cardBytes.Length >= 160)
{
var bytes = new byte[160];
Buffer.BlockCopy(versionInfoBytes, 0, bytes, 0, versionInfoBytes.Length);
Buffer.BlockCopy(cardBytes, 0, bytes, versionInfoBytes.Length, 160 - versionInfoBytes.Length);
response.Content = bytes.ToList();
}
else
{
var bytes = new byte[160];
Buffer.BlockCopy(versionInfoBytes, 0, bytes, 0, versionInfoBytes.Length);
Buffer.BlockCopy(cardBytes, 0, bytes, versionInfoBytes.Length, cardBytes.Length);
response.Content = bytes.ToList();
}
}
}
else
{
var bytes = new byte[160];
int segmentsToBeSkipped = request.SegmentOffset > 10 ? request.SegmentOffset / 10 : 0;
logger.Info($"Newly-deleted blacklist, Segment Offset: {request.SegmentOffset.ToString("X")}, " +
$"Cards to be skipped: {segmentsToBeSkipped * 16}");
if (VersionedListedCard.CardCount <= 16)
{
var cardBytes = StringToByteArray(VersionedListedCard.AllCards);
Buffer.BlockCopy(cardBytes, 0, bytes, 0, cardBytes.Length);
response.Content = bytes.ToList();
}
else
{
var currentCards = VersionedListedCard.ListedCards.Skip(segmentsToBeSkipped * 16).Take(16);
StringBuilder sb = new StringBuilder();
foreach (var card in currentCards)
{
sb.Append(card.CardNo.PadLeft(20, '0'));
logger.Info(card.CardNo);
}
var cardBytes = StringToByteArray(sb.ToString());
Buffer.BlockCopy(cardBytes, 0, bytes, 0, cardBytes.Length);
response.Content = bytes.ToList();
}
}
}
}
//White list, 白名单数据
else if (request.DataContentType == DataContentType.Whitelist)
{
logger.Info($"Terminal downloads whitelist, Source: {request.SourceAddress}");
response.DataContentType = DataContentType.Whitelist;
response.SegmentOffset = request.SegmentOffset;
response.SegmentCount = request.SegmentCount;
var cards = _dbManager.GetWhitelistedCards(_cardAppType);
var versions = _dbManager.GetDataVersions();
if (versions.Count > 0 && cards.Count > 0)
{
logger.Info($"Whitelist, card count: {cards.Count}");
var versionInfo = versions.First(v => v.VersionType == VersionType.WhitelistVersion);
ListedCardRecord record = null;
if (versionInfo != null)
{
logger.Info($"Whitelist, version: {versionInfo.VersionNo}");
record = new ListedCardRecord();
record.Version = versionInfo.VersionNo;
record.ValidStartDate = versionInfo.Effectivetime.ToString("yyyyMMdd");
record.ExpiryDate = versionInfo.ExpiredTime.ToString("yyyyMMdd");
record.Region = Convert.ToString(ushort.MaxValue);
record.CardCount = cards.Count;
}
if (VersionedListedCard == null)
{
logger.Info("Formatting card list (whitelist)");
VersionedListedCard = new VersionedListedCard(versionInfo.VersionNo, versionInfo.Effectivetime.ToString("yyyyMMdd"),
versionInfo.ExpiredTime.ToString("yyyyMMdd"), cards.Count, cards.ToList());
}
if (request.SegmentOffset == 0)
{
logger.Info("Segment offset is: 0");
if (request.SegmentCount == 1)
{
logger.Info("Segment count is: 1");
response.Content = parser.SerializeInnerElement(record).ToList();
}
else
{
logger.Info($"Segment count is: {request.SegmentCount}");
var versionInfoBytes = parser.SerializeInnerElement(record);
var cardBytes = StringToByteArray(VersionedListedCard.AllCards);
if (versionInfoBytes.Length + cardBytes.Length >= 160)
{
logger.Info($"Version Info serialized: {ByteArrayToString(versionInfoBytes)}, Length: {versionInfoBytes.Length}, ");
logger.Info($"More than 160 bytes, Cards bytes Length: {cardBytes.Length}");
var bytes = new byte[160];
try
{
Buffer.BlockCopy(versionInfoBytes, 0, bytes, 0, versionInfoBytes.Length);
Buffer.BlockCopy(cardBytes, 0, bytes, versionInfoBytes.Length, 160 - versionInfoBytes.Length);
}
catch (Exception ex)
{
logger.Info($"{ex.ToString()}");
}
response.Content = bytes.ToList();
}
else
{
logger.Info("Less than 160 bytes");
var bytes = new byte[160];
Buffer.BlockCopy(versionInfoBytes, 0, bytes, 0, versionInfoBytes.Length);
Buffer.BlockCopy(cardBytes, 0, bytes, versionInfoBytes.Length - 1, cardBytes.Length);
response.Content = bytes.ToList();
}
}
}
else
{
logger.Info($"Segment offset is: {request.SegmentOffset}");
var bytes = new byte[160];
int segmentsToBeSkipped = request.SegmentOffset > 10 ? request.SegmentOffset / 10 : 0;
logger.Info($"Whitelist, Segment Offset: {request.SegmentOffset.ToString("X")}, " +
$"Cards to be skipped: {segmentsToBeSkipped * 16}");
if (VersionedListedCard.CardCount <= 16)
{
var currentCards = VersionedListedCard.ListedCards.Skip((request.SegmentOffset - 1) * 15).Take(15);
StringBuilder sb = new StringBuilder();
foreach (var card in currentCards)
{
sb.Append(card.CardNo.PadLeft(20, '0'));
logger.Info(card.CardNo);
}
var cardBytes = StringToByteArray(sb.ToString());
Buffer.BlockCopy(cardBytes, 0, bytes, 0, cardBytes.Length);
response.Content = bytes.ToList();
}
else
{
var currentCards = VersionedListedCard.ListedCards.Skip(segmentsToBeSkipped * 16).Take(16);
StringBuilder sb = new StringBuilder();
foreach (var card in currentCards)
{
sb.Append(card.CardNo.PadLeft(20, '0'));
logger.Info(card.CardNo);
}
var cardBytes = StringToByteArray(sb.ToString());
Buffer.BlockCopy(cardBytes, 0, bytes, 0, cardBytes.Length);
response.Content = bytes.ToList();
//var cardBytes = StringToByteArray(VersionedListedCard.AllCards);
//Buffer.BlockCopy(cardBytes, 0, bytes, 0, cardBytes.Length);
//response.Content = bytes.ToList();
}
}
}
else if (cards.Count == 0)
{
logger.Info("No cards for whitelist");
var versionInfo = versions.First(v => v.VersionType == VersionType.WhitelistVersion);
ListedCardRecord record = null;
if (versionInfo != null)
{
record = new ListedCardRecord();
record.Version = versionInfo.VersionNo;
record.ValidStartDate = versionInfo.Effectivetime.ToString("yyyyMMdd");
record.ExpiryDate = versionInfo.ExpiredTime.ToString("yyyyMMdd");
record.Region = Convert.ToString(ushort.MaxValue);
record.CardCount = 0;
}
if (VersionedListedCard == null)
{
logger.Info("Formatting card list (whitelist)");
VersionedListedCard = new VersionedListedCard(versionInfo.VersionNo, versionInfo.Effectivetime.ToString("yyyyMMdd"),
versionInfo.ExpiredTime.ToString("yyyyMMdd"), 0, null);
}
response.Content = parser.SerializeInnerElement(record).ToList();
}
}
//Base blacklist, 基础黑名单
else if (request.DataContentType == DataContentType.BaseBlacklist)
{
logger.Info($"Terminal downloads base blacklist, Source: {request.SourceAddress}");
response.DataContentType = DataContentType.BaseBlacklist;
response.SegmentOffset = request.SegmentOffset;
response.SegmentCount = request.SegmentCount;
var cards = _dbManager.GetBaseBlacklistedCards(_cardAppType);
var versions = _dbManager.GetDataVersions();
if (versions.Count > 0 && cards.Count > 0)
{
var versionInfo = versions.First(v => v.VersionType == VersionType.BaseBlacklistVersion);
ListedCardRecord record = null;
if (versionInfo != null)
{
record = new ListedCardRecord();
record.Version = versionInfo.VersionNo;
record.ValidStartDate = versionInfo.Effectivetime.ToString("yyyyMMdd");
record.ExpiryDate = versionInfo.ExpiredTime.ToString("yyyyMMdd");
record.Region = Convert.ToString(ushort.MaxValue);
record.CardCount = cards.Count;
}
if (VersionedListedCard == null)
{
logger.Info("Formatting card list (base blacklist)");
VersionedListedCard = new VersionedListedCard(versionInfo.VersionNo, versionInfo.Effectivetime.ToString("yyyyMMdd"),
versionInfo.ExpiredTime.ToString("yyyyMMdd"), cards.Count, cards.ToList());
}
if (request.SegmentOffset == 0)
{
if (request.SegmentCount == 1)
{
response.Content = parser.SerializeInnerElement(record).ToList();
}
else
{
var versionInfoBytes = parser.SerializeInnerElement(record);
var cardBytes = StringToByteArray(VersionedListedCard.AllCards);
if (versionInfoBytes.Length + cardBytes.Length >= 160)
{
var bytes = new byte[160];
Buffer.BlockCopy(versionInfoBytes, 0, bytes, 0, versionInfoBytes.Length);
Buffer.BlockCopy(cardBytes, 0, bytes, versionInfoBytes.Length, 160 - versionInfoBytes.Length);
response.Content = bytes.ToList();
}
else
{
var bytes = new byte[160];
Buffer.BlockCopy(versionInfoBytes, 0, bytes, 0, versionInfoBytes.Length);
Buffer.BlockCopy(cardBytes, 0, bytes, versionInfoBytes.Length - 1, cardBytes.Length);
response.Content = bytes.ToList();
}
}
}
else
{
var bytes = new byte[160];
int cardsToBeSkipped = request.SegmentOffset > 10 ? request.SegmentOffset / 10 : 0;
logger.Info($"Base blacklist, Segment Offset: {request.SegmentOffset}, Cards to be skipped: {cardsToBeSkipped * 16}");
if (VersionedListedCard.CardCount <= 16)
{
var cardBytes = StringToByteArray(VersionedListedCard.AllCards);
Buffer.BlockCopy(cardBytes, 0, bytes, 0, cardBytes.Length);
response.Content = bytes.ToList();
}
else
{
IEnumerable currentCards = VersionedListedCard.ListedCards.Skip(cardsToBeSkipped * 16).Take(16);
StringBuilder sb = new StringBuilder();
foreach (var card in currentCards)
{
sb.Append(card.CardNo.PadLeft(20, '0'));
logger.Info(card.CardNo);
}
var cardBytes = StringToByteArray(sb.ToString());
Buffer.BlockCopy(cardBytes, 0, bytes, 0, cardBytes.Length);
response.Content = bytes.ToList();
}
}
}
}
//Station general info, 油站通用信息
else if (request.DataContentType == DataContentType.StationGeneralInfo)
{
logger.Info($"Terminal downloads Station general info (油站通用信息内容), Source: {request.SourceAddress}");
response.DataContentType = DataContentType.StationGeneralInfo;
response.SegmentOffset = request.SegmentOffset;
var result = _dbManager.GetPumpInfo(Convert.ToByte(PumpId));
List data = new List();
var pumpInfo = result.First();
data.Add(Convert.ToByte(pumpInfo.VersionId));
data.Add(Convert.ToByte(pumpInfo.PosId));
data.Add(Convert.ToByte(PumpId));
if (result != null && result.Count > 0)
{
data.Add(Convert.ToByte(result.Count()));
foreach (var item in result)
data.Add(Convert.ToByte(item.LogicId));
data.AddRange(new byte[10]);
}
response.SegmentCount = 1;
response.Content = data;
}
if (response.Content != null && response.Content.Count > 0)
logger.Info($"ContentType: {response.DataContentType}, Content: {ByteArrayToString(response.Content.ToArray())}");
else
logger.Info($"ContentType: {response.DataContentType}, Content emtpy");
_terminal.Write(response);
}
#endregion
#region Change Auth Mode 修改授权模式
private void ChangeAuthMode(CheckCmdRequest checkCmd, WorkMode workMode)
{
var request = new ChangeAuthMode
{
Prefix = STX,
SourceAddress = checkCmd.SourceAddress,
DestinationAddress = checkCmd.DestinationAddress,
FrameSqNoByte = checkCmd.FrameSqNoByte,
Handle = Convert.ToByte(Command.ChangeAuthMode),
FPCode = EncodeFPCodeString(PumpId, PumpId),
WorkMode = workMode
};
PrepareSendMessage(request);
}
public void HandleModeChangeResult(ChangeAuthModeAck response)
{
logger.Info($"Mode change result: {response.ModeChangeResult}, current mode: {response.WorkMode}");
}
#endregion
#region Cancel Auth request 撤销授权
public void HandleCancelAuth(CancelAuthRequest cancelAuthRequest)
{
logger.Info("Handling CancelAuth (0x19)");
//There is no special handling for cancel auth.
SendCancelAuthResult(cancelAuthRequest);
}
private void SendCancelAuthResult(CancelAuthRequest request)
{
var result = new CancelAuthResult();
result.Prefix = STX;
result.DestinationAddress = request.DestinationAddress;
result.SourceAddress = request.SourceAddress;
result.FrameSqNoByte = request.FrameSqNoByte;
result.Handle = Convert.ToByte(Command.CancelAuthResult);
result.Asn = request.Asn;
result.PosTtc = request.PosTtc;
result.SeqNo = request.SeqNo;
result.FPCode = request.FPCode.ToUInt16();
result.Result = 0;
result.AdditionalInfoLength = 0;
_terminal.Write(result);
}
#endregion
#region Query gray record 查询灰记录
public void HandleQueryGrayRecord(QueryGrayRecordRequest request)
{
logger.Info($"Query gray record, info, ASN: {request.Asn}, CTC: {request.Ctc}, Time: {request.Time}");
SendGrayRecord(request);
}
private void SendGrayRecord(QueryGrayRecordRequest request)
{
var record = _dbManager.SelectGrayInfo(request.Asn, request.Ctc, request.Time);
QueryGrayRecordResult result = new QueryGrayRecordResult();
result.Prefix = STX;
result.SourceAddress = request.SourceAddress;
result.DestinationAddress = request.DestinationAddress;
result.FrameSqNoByte = request.FrameSqNoByte;
result.Handle = Convert.ToByte(Command.GrayRecord);
if (record != null)
{
result.Match = 0;
result.Asn = request.Asn;
result.Balance = Convert.ToInt32(record.CardBal);
//result.Amount = Convert.ToInt32(record.Mon);
result.Amount1 = Convert.ToInt32(record.RealMon);
result.Ctc = request.Ctc;
result.Time = record.Ttctime;
result.Gmac = record.Gmac.ToString("X");
result.PsamTid = record.Psamtid;
result.PsamTtc = Convert.ToInt32(record.Psamttc);
result.Volume = Convert.ToInt32(record.Vol);
}
else
{
result.Match = 1;
}
_terminal.Write(result);
}
#endregion
public void HandleFakeNullMessage()
{
logger.Info($"Terminal {PumpId} Handling Fake null message");
ResetTimer();
}
#region Message sent to terminal from System
private bool IsMessagePendingForSend()
{
lock (sendQueueSyncObj)
{
logger.Debug($"Current send queue count: {activeSendQueue.Count}");
return activeSendQueue.Count > 0;
}
}
private void PrepareSendMessage(CardMessageBase cardMessage)
{
lock (sendQueueSyncObj)
{
activeSendQueue.Enqueue(cardMessage);
}
}
private CardMessageBase PickAndSendCardMessage()
{
lock (sendQueueSyncObj)
{
if (activeSendQueue.Count > 0)
{
logger.Info("Dequeued message from SendQueue");
return activeSendQueue.Dequeue();
}
}
return null;
}
#endregion
#region Check terminal id
private void CheckTerminalId(string terminalId)
{
int tid;
if (int.TryParse(terminalId, out tid))
{
if (tid <= 0 || tid >= 99)
{
logger.Error($"Invalid terminal id: {tid}");
}
}
else
{
logger.Error("Invalid terminal id, unrecognized");
}
}
#endregion
#endregion
#region Helper methods
public byte[] StringToByteArray(string hex)
{
int numberChars = hex.Length;
byte[] bytes = new byte[numberChars / 2];
for (int i = 0; i < numberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-", "");
}
private DateTime GetDateTime(string time)
{
if (time == "00000000000000")
return new DateTime(0001, 01, 01, 00, 00, 00);
var timeFormat = "yyyyMMddHHmmss";
return DateTime.ParseExact(time, timeFormat, null);
}
private string EncodeFPCodeString(int nozzleId, int pumpId)
{
return nozzleId.ToString("X").PadLeft(2, '0') + pumpId.ToString("X").PadLeft(2, '0');
}
#endregion
#region Save non card trade
private long AddTradeFromFuelingData(FuelingDataRequest request)
{
int payModeId = 102;
string payModeNo = string.Empty;
byte trxType = 6;
string asn = string.Empty;
if (request.Asn.Any(c => c > 48))
asn = request.Asn;
int stationNo = 1;
if (_posApp.StationInfo != null)
stationNo = _posApp.StationInfo.StationNo;
var newGid = _dbManager.AddTrade(
stationNo,
asn,
asn,
payModeId,
payModeNo,
trxType,
Convert.ToString(request.ProductCode),
request.Price,
request.Volume,
request.Amount,
request.Amount,
0, //Card Balance,
0, //card type, use 0 for non-card transaction
0, //card ctc use 0 for non-card transaction
GetDateTime(request.DispenserTime),
GetDateTime(request.TransactionEndTime),
request.PosTtc,
request.SeqNo,
_posApp.GetNextBillNo(), //billNo
request.FuelingPoint.NozzleNo,
request.FuelingPoint.PumpNo,
Convert.ToInt32(request.TerminalId.TrimStart('0')),
request.VolumeTotal,
0, //discountNo
1001,
1,
string.Empty,//request.PsamAsn, //PsamAsn
0,//uint.Parse(request.PsamTac, System.Globalization.NumberStyles.HexNumber),//PsamTac
string.Empty,//request.PsamTid,//psam tid
0,//uint.Parse(request.PsamTtc, System.Globalization.NumberStyles.HexNumber),//psam ttc
0,//uint.Parse(request.Tac, System.Globalization.NumberStyles.HexNumber),//tac
0,//uint.Parse(request.Gmac, System.Globalization.NumberStyles.HexNumber),//gmac
0,//uint.Parse(request.Tmac, System.Globalization.NumberStyles.HexNumber),//tmac
0, //Integral
Convert.ToInt32(_posApp.CurrentShiftNo),
string.Empty,
string.Empty,
string.Empty,
0);
return newGid;
}
#endregion
private string GetCardNo(string asn)
{
if (_cardAppType == CardAppType.CpuCard)
{
string leftOver = asn.TrimStart('0');
return leftOver.PadLeft(20, '0');
}
else if (_cardAppType == CardAppType.RfCard)
{
string leftOver = asn.TrimStart('0');
return leftOver.PadLeft(8, '0');
}
return asn;
}
}
}