CloudHelper.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. using Dfs.WayneChina.PosModelMini;
  2. using Microsoft.Extensions.Logging;
  3. using Microsoft.Extensions.Logging.Abstractions;
  4. using Newtonsoft.Json;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Linq;
  8. using System.Net.Http;
  9. using System.Net.Http.Headers;
  10. using System.Text;
  11. using System.Text.Json;
  12. using System.Threading;
  13. using System.Threading.Tasks;
  14. namespace VBaoProxyApp.Cloud
  15. {
  16. public enum PaymentID
  17. {
  18. Cash = 1, //室内现金
  19. AliPay = 2,
  20. WechatPay = 3,
  21. IC = 4,
  22. MiniProgram = 5,
  23. ThirdPartyPay = 6,
  24. AutoCleared = 7, //油机大屏上的自动清除
  25. BankCard = 8,
  26. OutdoorCash = 9, //油机大屏的室外现金
  27. IndoorPay = 10, //油机大屏上的室内支付
  28. AllInPay = 11, //通联支付,
  29. PumpTest = 12, //打油测试
  30. AllInPayV2 = 13, //通联分账支付,
  31. WechatQrScan = 20, //微信扫码,统一下单,用户扫商户,
  32. AliPayQrScan = 21, //支付宝扫码,统一下单,用户扫商户,
  33. CarPlate = 100,
  34. /// <summary>
  35. /// outdoor driver drive off without real payment
  36. /// </summary>
  37. DriveOff = 400,
  38. /// <summary>
  39. /// indoor customer leave off without real payment
  40. /// </summary>
  41. LeaveOff = 401,
  42. /// <summary>
  43. /// membership depoist balance payment initiated by mini program, 余额支付
  44. /// </summary>
  45. MembershipPay = 500,
  46. /// <summary>
  47. /// membership mobile payment initiated by mini program, 小程序微信支付
  48. /// </summary>
  49. MembershipMobilePay = 501,
  50. PetroChinaIC = 600,
  51. }
  52. public class TrxCreationResponse
  53. {
  54. public Guid Id { get; set; }
  55. }
  56. public class ServerSideItem
  57. {
  58. public string itemId { get; set; }
  59. public string BarCode { get; set; }
  60. public string ItemName { get; set; }
  61. public decimal Price { get; set; }
  62. public bool IsFuelItem { get; set; }
  63. public Guid Id { get; set; }
  64. }
  65. public class ServerSideTrxItem
  66. {
  67. public int LineNum { get; set; }
  68. public Guid PosItemId { get; set; }
  69. public ServerSideItem Item { get; set; }
  70. public double Qty { get; set; }
  71. public int FuelItemSoldOnPumpId { get; set; }
  72. public int FuelItemSoldOnPumpNozzleId { get; set; }
  73. public string FuelItemFdcTransactionSeqNo { get; set; }
  74. public decimal FuelItemPumpTotalizerVolume { get; set; }
  75. }
  76. public enum ServerSideTrxStatus
  77. {
  78. Open,
  79. Finish
  80. }
  81. public enum ServerSideTrxType
  82. {
  83. Sale = 0,
  84. Refund = 1,
  85. Reconciliation = 2,
  86. Redemption = 3,
  87. LogOff = 4,
  88. LogOn = 5,
  89. EndOfShift = 6,
  90. EndOfDay = 7,
  91. Restore = 8,
  92. FuelDelivery = 9, //Manual input on POS Side
  93. TankDelivery = 10, //Auto Detect, in using
  94. TankReading = 11 //when POS EOS, POS Read ATG Data
  95. }
  96. public class ServerSideTrx
  97. {
  98. public Guid Id { get; set; }
  99. public ServerSideTrxType TransactionType { get; set; }
  100. public DateTime CreatedDateTime { get; set; }
  101. public Guid TargetBusinessUnitId { get; set; }
  102. public decimal NetAmount { get; set; }
  103. public decimal GrossAmount { get; set; }
  104. public List<ServerSideTrxItem> Items { get; set; }
  105. public ServerSideTrxStatus TransactionStatus { get; set; }
  106. }
  107. public class ClientFuelTrxInfo
  108. {
  109. public PosTrxSource Source { get; set; }
  110. public int Barcode { get; set; }
  111. public int PumpId { get; set; }
  112. public int NozzleId { get; set; }
  113. public int SiteNozzleNo { get; set; }
  114. /// <summary>
  115. /// should get it from cloud side.
  116. /// </summary>
  117. public Guid PosItemId { get; set; }
  118. //public ushort FPosSqNo { get; set; }
  119. //public int FdcSqNo { get; set; }
  120. //public DateTime TimeStamp { get; set; }
  121. public decimal Volume { get; set; }
  122. public decimal Amount { get; set; }
  123. public decimal PayAmount { get; set; }
  124. public decimal UnitPrice { get; set; }
  125. public int SeqNo { get; set; }
  126. //public string CardNo { get; set; }
  127. //public decimal CurrentCardBalance { get; set; }
  128. public DateTime FuelingStartTime { get; set; }
  129. public DateTime FuelingFinishedTime { get; set; }
  130. public decimal VolumeTotalizer { get; set; }
  131. //public string CardHolder { get; set; }
  132. //public string AccountName { get; set; }
  133. }
  134. public class AuthenticationParameter
  135. {
  136. public AuthenticationParameter(string grantType, string userName, string password)
  137. {
  138. Params.Clear();
  139. Params.Add("grant_type", grantType);
  140. Params.Add("username", userName);
  141. Params.Add("password", password);
  142. }
  143. public Dictionary<string, string> Params { get; } = new Dictionary<string, string>();
  144. }
  145. public class CloudCredential
  146. {
  147. public string UserName { get; set; }
  148. public string Password { get; set; }
  149. /// <summary>
  150. /// Sample: http://url:8698
  151. /// DO NOT end with slash
  152. /// </summary>
  153. public string ApiGatewayEntryUrl { get; set; }
  154. //public string AuthServiceBaseUrl { get; set; }
  155. //public string TransactionServiceBaseUrl { get; set; }
  156. //public string DiscountServiceBaseUrl { get; set; }
  157. public string DeviceSN { get; set; }
  158. //public string CurrentBuId { get; set; }
  159. }
  160. //public class PosItem
  161. //{
  162. // /// <summary>
  163. // /// Unique id.
  164. // /// </summary>
  165. // public Guid Id { get; set; }
  166. // /// <summary>
  167. // /// Item Id.
  168. // /// </summary>
  169. // public int ItemId { get; set; }
  170. // /// <summary>
  171. // /// Item barcode.
  172. // /// </summary>
  173. // public string BarCode { get; set; }
  174. // /// <summary>
  175. // /// Change set id.
  176. // /// </summary>
  177. // public Guid ChangesetId { get; set; }
  178. // /// <summary>
  179. // /// Name of the item.
  180. // /// </summary>
  181. // public string ItemName { get; set; }
  182. // /// <summary>
  183. // /// Unit id.
  184. // /// </summary>
  185. // public int UnitId { get; set; }
  186. // /// <summary>
  187. // /// Unit price.
  188. // /// </summary>
  189. // public decimal Price { get; set; }
  190. // /// <summary>
  191. // /// When the item is activated.
  192. // /// </summary>
  193. // public DateTime TimeToActivate { get; set; }
  194. // /// <summary>
  195. // /// When the time is deactivated.
  196. // /// </summary>
  197. // public DateTime TimeToDeactivate { get; set; }
  198. // /// <summary>
  199. // /// When the item is created.
  200. // /// </summary>
  201. // public DateTime CreatedDateTime { get; set; }
  202. // /// <summary>
  203. // /// Whether this item is a fuel item.
  204. // /// </summary>
  205. // public bool IsFuelItem { get; set; }
  206. // /// <summary>
  207. // /// Whether the item is deleted.
  208. // /// </summary>
  209. // public bool IsMarkedAsDeletion { get; set; }
  210. // /// <summary>
  211. // /// Target business unit id.
  212. // /// </summary>
  213. // public Guid TargetBusinessUnitId { get; set; }
  214. //}
  215. public class BusinessUnit
  216. {
  217. [JsonProperty("Id")]
  218. public Guid Id { get; set; }
  219. [JsonProperty("Name")]
  220. public string Name { get; set; }
  221. }
  222. public class AuthToken
  223. {
  224. #region Standard OAuth token format
  225. /// <summary>
  226. /// The access token string issued by the authorization server.
  227. /// </summary>
  228. [JsonProperty("access_token")]
  229. public string AccessToken { get; set; }
  230. /// <summary>
  231. /// Token type, simply `bearer`.
  232. /// </summary>
  233. [JsonProperty("token_type")]
  234. public string TokenType { get; set; }
  235. /// <summary>
  236. /// Duration of the time the access token is granted for, in total seconds.
  237. /// </summary>
  238. [JsonProperty("expires_in")]
  239. public int ExpiresIn { get; set; }
  240. /// <summary>
  241. /// Access token to be used to get a new token, optional.
  242. /// </summary>
  243. [JsonProperty("refresh_token")]
  244. public string RefreshToken { get; set; }
  245. /// <summary>
  246. /// The error information
  247. /// </summary>
  248. [JsonProperty("error")]
  249. public string Error { get; set; }
  250. [JsonProperty("userName")]
  251. public string UserName { get; set; }
  252. [JsonProperty("alias")]
  253. public string UserAlias { get; set; }
  254. [JsonProperty("roleNames")]
  255. public string RoleNames { get; set; }
  256. [JsonProperty("BusinessUnits")]
  257. public string BusinessUnitsJsonString { get; set; }
  258. public IList<BusinessUnit> BusinessUnitList { get; set; }
  259. #endregion
  260. #region Custom extension
  261. /// <summary>
  262. /// When the token is retrieve from the auth server, to simplify, set the local received time.
  263. /// </summary>
  264. public DateTime TokenRetrievedTime { get; set; }
  265. /// <summary>
  266. /// Check if the token has expired or not, if not, then it's valid.
  267. /// </summary>
  268. /// <returns>valid or not</returns>
  269. public bool IsTokenValid()
  270. {
  271. //Current Time = Now + 5 minutes, Expiry Time = token retrieved time + expiry time span
  272. //If current time is not later than expiry time, then the token is valid
  273. if (DateTime.Now + new TimeSpan(0, 5, 0) < TokenRetrievedTime + new TimeSpan(0, 0, ExpiresIn))
  274. return true;
  275. return false;
  276. }
  277. #endregion
  278. }
  279. public class CloudHelper
  280. {
  281. public CloudCredential Credential { get; set; }
  282. public ILogger Logger { get; set; } = NullLogger.Instance;
  283. public int Id { get; }
  284. private string grantType = "password";
  285. private string tokenAuthPath = "token";
  286. private string authScheme = "bearer";
  287. private HttpClient _client = new HttpClient();
  288. private AuthToken currentAuthToken = null;
  289. private static CloudHelper instance = new CloudHelper();
  290. public static CloudHelper Default { get; } = instance;
  291. private CloudHelper()
  292. {
  293. }
  294. private async Task RefreshAuthTokenAsync(string userName, string password)
  295. {
  296. this.Logger.LogDebug("GetTokenAsync...");
  297. _client.DefaultRequestHeaders.Clear();
  298. string tokenUrl = string.Concat(this.Credential.ApiGatewayEntryUrl, "/", tokenAuthPath);
  299. var formParam = new AuthenticationParameter(grantType, userName, password);
  300. var response = await _client.PostAsync(tokenUrl, new FormUrlEncodedContent(formParam.Params)).ConfigureAwait(false);
  301. this.Logger.LogDebug($"Response for getting token, Response==null? {response == null}");
  302. if (response != null && response.IsSuccessStatusCode)
  303. {
  304. var resultStr = await response.Content.ReadAsStringAsync();
  305. this.currentAuthToken = JsonConvert.DeserializeObject<AuthToken>(resultStr);
  306. if (!string.IsNullOrEmpty(this.currentAuthToken.BusinessUnitsJsonString))
  307. {
  308. this.currentAuthToken.BusinessUnitList = JsonConvert.DeserializeObject<IList<BusinessUnit>>(currentAuthToken.BusinessUnitsJsonString);
  309. return;
  310. }
  311. }
  312. throw new InvalidOperationException($"GetAuthTokenAsync failed(serverUrl: {tokenUrl}, username/pwd: {userName ?? ""}/{password ?? ""}) due to response with code: {response?.StatusCode.ToString() ?? "-1"}, message: {response?.ReasonPhrase ?? ""}");
  313. }
  314. /// <summary>
  315. /// query the last trx from server side, if its TransactionStatus == Open, then server will return it. otherwise, server will return null.
  316. /// </summary>
  317. /// <typeparam name="T"></typeparam>
  318. /// <returns></returns>
  319. public async Task<T> GeLastTrxWithOpenStatusByUserNameAsync<T>()
  320. {
  321. if (this.currentAuthToken == null)
  322. await this.RefreshAuthTokenAsync(this.Credential.UserName, this.Credential.Password);
  323. string targetUrl = string.Concat(this.Credential.ApiGatewayEntryUrl + "/api/transactions/?searchType=lastOpenSaleTrx&searchValue0=userName&searchValue1=", this.Credential.UserName);
  324. _client.DefaultRequestHeaders.Clear();
  325. _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, this.currentAuthToken.AccessToken);
  326. this.Logger.LogDebug($"{_client.DefaultRequestHeaders.Authorization.Scheme}, {_client.DefaultRequestHeaders.Authorization.Parameter}");
  327. //BusinessUnitId = currentAuthToken.BusinessUnitList.Count > 0 ? currentAuthToken.BusinessUnitList.First().Id.ToString() : string.Empty;
  328. //Be aware, DeviceSN is `required` after 2019.04 for Smart Fuel v0.5
  329. _client.DefaultRequestHeaders.Add("DeviceSN", this.Credential.DeviceSN);
  330. var response = await _client.GetAsync(targetUrl).ConfigureAwait(false);
  331. if (response != null && response.IsSuccessStatusCode)
  332. {
  333. var resultStr = await response.Content.ReadAsStringAsync();
  334. if (string.IsNullOrEmpty(resultStr)) return default(T);
  335. var jd = JsonDocument.Parse(resultStr);
  336. return JsonConvert.DeserializeObject<T>(resultStr);
  337. //return jd.RootElement.GetProperty("result");
  338. }
  339. 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 ?? ""}");
  340. }
  341. public async Task<PosItem> GetPosItemAsync(string itemId)
  342. {
  343. if (this.currentAuthToken == null)
  344. await this.RefreshAuthTokenAsync(this.Credential.UserName, this.Credential.Password);
  345. string productItemUrl = string.Concat(this.Credential.ApiGatewayEntryUrl + "/api/Products/", "itemId/");
  346. this.Logger.LogDebug("GetPosItemAsync...");
  347. _client.DefaultRequestHeaders.Clear();
  348. _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, this.currentAuthToken.AccessToken);
  349. this.Logger.LogDebug($"{_client.DefaultRequestHeaders.Authorization.Scheme}, {_client.DefaultRequestHeaders.Authorization.Parameter}");
  350. //BusinessUnitId = currentAuthToken.BusinessUnitList.Count > 0 ? currentAuthToken.BusinessUnitList.First().Id.ToString() : string.Empty;
  351. //Be aware, DeviceSN is `required` after 2019.04 for Smart Fuel v0.5
  352. _client.DefaultRequestHeaders.Add("DeviceSN", this.Credential.DeviceSN);
  353. //_client.DefaultRequestHeaders.Add("CurrentBuId", BusinessUnitId);
  354. //this.logger.LogDebug($"DeviceSN: {DeviceSN}");
  355. //this.logger.LogDebug($"BuId: {BusinessUnitId}");
  356. var productUrl = string.Concat(productItemUrl, itemId);
  357. this.Logger.LogDebug($"item url: {productUrl}");
  358. var response = await _client.GetAsync(productUrl).ConfigureAwait(false);
  359. if (response != null && response.IsSuccessStatusCode)
  360. {
  361. var resultStr = await response.Content.ReadAsStringAsync();
  362. return JsonConvert.DeserializeObject<PosItem>(resultStr);
  363. }
  364. 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 ?? ""}");
  365. }
  366. public async Task<JsonElement> GetAccountIdByVBaoBeaconIdAsync(string beaconId)
  367. {
  368. if (this.currentAuthToken == null)
  369. await this.RefreshAuthTokenAsync(this.Credential.UserName, this.Credential.Password);
  370. string targetUrl = string.Concat(this.Credential.ApiGatewayEntryUrl + "/userinfo/", "vBaoId?vBaoId=", beaconId);
  371. _client.DefaultRequestHeaders.Clear();
  372. _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, this.currentAuthToken.AccessToken);
  373. this.Logger.LogDebug($"{_client.DefaultRequestHeaders.Authorization.Scheme}, {_client.DefaultRequestHeaders.Authorization.Parameter}");
  374. //BusinessUnitId = currentAuthToken.BusinessUnitList.Count > 0 ? currentAuthToken.BusinessUnitList.First().Id.ToString() : string.Empty;
  375. //Be aware, DeviceSN is `required` after 2019.04 for Smart Fuel v0.5
  376. _client.DefaultRequestHeaders.Add("DeviceSN", this.Credential.DeviceSN);
  377. var response = await _client.GetAsync(targetUrl).ConfigureAwait(false);
  378. if (response != null && response.IsSuccessStatusCode)
  379. {
  380. var resultStr = await response.Content.ReadAsStringAsync();
  381. var jd = JsonDocument.Parse(resultStr);
  382. return jd.RootElement.GetProperty("result");
  383. }
  384. throw new InvalidOperationException($"GetAccountIdByVBaoBeaconIdAsync failed due to response with code: {response?.StatusCode.ToString() ?? "-1"}, message: {response?.ReasonPhrase ?? ""}");
  385. }
  386. public async Task<Guid> CreateTransactionAsync(ClientFuelTrxInfo clientFuelTrxInfo, string trxComment)
  387. {
  388. if (this.currentAuthToken == null)
  389. await this.RefreshAuthTokenAsync(this.Credential.UserName, this.Credential.Password);
  390. Guid trxId = Guid.Empty;
  391. var trxUrl = string.Concat(this.Credential.ApiGatewayEntryUrl + "/api/transactions/");
  392. var clientPosTrx = new ClientPosTrx
  393. {
  394. RequestingCreationTimeInPosClient = DateTime.Now,
  395. Type = PosTrxType.Sale,
  396. Source = clientFuelTrxInfo.Source,
  397. Items = new List<ClientRingUpPosItem>
  398. {
  399. new ClientRingUpPosItem
  400. {
  401. PosItemUniqueId = clientFuelTrxInfo.PosItemId, //GUID
  402. Qty = clientFuelTrxInfo.Volume,
  403. ClientRingUpTime = DateTime.Now,
  404. FuelItemSoldOnPumpId = clientFuelTrxInfo.PumpId,
  405. FuelItemSoldOnPumpNozzleId = clientFuelTrxInfo.SiteNozzleNo,//SiteNozzles[(clientTrxInfo.PumpId, clientTrxInfo.NozzleId)],//fdcTrx.Nozzle.LogicalId,
  406. FuelItemOriginalGrossAmount = clientFuelTrxInfo.Amount,//(decimal)trdInfo.Mon.Value / 100,
  407. FuelItemFdcTransactionSeqNo = Convert.ToString(clientFuelTrxInfo.SeqNo),
  408. fuelItemTransactionEndTime = clientFuelTrxInfo.FuelingFinishedTime,
  409. TransactionComment = trxComment,
  410. FuelItemPumpTotalizerVolume = clientFuelTrxInfo.VolumeTotalizer
  411. }
  412. }
  413. };
  414. _client.DefaultRequestHeaders.Clear();
  415. _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, currentAuthToken.AccessToken);
  416. _client.DefaultRequestHeaders.Add("DeviceSN", this.Credential.DeviceSN);
  417. //_client.DefaultRequestHeaders.Add("CurrentBuId", BusinessUnitId);
  418. var response = await _client.PostAsJsonAsync(trxUrl, clientPosTrx).ConfigureAwait(false);
  419. if (response != null && response.IsSuccessStatusCode)
  420. {
  421. var resultStr = await response.Content.ReadAsStringAsync();
  422. var trxCreationResponse = JsonConvert.DeserializeObject<TrxCreationResponse>(resultStr);
  423. return trxCreationResponse.Id;
  424. }
  425. 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 ?? ""}");
  426. }
  427. public async Task<bool> CommitTransactionAsync(Guid trxId, PosTrxMop posTrxMop, string accountId)
  428. {
  429. if (this.currentAuthToken == null)
  430. await this.RefreshAuthTokenAsync(this.Credential.UserName, this.Credential.Password);
  431. string targetUrl = "";
  432. if (string.IsNullOrEmpty(accountId))
  433. targetUrl = string.Concat(this.Credential.ApiGatewayEntryUrl + "/api/transactions/", trxId, "/payment");
  434. else
  435. targetUrl = string.Concat(this.Credential.ApiGatewayEntryUrl + "/api/transactions/", trxId, "/payment?accountId=", accountId);
  436. //Payment detail for receipt
  437. //string version = "1.0";
  438. //var icPayDetail = new
  439. //{
  440. // CardNo = clientTrxInfo.CardNo,
  441. // PayAmount = clientTrxInfo.PayAmount,
  442. // CardHolder = clientTrxInfo.CardHolder,
  443. // AccountName = clientTrxInfo.AccountName,
  444. // CardBalance = clientTrxInfo.CurrentCardBalance,
  445. // Version = version
  446. //};
  447. //posTrxMop.Mop = new PosMop
  448. //{
  449. // Id = Guid.Parse("ff64ab36-658d-40a4-94bf-bee7231ac788"),
  450. // Name = "IC",
  451. // PaymentId = 4,
  452. // CreatedDateTime = DateTime.Now,
  453. // ChangesetId = Guid.Parse("84f02b52-6950-4c50-a0b1-9827d6459edc"),
  454. // TargetBusinessUnitId = Guid.Parse("6c40e7f6-2b2d-40de-8c5c-f5693a05ab0d"),
  455. // DisplayName = "IC卡"
  456. //};
  457. _client.DefaultRequestHeaders.Clear();
  458. _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, currentAuthToken.AccessToken);
  459. _client.DefaultRequestHeaders.Add("DeviceSN", this.Credential.DeviceSN);
  460. //_client.DefaultRequestHeaders.Add("CurrentBuId", BusinessUnitId);
  461. var response = await _client.PostAsJsonAsync(targetUrl, posTrxMop).ConfigureAwait(false);
  462. if (response != null && response.IsSuccessStatusCode)
  463. return true;
  464. throw new InvalidOperationException($"CommitTransactionAsync failed(serverUrl: {targetUrl}, deviceSN: {this.Credential?.DeviceSN ?? ""}) due to response with code: {response?.StatusCode.ToString() ?? "-1"}, message: {response?.ReasonPhrase ?? ""}");
  465. }
  466. }
  467. }