123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561 |
- using Dfs.WayneChina.PosModelMini;
- using Microsoft.Extensions.Logging;
- using Microsoft.Extensions.Logging.Abstractions;
- 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.Text.Json;
- using System.Threading;
- using System.Threading.Tasks;
- namespace VBaoProxyApp.Cloud
- {
- public enum PaymentID
- {
- Cash = 1, //室内现金
- AliPay = 2,
- WechatPay = 3,
- IC = 4,
- MiniProgram = 5,
- ThirdPartyPay = 6,
- AutoCleared = 7, //油机大屏上的自动清除
- BankCard = 8,
- OutdoorCash = 9, //油机大屏的室外现金
- IndoorPay = 10, //油机大屏上的室内支付
- AllInPay = 11, //通联支付,
- PumpTest = 12, //打油测试
- AllInPayV2 = 13, //通联分账支付,
- WechatQrScan = 20, //微信扫码,统一下单,用户扫商户,
- AliPayQrScan = 21, //支付宝扫码,统一下单,用户扫商户,
- CarPlate = 100,
- /// <summary>
- /// outdoor driver drive off without real payment
- /// </summary>
- DriveOff = 400,
- /// <summary>
- /// indoor customer leave off without real payment
- /// </summary>
- LeaveOff = 401,
- /// <summary>
- /// membership depoist balance payment initiated by mini program, 余额支付
- /// </summary>
- MembershipPay = 500,
- /// <summary>
- /// membership mobile payment initiated by mini program, 小程序微信支付
- /// </summary>
- MembershipMobilePay = 501,
- PetroChinaIC = 600,
- }
- public class TrxCreationResponse
- {
- public Guid Id { get; set; }
- }
- public class ServerSideItem
- {
- public string itemId { get; set; }
- public string BarCode { get; set; }
- public string ItemName { get; set; }
- public decimal Price { get; set; }
- public bool IsFuelItem { get; set; }
- public Guid Id { get; set; }
- }
- public class ServerSideTrxItem
- {
- public int LineNum { get; set; }
- public Guid PosItemId { get; set; }
- public ServerSideItem Item { get; set; }
- public double Qty { get; set; }
- public int FuelItemSoldOnPumpId { get; set; }
- public int FuelItemSoldOnPumpNozzleId { get; set; }
- public string FuelItemFdcTransactionSeqNo { get; set; }
- public decimal FuelItemPumpTotalizerVolume { get; set; }
- }
- public enum ServerSideTrxStatus
- {
- Open,
- Finish
- }
- public enum ServerSideTrxType
- {
- Sale = 0,
- Refund = 1,
- Reconciliation = 2,
- Redemption = 3,
- LogOff = 4,
- LogOn = 5,
- EndOfShift = 6,
- EndOfDay = 7,
- Restore = 8,
- FuelDelivery = 9, //Manual input on POS Side
- TankDelivery = 10, //Auto Detect, in using
- TankReading = 11 //when POS EOS, POS Read ATG Data
- }
- public class ServerSideTrx
- {
- public Guid Id { get; set; }
- public ServerSideTrxType TransactionType { get; set; }
- public DateTime CreatedDateTime { get; set; }
- public Guid TargetBusinessUnitId { get; set; }
- public decimal NetAmount { get; set; }
- public decimal GrossAmount { get; set; }
- public List<ServerSideTrxItem> Items { get; set; }
- public ServerSideTrxStatus TransactionStatus { get; set; }
- }
- public class ClientFuelTrxInfo
- {
- public PosTrxSource Source { get; set; }
- public int Barcode { get; set; }
- public int PumpId { get; set; }
- public int NozzleId { get; set; }
- public int SiteNozzleNo { get; set; }
- /// <summary>
- /// should get it from cloud side.
- /// </summary>
- public Guid PosItemId { 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 AuthenticationParameter
- {
- public AuthenticationParameter(string grantType, string userName, string password)
- {
- Params.Clear();
- Params.Add("grant_type", grantType);
- Params.Add("username", userName);
- Params.Add("password", password);
- }
- public Dictionary<string, string> Params { get; } = new Dictionary<string, string>();
- }
- public class CloudCredential
- {
- public string UserName { get; set; }
- public string Password { get; set; }
- /// <summary>
- /// Sample: http://url:8698
- /// DO NOT end with slash
- /// </summary>
- public string ApiGatewayEntryUrl { get; set; }
- //public string AuthServiceBaseUrl { get; set; }
- //public string TransactionServiceBaseUrl { get; set; }
- //public string DiscountServiceBaseUrl { get; set; }
- public string DeviceSN { get; set; }
- //public string CurrentBuId { get; set; }
- }
- //public class PosItem
- //{
- // /// <summary>
- // /// Unique id.
- // /// </summary>
- // public Guid Id { get; set; }
- // /// <summary>
- // /// Item Id.
- // /// </summary>
- // public int ItemId { get; set; }
- // /// <summary>
- // /// Item barcode.
- // /// </summary>
- // public string BarCode { get; set; }
- // /// <summary>
- // /// Change set id.
- // /// </summary>
- // public Guid ChangesetId { get; set; }
- // /// <summary>
- // /// Name of the item.
- // /// </summary>
- // public string ItemName { get; set; }
- // /// <summary>
- // /// Unit id.
- // /// </summary>
- // public int UnitId { get; set; }
- // /// <summary>
- // /// Unit price.
- // /// </summary>
- // public decimal Price { get; set; }
- // /// <summary>
- // /// When the item is activated.
- // /// </summary>
- // public DateTime TimeToActivate { get; set; }
- // /// <summary>
- // /// When the time is deactivated.
- // /// </summary>
- // public DateTime TimeToDeactivate { get; set; }
- // /// <summary>
- // /// When the item is created.
- // /// </summary>
- // public DateTime CreatedDateTime { get; set; }
- // /// <summary>
- // /// Whether this item is a fuel item.
- // /// </summary>
- // public bool IsFuelItem { get; set; }
- // /// <summary>
- // /// Whether the item is deleted.
- // /// </summary>
- // public bool IsMarkedAsDeletion { get; set; }
- // /// <summary>
- // /// Target business unit id.
- // /// </summary>
- // public Guid TargetBusinessUnitId { get; set; }
- //}
- public class BusinessUnit
- {
- [JsonProperty("Id")]
- public Guid Id { get; set; }
- [JsonProperty("Name")]
- public string Name { get; set; }
- }
- public class AuthToken
- {
- #region Standard OAuth token format
- /// <summary>
- /// The access token string issued by the authorization server.
- /// </summary>
- [JsonProperty("access_token")]
- public string AccessToken { get; set; }
- /// <summary>
- /// Token type, simply `bearer`.
- /// </summary>
- [JsonProperty("token_type")]
- public string TokenType { get; set; }
- /// <summary>
- /// Duration of the time the access token is granted for, in total seconds.
- /// </summary>
- [JsonProperty("expires_in")]
- public int ExpiresIn { get; set; }
- /// <summary>
- /// Access token to be used to get a new token, optional.
- /// </summary>
- [JsonProperty("refresh_token")]
- public string RefreshToken { get; set; }
- /// <summary>
- /// The error information
- /// </summary>
- [JsonProperty("error")]
- public string Error { get; set; }
- [JsonProperty("userName")]
- public string UserName { get; set; }
- [JsonProperty("alias")]
- public string UserAlias { get; set; }
- [JsonProperty("roleNames")]
- public string RoleNames { get; set; }
- [JsonProperty("BusinessUnits")]
- public string BusinessUnitsJsonString { get; set; }
- public IList<BusinessUnit> BusinessUnitList { get; set; }
- #endregion
- #region Custom extension
- /// <summary>
- /// When the token is retrieve from the auth server, to simplify, set the local received time.
- /// </summary>
- public DateTime TokenRetrievedTime { get; set; }
- /// <summary>
- /// Check if the token has expired or not, if not, then it's valid.
- /// </summary>
- /// <returns>valid or not</returns>
- public bool IsTokenValid()
- {
- //Current Time = Now + 5 minutes, Expiry Time = token retrieved time + expiry time span
- //If current time is not later than expiry time, then the token is valid
- if (DateTime.Now + new TimeSpan(0, 5, 0) < TokenRetrievedTime + new TimeSpan(0, 0, ExpiresIn))
- return true;
- return false;
- }
- #endregion
- }
- public class CloudHelper
- {
- public CloudCredential Credential { get; set; }
- public ILogger Logger { get; set; } = NullLogger.Instance;
- public int Id { get; }
- private string grantType = "password";
- private string tokenAuthPath = "token";
- private string authScheme = "bearer";
- private HttpClient _client = new HttpClient();
- private AuthToken currentAuthToken = null;
- private static CloudHelper instance = new CloudHelper();
- public static CloudHelper Default { get; } = instance;
- private CloudHelper()
- {
- }
- private async Task RefreshAuthTokenAsync(string userName, string password)
- {
- this.Logger.LogDebug("GetTokenAsync...");
- _client.DefaultRequestHeaders.Clear();
- string tokenUrl = string.Concat(this.Credential.ApiGatewayEntryUrl, "/", tokenAuthPath);
- var formParam = new AuthenticationParameter(grantType, userName, password);
- var response = await _client.PostAsync(tokenUrl, new FormUrlEncodedContent(formParam.Params)).ConfigureAwait(false);
- this.Logger.LogDebug($"Response for getting token, Response==null? {response == null}");
- if (response != null && response.IsSuccessStatusCode)
- {
- var resultStr = await response.Content.ReadAsStringAsync();
- this.currentAuthToken = JsonConvert.DeserializeObject<AuthToken>(resultStr);
- if (!string.IsNullOrEmpty(this.currentAuthToken.BusinessUnitsJsonString))
- {
- this.currentAuthToken.BusinessUnitList = JsonConvert.DeserializeObject<IList<BusinessUnit>>(currentAuthToken.BusinessUnitsJsonString);
- return;
- }
- }
- throw new InvalidOperationException($"GetAuthTokenAsync failed(serverUrl: {tokenUrl}, username/pwd: {userName ?? ""}/{password ?? ""}) due to response with code: {response?.StatusCode.ToString() ?? "-1"}, message: {response?.ReasonPhrase ?? ""}");
- }
- /// <summary>
- /// query the last trx from server side, if its TransactionStatus == Open, then server will return it. otherwise, server will return null.
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <returns></returns>
- public async Task<T> GeLastTrxWithOpenStatusByUserNameAsync<T>()
- {
- if (this.currentAuthToken == null)
- await this.RefreshAuthTokenAsync(this.Credential.UserName, this.Credential.Password);
- string targetUrl = string.Concat(this.Credential.ApiGatewayEntryUrl + "/api/transactions/?searchType=lastOpenSaleTrx&searchValue0=userName&searchValue1=", this.Credential.UserName);
- _client.DefaultRequestHeaders.Clear();
- _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, this.currentAuthToken.AccessToken);
- this.Logger.LogDebug($"{_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", this.Credential.DeviceSN);
- var response = await _client.GetAsync(targetUrl).ConfigureAwait(false);
- if (response != null && response.IsSuccessStatusCode)
- {
- var resultStr = await response.Content.ReadAsStringAsync();
- if (string.IsNullOrEmpty(resultStr)) return default(T);
- var jd = JsonDocument.Parse(resultStr);
- return JsonConvert.DeserializeObject<T>(resultStr);
- //return jd.RootElement.GetProperty("result");
- }
- throw new InvalidOperationException($"GeLastOpenTrxByUserNameAsync failed(serverUrl: {targetUrl}, userName: {this.Credential.UserName ?? ""}, deviceSN: {this.Credential?.DeviceSN ?? ""}) due to response with code: {response?.StatusCode.ToString() ?? "-1"}, message: {response?.ReasonPhrase ?? ""}");
- }
- public async Task<PosItem> GetPosItemAsync(string itemId)
- {
- if (this.currentAuthToken == null)
- await this.RefreshAuthTokenAsync(this.Credential.UserName, this.Credential.Password);
- string productItemUrl = string.Concat(this.Credential.ApiGatewayEntryUrl + "/api/Products/", "itemId/");
- this.Logger.LogDebug("GetPosItemAsync...");
- _client.DefaultRequestHeaders.Clear();
- _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, this.currentAuthToken.AccessToken);
- this.Logger.LogDebug($"{_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", this.Credential.DeviceSN);
- //_client.DefaultRequestHeaders.Add("CurrentBuId", BusinessUnitId);
- //this.logger.LogDebug($"DeviceSN: {DeviceSN}");
- //this.logger.LogDebug($"BuId: {BusinessUnitId}");
- var productUrl = string.Concat(productItemUrl, itemId);
- this.Logger.LogDebug($"item url: {productUrl}");
- var response = await _client.GetAsync(productUrl).ConfigureAwait(false);
- if (response != null && response.IsSuccessStatusCode)
- {
- var resultStr = await response.Content.ReadAsStringAsync();
- return JsonConvert.DeserializeObject<PosItem>(resultStr);
- }
- throw new InvalidOperationException($"GetPosItemAsync failed(serverUrl: {productItemUrl}, itemId: {itemId}, deviceSN: {this.Credential?.DeviceSN ?? ""}) due to response with code: {response?.StatusCode.ToString() ?? "-1"}, message: {response?.ReasonPhrase ?? ""}");
- }
- public async Task<JsonElement> GetAccountIdByVBaoBeaconIdAsync(string beaconId)
- {
- if (this.currentAuthToken == null)
- await this.RefreshAuthTokenAsync(this.Credential.UserName, this.Credential.Password);
- string targetUrl = string.Concat(this.Credential.ApiGatewayEntryUrl + "/userinfo/", "vBaoId?vBaoId=", beaconId);
- _client.DefaultRequestHeaders.Clear();
- _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, this.currentAuthToken.AccessToken);
- this.Logger.LogDebug($"{_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", this.Credential.DeviceSN);
- var response = await _client.GetAsync(targetUrl).ConfigureAwait(false);
- if (response != null && response.IsSuccessStatusCode)
- {
- var resultStr = await response.Content.ReadAsStringAsync();
- var jd = JsonDocument.Parse(resultStr);
- return jd.RootElement.GetProperty("result");
- }
- throw new InvalidOperationException($"GetAccountIdByVBaoBeaconIdAsync failed due to response with code: {response?.StatusCode.ToString() ?? "-1"}, message: {response?.ReasonPhrase ?? ""}");
- }
- public async Task<Guid> CreateTransactionAsync(ClientFuelTrxInfo clientFuelTrxInfo, string trxComment)
- {
- if (this.currentAuthToken == null)
- await this.RefreshAuthTokenAsync(this.Credential.UserName, this.Credential.Password);
- Guid trxId = Guid.Empty;
- var trxUrl = string.Concat(this.Credential.ApiGatewayEntryUrl + "/api/transactions/");
- var clientPosTrx = new ClientPosTrx
- {
- RequestingCreationTimeInPosClient = DateTime.Now,
- Type = PosTrxType.Sale,
- Source = clientFuelTrxInfo.Source,
- Items = new List<ClientRingUpPosItem>
- {
- new ClientRingUpPosItem
- {
- PosItemUniqueId = clientFuelTrxInfo.PosItemId, //GUID
- Qty = clientFuelTrxInfo.Volume,
- ClientRingUpTime = DateTime.Now,
- FuelItemSoldOnPumpId = clientFuelTrxInfo.PumpId,
- FuelItemSoldOnPumpNozzleId = clientFuelTrxInfo.SiteNozzleNo,//SiteNozzles[(clientTrxInfo.PumpId, clientTrxInfo.NozzleId)],//fdcTrx.Nozzle.LogicalId,
- FuelItemOriginalGrossAmount = clientFuelTrxInfo.Amount,//(decimal)trdInfo.Mon.Value / 100,
- FuelItemFdcTransactionSeqNo = Convert.ToString(clientFuelTrxInfo.SeqNo),
- fuelItemTransactionEndTime = clientFuelTrxInfo.FuelingFinishedTime,
- TransactionComment = trxComment,
- FuelItemPumpTotalizerVolume = clientFuelTrxInfo.VolumeTotalizer
- }
- }
- };
- _client.DefaultRequestHeaders.Clear();
- _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, currentAuthToken.AccessToken);
- _client.DefaultRequestHeaders.Add("DeviceSN", this.Credential.DeviceSN);
- //_client.DefaultRequestHeaders.Add("CurrentBuId", BusinessUnitId);
- var response = await _client.PostAsJsonAsync(trxUrl, clientPosTrx).ConfigureAwait(false);
- if (response != null && response.IsSuccessStatusCode)
- {
- var resultStr = await response.Content.ReadAsStringAsync();
- var trxCreationResponse = JsonConvert.DeserializeObject<TrxCreationResponse>(resultStr);
- return trxCreationResponse.Id;
- }
- throw new InvalidOperationException($"CreateTransactionAsync failed(serverUrl: {trxUrl}, trx volume: {clientFuelTrxInfo?.Volume ?? -1 }, deviceSN: {this.Credential?.DeviceSN ?? ""}) due to response with code: {response?.StatusCode.ToString() ?? "-1"}, message: {response?.ReasonPhrase ?? ""}");
- }
- public async Task<bool> CommitTransactionAsync(Guid trxId, PosTrxMop posTrxMop, string accountId)
- {
- if (this.currentAuthToken == null)
- await this.RefreshAuthTokenAsync(this.Credential.UserName, this.Credential.Password);
- string targetUrl = "";
- if (string.IsNullOrEmpty(accountId))
- targetUrl = string.Concat(this.Credential.ApiGatewayEntryUrl + "/api/transactions/", trxId, "/payment");
- else
- targetUrl = string.Concat(this.Credential.ApiGatewayEntryUrl + "/api/transactions/", trxId, "/payment?accountId=", accountId);
- //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.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", this.Credential.DeviceSN);
- //_client.DefaultRequestHeaders.Add("CurrentBuId", BusinessUnitId);
- var response = await _client.PostAsJsonAsync(targetUrl, posTrxMop).ConfigureAwait(false);
- if (response != null && response.IsSuccessStatusCode)
- return true;
- throw new InvalidOperationException($"CommitTransactionAsync failed(serverUrl: {targetUrl}, deviceSN: {this.Credential?.DeviceSN ?? ""}) due to response with code: {response?.StatusCode.ToString() ?? "-1"}, message: {response?.ReasonPhrase ?? ""}");
- }
- }
- }
|