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,
///
/// outdoor driver drive off without real payment
///
DriveOff = 400,
///
/// indoor customer leave off without real payment
///
LeaveOff = 401,
///
/// membership depoist balance payment initiated by mini program, 余额支付
///
MembershipPay = 500,
///
/// membership mobile payment initiated by mini program, 小程序微信支付
///
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 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; }
///
/// should get it from cloud side.
///
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 Params { get; } = new Dictionary();
}
public class CloudCredential
{
public string UserName { get; set; }
public string Password { get; set; }
///
/// Sample: http://url:8698
/// DO NOT end with slash
///
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
//{
// ///
// /// Unique id.
// ///
// public Guid Id { get; set; }
// ///
// /// Item Id.
// ///
// public int ItemId { get; set; }
// ///
// /// Item barcode.
// ///
// public string BarCode { get; set; }
// ///
// /// Change set id.
// ///
// public Guid ChangesetId { get; set; }
// ///
// /// Name of the item.
// ///
// public string ItemName { get; set; }
// ///
// /// Unit id.
// ///
// public int UnitId { get; set; }
// ///
// /// Unit price.
// ///
// public decimal Price { get; set; }
// ///
// /// When the item is activated.
// ///
// public DateTime TimeToActivate { get; set; }
// ///
// /// When the time is deactivated.
// ///
// public DateTime TimeToDeactivate { get; set; }
// ///
// /// When the item is created.
// ///
// public DateTime CreatedDateTime { get; set; }
// ///
// /// Whether this item is a fuel item.
// ///
// public bool IsFuelItem { get; set; }
// ///
// /// Whether the item is deleted.
// ///
// public bool IsMarkedAsDeletion { get; set; }
// ///
// /// Target business unit id.
// ///
// 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
///
/// The access token string issued by the authorization server.
///
[JsonProperty("access_token")]
public string AccessToken { get; set; }
///
/// Token type, simply `bearer`.
///
[JsonProperty("token_type")]
public string TokenType { get; set; }
///
/// Duration of the time the access token is granted for, in total seconds.
///
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
///
/// Access token to be used to get a new token, optional.
///
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
///
/// The error information
///
[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 BusinessUnitList { get; set; }
#endregion
#region Custom extension
///
/// When the token is retrieve from the auth server, to simplify, set the local received time.
///
public DateTime TokenRetrievedTime { get; set; }
///
/// Check if the token has expired or not, if not, then it's valid.
///
/// valid or not
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(resultStr);
if (!string.IsNullOrEmpty(this.currentAuthToken.BusinessUnitsJsonString))
{
this.currentAuthToken.BusinessUnitList = JsonConvert.DeserializeObject>(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 ?? ""}");
}
///
/// query the last trx from server side, if its TransactionStatus == Open, then server will return it. otherwise, server will return null.
///
///
///
public async Task GeLastTrxWithOpenStatusByUserNameAsync()
{
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(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 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(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 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 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
{
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(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 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 ?? ""}");
}
}
}