App.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. using Edge.Core.Processor;
  2. using Edge.Core.Processor.Dispatcher.Attributes;
  3. using Edge.Core.UniversalApi;
  4. using Microsoft.Extensions.DependencyInjection;
  5. using Microsoft.Extensions.Logging;
  6. using Microsoft.Extensions.Logging.Abstractions;
  7. using System;
  8. using System.Collections.Concurrent;
  9. using System.Collections.Generic;
  10. using System.Linq;
  11. using System.Threading;
  12. using System.Threading.Tasks;
  13. using VBaoProxyApp.Cloud;
  14. namespace VBaoProxyApp
  15. {
  16. [UniversalApi(Name = OnFuelTrxDoneEventName, EventDataType = typeof(object), Description = "使用短码授权加油的用户完成加油时,将触发此事件")]
  17. [UniversalApi(Name = "OnRemotePaymentFailed", EventDataType = typeof(object), Description = "当与云端进行支付流程进行中,且失败了,将触发此事件")]
  18. [MetaPartsDescriptor(
  19. "V宝车载定位",
  20. "用于与V宝车载定位站级系统的对接",
  21. new[] { "V宝" })]
  22. public class App : IAppProcessor
  23. {
  24. public const string OnFuelTrxDoneEventName = "OnFuelTrxDone";
  25. private IServiceProvider services;
  26. private Applications.FDC.FdcServerHostApp fdcServerApp;
  27. private AppConfigV1 appConfig;
  28. private TimeSpan InRangeBeaconLifeTime = new TimeSpan(0, 1, 30);
  29. private ILogger logger = NullLogger.Instance;
  30. private object syncObject = new object();
  31. public class AppConfigV1
  32. {
  33. public string UserName { get; set; } = "3038";
  34. public string Password { get; set; } = "111111";
  35. /// <summary>
  36. /// Sample: http://url:8698
  37. /// DO NOT end with slash
  38. /// </summary>
  39. public string ApiGatewayEntryUrlWithoutLastSlash { get; set; } = "http://47.97.120.160:8698";
  40. public string DeviceSN { get; set; } = "66662e74-d51e-42ae-bc89-2d39312c9c30";
  41. }
  42. public class InRangeBeacon
  43. {
  44. public string BeaconId { get; set; }
  45. public DateTime Timestamp { get; set; }
  46. public string AuthCode { get; set; }
  47. }
  48. private ConcurrentDictionary<string, InRangeBeacon> inRangeBeacons = new ConcurrentDictionary<string, InRangeBeacon>();
  49. public string MetaConfigName { get; set; }
  50. public void Init(IEnumerable<IProcessor> processors)
  51. {
  52. this.fdcServerApp =
  53. processors.WithHandlerOrApp<Applications.FDC.FdcServerHostApp>().SelectHandlerOrAppThenCast<Applications.FDC.FdcServerHostApp>().FirstOrDefault();
  54. if (this.fdcServerApp == null)
  55. {
  56. this.logger.LogInformation("There's no FdcServerHostApp defined");
  57. throw new ArgumentException("Must provide FdcServerHostApp");
  58. }
  59. //this.fdcServerApp.OnFdcFuelSaleTransactinStateChange += async (s, a) =>
  60. //{
  61. // if (a.State == Edge.Core.Database.Models.FuelSaleTransactionState.Payable)
  62. // {
  63. // var trxes = await this.fdcServerApp.GetFuelSaleTrxDetailsAsync(new ApiData()
  64. // {
  65. // Parameters = new List<ApiDataParameter>() {
  66. // new ApiDataParameter()
  67. // {
  68. // Name="releasetoken",
  69. // Value = a.Transaction.ReleaseToken.ToString()
  70. // }
  71. // }
  72. // });
  73. // var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
  74. // await universalApiHub.FireEvent(this, OnFuelTrxDoneEventName, new
  75. // {
  76. // InRangeBeacon = new InRangeBeacon()
  77. // {
  78. // AuthCode = new Random().Next(0, 9999).ToString().PadLeft(4, '0'),
  79. // BeaconId = new Random().Next(99999, 999999999).ToString().PadLeft(10, '0'),
  80. // },
  81. // FuelTrx = trxes.FirstOrDefault()
  82. // });
  83. // }
  84. //};
  85. }
  86. public App(AppConfigV1 appConfig, IServiceProvider services)
  87. {
  88. this.appConfig = appConfig;
  89. var loggerFactory = services.GetRequiredService<ILoggerFactory>();
  90. this.logger = loggerFactory.CreateLogger("DynamicPrivate_VBaoProxyApp");
  91. this.services = services;
  92. CloudHelper.Default.Credential = new CloudCredential()
  93. {
  94. ApiGatewayEntryUrl = appConfig.ApiGatewayEntryUrlWithoutLastSlash,// "http://wc.shaojun.xyz:8698",
  95. UserName = appConfig.UserName,// "507",
  96. Password = appConfig.Password,//"111111",
  97. DeviceSN = appConfig.DeviceSN,//"1234567890sss",
  98. };
  99. }
  100. public async Task<bool> Start()
  101. {
  102. var extraInfos = this.fdcServerApp.GetNozzleExtraInfos();
  103. var fuelItemBarcodes = extraInfos.GroupBy(i => i.ProductBarcode).Select(g => g.Key);
  104. foreach (var barcode in fuelItemBarcodes)
  105. {
  106. try
  107. {
  108. var cloudFuelItem = await CloudHelper.Default.GetPosItemAsync(barcode.ToString());
  109. if (cloudFuelItem == null)
  110. throw new InvalidOperationException($"Local fuel item with barcode: {barcode} could not find mapping item in cloud side.");
  111. }
  112. catch (Exception exxx)
  113. {
  114. this.logger.LogError($"Failed to retrieve Cloud side fuel item based on local barcode: {barcode}, make sure local fuel items are all" +
  115. $" correctly set and mapped(by their item id) in cloud side. error detail: {exxx}");
  116. throw;
  117. }
  118. }
  119. return true;
  120. }
  121. public Task<bool> Stop() { return Task.FromResult(true); }
  122. [UniversalApi(Description = "报告用户信标进入了感应范围,并获取验证码短码")]
  123. public async Task<InRangeBeacon> NotifyBeaconInRange(string userBeaconId)
  124. {
  125. this.logger.LogDebug($"NotifyBeaconInRange: {userBeaconId}");
  126. try
  127. {
  128. if (!this.inRangeBeacons.TryGetValue(userBeaconId, out InRangeBeacon exist)
  129. || (DateTime.Now.Subtract(exist.Timestamp) > this.InRangeBeaconLifeTime))
  130. {
  131. //remove the expired one
  132. if (exist != null)
  133. {
  134. this.logger.LogDebug($" removing an expired one...");
  135. this.inRangeBeacons.TryRemove(userBeaconId, out _);
  136. }
  137. string authCode = null;
  138. while (true)
  139. {
  140. authCode = new Random().Next(0, 9999).ToString().PadLeft(4, '0');
  141. if (this.inRangeBeacons.Values.Any(i => i.AuthCode == authCode))
  142. continue;
  143. break;
  144. }
  145. var info = new InRangeBeacon()
  146. {
  147. BeaconId = userBeaconId,
  148. Timestamp = DateTime.Now,
  149. AuthCode = authCode,
  150. };
  151. this.logger.LogDebug($" adding one with beaconId: {userBeaconId}, authCode: {authCode}");
  152. this.inRangeBeacons.TryAdd(userBeaconId, info);
  153. return info;
  154. //var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
  155. //await universalApiHub.FireEvent(this, OnUserAuthCodeGeneratedEventName, info);
  156. }
  157. else
  158. return exist;
  159. }
  160. finally
  161. {
  162. //if (lockTaken) spinLock_NotifyInRangeBeacon.Exit(false);
  163. }
  164. }
  165. [UniversalApi(Description = "验证短码是否有效")]
  166. public async Task<object> ValidateAuthCode(string authCode)
  167. {
  168. bool lockTaken = false;
  169. try
  170. {
  171. var exist = this.inRangeBeacons.FirstOrDefault(i => i.Value.AuthCode == authCode);
  172. if (exist.Value != null && (DateTime.Now.Subtract(exist.Value.Timestamp) <= this.InRangeBeaconLifeTime))
  173. {
  174. var accountResult = await CloudHelper.Default.GetAccountIdByVBaoBeaconIdAsync(exist.Value.BeaconId);
  175. var accountId = accountResult.GetProperty("userId").GetString();
  176. var vehicleLicenseNo = accountResult.GetProperty("vehicleLicenseNo").GetString();
  177. if (string.IsNullOrEmpty(accountId))
  178. {
  179. return new
  180. {
  181. OverallResult = false,
  182. FailedReason = $"Could not find account id from remote by vbao beaconId: {exist.Value.BeaconId}, need pre-regist."
  183. };
  184. }
  185. return new
  186. {
  187. OverallResult = true,
  188. VehicleLicenseNo = vehicleLicenseNo,
  189. };
  190. }
  191. if (exist.Value != null)
  192. {
  193. this.logger.LogDebug($"Validating authCode: {authCode}, but it was expired.");
  194. this.inRangeBeacons.TryRemove(exist.Key, out _);
  195. }
  196. this.logger.LogDebug($"Validating authCode: {authCode} with failed.");
  197. return new
  198. {
  199. OverallResult = false,
  200. FailedReason = "无效的短码"
  201. };
  202. }
  203. catch (Exception eeee)
  204. {
  205. return new
  206. {
  207. OverallResult = false,
  208. FailedReason = $"Unknown reason for retrieve the car plate No. from remote, detail: {eeee}",
  209. };
  210. }
  211. finally
  212. {
  213. //if (lockTaken) spinLock_NotifyInRangeBeacon.Exit(false);
  214. }
  215. }
  216. [UniversalApi(Description = "使用掉authcode,并提供相关联的fuel trx release token")]
  217. public async Task<object> NotifyAuthCodeConsumedWithFuelTrx(string authCode, int fuelTrxReleaseToken)
  218. {
  219. bool lockTaken = false;
  220. try
  221. {
  222. //spinLock_NotifyInRangeBeacon.Enter(ref lockTaken);
  223. var exist = this.inRangeBeacons.FirstOrDefault(i => i.Value.AuthCode == authCode);
  224. if (exist.Value == null)
  225. {
  226. this.logger.LogDebug($"Consuming authCode: {authCode} failed due to no such auth code.");
  227. return new
  228. {
  229. OverallResult = false,
  230. FailedReason = $"Consuming authCode: {authCode} failed due to no such auth code."
  231. };
  232. }
  233. this.logger.LogDebug($"Consuming authCode: {authCode}, then GetFuelSaleTrxDetails for releaseToken: {fuelTrxReleaseToken}");
  234. var trxes = await this.fdcServerApp.GetFuelSaleTrxDetailsAsync(new ApiData()
  235. {
  236. Parameters = new List<ApiDataParameter>() {
  237. new ApiDataParameter()
  238. {
  239. Name="releasetoken",
  240. Value = fuelTrxReleaseToken.ToString()
  241. }
  242. }
  243. });
  244. if (trxes == null || !trxes.Any())
  245. {
  246. //this.logger.LogDebug($" trxes details retrieved, count: {trxes?.Count() ?? -1}");
  247. this.logger.LogDebug($"Consuming authCode: {authCode} failed due to no such fule trx in local.");
  248. return new
  249. {
  250. OverallResult = false,
  251. FailedReason = $"Consuming authCode: {authCode} partial failed(code consumed) due to no such fule trx."
  252. };
  253. }
  254. var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
  255. var pumpId = int.Parse(trxes.First().GetType().GetProperty("PumpId").GetValue(trxes.First()).ToString());
  256. var logicalNozzleId = int.Parse(trxes.First().GetType().GetProperty("LogicalNozzleId").GetValue(trxes.First()).ToString());
  257. var trxSeqNoFromPhysicalPump = int.Parse(trxes.First().GetType().GetProperty("TransactionSeqNumberFromPhysicalPump").GetValue(trxes.First()).ToString());
  258. var fuelItemBarcode = trxes.First().GetType().GetProperty("ProductBarcode").GetValue(trxes.First()).ToString();
  259. var amount = decimal.Parse(trxes.First().GetType().GetProperty("Amount").GetValue(trxes.First()).ToString());
  260. var volume = decimal.Parse(trxes.First().GetType().GetProperty("Volume").GetValue(trxes.First()).ToString());
  261. var price = decimal.Parse(trxes.First().GetType().GetProperty("Price").GetValue(trxes.First()).ToString());
  262. var siteLevelNozzleId = int.Parse(trxes.First().GetType().GetProperty("SiteLevelNozzleId").GetValue(trxes.First()).ToString());
  263. this.logger.LogDebug($" local fuel trx details-> pumpId: {pumpId}, logicalNozzleId: {logicalNozzleId}, amount: {amount}, volume: {volume}");
  264. var fdcClientId = 987654;
  265. var lockedFuelTrx = await this.fdcServerApp.LockFuelSaleTrxAndNotifyAllFdcClientsAsync(
  266. fdcClientId, pumpId, trxSeqNoFromPhysicalPump, fuelTrxReleaseToken);
  267. if (lockedFuelTrx == null)
  268. {
  269. this.logger.LogError($"Local locking trx with releaseToken: {fuelTrxReleaseToken} failed.");
  270. await universalApiHub.FireEvent(this, "OnRemotePaymentFailed", new
  271. {
  272. FuelTrx = trxes.FirstOrDefault(),
  273. FailedReason = $"Local fuel trx with releaseToken: {fuelTrxReleaseToken} locking failed."
  274. });
  275. return new
  276. {
  277. OverallResult = false,
  278. FailedReason = $"Local fuel trx with releaseToken: {fuelTrxReleaseToken} locking failed."
  279. };
  280. }
  281. var cloudFuelItem = await CloudHelper.Default.GetPosItemAsync(fuelItemBarcode);
  282. if (cloudFuelItem == null)
  283. {
  284. this.logger.LogError($"Retreieve cloud pos item failed for fuel trx with releaseToken: {fuelTrxReleaseToken} with fuel item barcode: {fuelItemBarcode}");
  285. await universalApiHub.FireEvent(this, "OnRemotePaymentFailed", new
  286. {
  287. FuelTrx = trxes.FirstOrDefault(),
  288. FailedReason = $"Local fuel item with barcode: {fuelItemBarcode} could not find mapping item in cloud side."
  289. });
  290. return new
  291. {
  292. OverallResult = false,
  293. FailedReason = $"Local fuel item with barcode: {fuelItemBarcode} could not find mapping item in cloud side."
  294. };
  295. }
  296. var createdTrxId = await CloudHelper.Default.CreateTransactionAsync(new ClientFuelTrxInfo()
  297. {
  298. PosItemId = cloudFuelItem.Id,
  299. PumpId = pumpId,
  300. NozzleId = logicalNozzleId,
  301. SiteNozzleNo = siteLevelNozzleId,
  302. Amount = amount,
  303. Volume = volume,
  304. Barcode = int.Parse(cloudFuelItem.BarCode),
  305. PayAmount = amount,
  306. Source = Dfs.WayneChina.PosModelMini.PosTrxSource.Outdoor,
  307. UnitPrice = price
  308. }, "vBao pay");
  309. var accountResult = await CloudHelper.Default.GetAccountIdByVBaoBeaconIdAsync(exist.Value.BeaconId);
  310. var accountId = accountResult.GetProperty("userId").GetString();
  311. var vehicleLicenseNo = accountResult.GetProperty("vehicleLicenseNo").GetString();
  312. if (string.IsNullOrEmpty(accountId))
  313. {
  314. await universalApiHub.FireEvent(this, "OnRemotePaymentFailed", new
  315. {
  316. FuelTrx = trxes.FirstOrDefault(),
  317. FailedReason = $"Could not find account id from remote by vbao beaconId: {exist.Value.BeaconId}, need pre-regist."
  318. });
  319. return new
  320. {
  321. OverallResult = false,
  322. FailedReason = $"Could not find account id from remote by vbao beaconId: {exist.Value.BeaconId}, need pre-regist."
  323. };
  324. }
  325. var commitResult = await CloudHelper.Default.CommitTransactionAsync(createdTrxId, new Dfs.WayneChina.PosModelMini.PosTrxMop()
  326. {
  327. Mop = new Dfs.WayneChina.PosModelMini.PosMop()
  328. {
  329. PaymentId = (int)PaymentID.MembershipPay,
  330. },
  331. Paid = amount
  332. }, accountId);
  333. if (!commitResult)
  334. {
  335. this.logger.LogError($"Commit trx to cloud failed for fuel trx with releaseToken: {fuelTrxReleaseToken}");
  336. await universalApiHub.FireEvent(this, "OnRemotePaymentFailed", new
  337. {
  338. FuelTrx = trxes.FirstOrDefault(),
  339. FailedReason = $"Commit trx to remote failed with MembershipPay, reason unknown."
  340. });
  341. return new
  342. {
  343. OverallResult = false,
  344. FailedReason = $"Commit trx to remote failed with MembershipPay, reason unknown."
  345. };
  346. }
  347. var clearedFuelTrx = await this.fdcServerApp.ClearFuelSaleTrxAndNotifyAllFdcClientsAsync(
  348. pumpId, trxSeqNoFromPhysicalPump.ToString(), fuelTrxReleaseToken, fdcClientId.ToString());
  349. if (clearedFuelTrx == null)
  350. {
  351. this.logger.LogError($"Clear local fuel trx failed for fuel trx with releaseToken: {fuelTrxReleaseToken}");
  352. await universalApiHub.FireEvent(this, "OnRemotePaymentFailed", new
  353. {
  354. FuelTrx = trxes.FirstOrDefault(),
  355. FailedReason = $"Local fuel trx with releaseToken: {fuelTrxReleaseToken} clear failed."
  356. });
  357. return new
  358. {
  359. OverallResult = false,
  360. FailedReason = $"Local fuel trx with releaseToken: {fuelTrxReleaseToken} clear failed."
  361. };
  362. }
  363. await universalApiHub.FireEvent(this, OnFuelTrxDoneEventName, new
  364. {
  365. InRangeBeacon = new { exist.Value.BeaconId, exist.Value.Timestamp, exist.Value.AuthCode },
  366. FuelTrx = trxes.FirstOrDefault()
  367. });
  368. this.inRangeBeacons.TryRemove(exist.Key, out _);
  369. this.logger.LogInformation($"Succeed finished a fuel trx payment with cloud and local with releaseToken: {fuelTrxReleaseToken}");
  370. return new
  371. {
  372. OverallResult = true,
  373. VehicleLicenseNo = vehicleLicenseNo,
  374. //FailedReason = "",
  375. };
  376. }
  377. catch (Exception eeee)
  378. {
  379. return new
  380. {
  381. OverallResult = false,
  382. FailedReason = $"Unknown reason to failed the payment from remote, detail: {eeee}",
  383. };
  384. }
  385. finally
  386. {
  387. //if (lockTaken) spinLock_NotifyInRangeBeacon.Exit(false);
  388. }
  389. }
  390. }
  391. }