App.cs 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  1. using Edge.Core.Processor;
  2. using Microsoft.EntityFrameworkCore;
  3. using Microsoft.Extensions.DependencyInjection;
  4. using Microsoft.Extensions.Logging;
  5. using Microsoft.Extensions.Logging.Abstractions;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Linq;
  9. using System.Threading.Tasks;
  10. using Gateway.POS.Models;
  11. using Edge.Core.UniversalApi;
  12. using AutoMapper;
  13. using Edge.Core.Processor.Dispatcher.Attributes;
  14. using Gateway.Payment.Shared;
  15. namespace Gateway.POS
  16. {
  17. [MetaPartsDescriptor(
  18. "lang-zh-cn:轻简POSlang-en-us:Lite POS",
  19. "lang-zh-cn:轻简POSlang-en-us:Lite POS",
  20. new[] { "lang-zh-cn:轻简POSlang-en-us:LitePOS" })]
  21. public partial class App : IAppProcessor
  22. {
  23. private bool enableTestingMode = false;
  24. private Gateway.Payment.App paymentApp;
  25. private ClassicCpuCardApp.App classicCpuCardApp;
  26. private ILogger logger = NullLogger.Instance;
  27. private IServiceProvider services;
  28. public string MetaConfigName { get; set; }
  29. /// <summary>
  30. /// must be 6 digits
  31. /// </summary>
  32. private string siteId = "abcdef";
  33. public void Init(IEnumerable<IProcessor> processors)
  34. {
  35. this.paymentApp =
  36. processors.WithHandlerOrApp<Gateway.Payment.App>().SelectHandlerOrAppThenCast<Gateway.Payment.App>().FirstOrDefault();
  37. if (this.paymentApp == null)
  38. this.logger.LogInformation("There's no Gateway.Payment.App defined, so only support cash payment.");
  39. this.classicCpuCardApp =
  40. processors.WithHandlerOrApp<ClassicCpuCardApp.App>().SelectHandlerOrAppThenCast<ClassicCpuCardApp.App>().FirstOrDefault();
  41. if (this.classicCpuCardApp == null)
  42. this.logger.LogInformation("There's no ClassicCpuCardApp.App defined, so no support for cpu card.");
  43. }
  44. public App(bool enableTestingMode, IServiceProvider services)
  45. {
  46. this.services = services;
  47. var loggerFactory = services.GetRequiredService<ILoggerFactory>();
  48. this.logger = loggerFactory.CreateLogger("DynamicPrivate_Gateway.POS");
  49. this.enableTestingMode = enableTestingMode;
  50. }
  51. public Task<bool> Start()
  52. {
  53. this.logger.LogInformation("Migrating database...");
  54. var migrateDbContext = new PosAppDbContext();
  55. try
  56. {
  57. migrateDbContext.Database.Migrate();
  58. }
  59. catch (Exception exx)
  60. {
  61. string migrationsStr = "";
  62. string pendingMigrationsStr = "";
  63. string appliedMigrationsStr = "";
  64. try
  65. {
  66. migrationsStr = migrateDbContext.Database?.GetMigrations()?.Aggregate("", (acc, n) => acc + ", " + n) ?? "";
  67. pendingMigrationsStr = migrateDbContext.Database?.GetPendingMigrations()?.Aggregate("", (acc, n) => acc + ", " + n) ?? "";
  68. appliedMigrationsStr = migrateDbContext.Database?.GetAppliedMigrations()?.Aggregate("", (acc, n) => acc + ", " + n) ?? "";
  69. }
  70. catch
  71. {
  72. }
  73. this.logger.LogError("Gateway.POS App Exceptioned when Migrating the database, detail: " +
  74. exx + System.Environment.NewLine +
  75. "migrations are: " + migrationsStr + System.Environment.NewLine +
  76. ", pendingMigrations are: " + pendingMigrationsStr + System.Environment.NewLine +
  77. ", appliedMigrations are: " + appliedMigrationsStr);
  78. throw new InvalidOperationException("failed for migrating the database");
  79. }
  80. this.logger.LogInformation(" Migrate database finished.");
  81. return Task.FromResult(true);
  82. }
  83. //Task<bool> Stop() { return Task.FromResult(true); }
  84. [UniversalApi]
  85. public async Task<TransactionOutputDto> Commit(TransactionInputDto input)
  86. {
  87. var mapper = this.services.GetRequiredService<IMapper>();
  88. var trx = mapper.Map<Transaction>(input);
  89. trx.ServerSideTimestamp = DateTime.Now;
  90. try
  91. {
  92. var dbContext = new PosAppDbContext();
  93. var foundOp = await dbContext.Operators.FindAsync(new object[] { input.OperatorId });
  94. if (foundOp == null)
  95. {
  96. var output = mapper.Map<TransactionOutputDto>(trx);
  97. output.CommitState = new TransactionCommitState()
  98. {
  99. StateCode = 400,
  100. Reason = "Input OperatorId does not map to existed operator.",
  101. };
  102. return output;
  103. }
  104. trx.Operator = foundOp;
  105. if (trx.TransactionType == TransactionTypeEnum.Sale
  106. && (trx.Payments == null || !trx.Payments.Any()))
  107. {
  108. var output = mapper.Map<TransactionOutputDto>(trx);
  109. output.CommitState = new TransactionCommitState()
  110. {
  111. StateCode = 400,
  112. Reason = "Must provide at least one Payment.",
  113. };
  114. return output;
  115. }
  116. if (trx.TransactionType == TransactionTypeEnum.Sale && input.RefundTrxId != null)
  117. {
  118. var output = mapper.Map<TransactionOutputDto>(trx);
  119. output.CommitState = new TransactionCommitState()
  120. {
  121. StateCode = 400,
  122. Reason = "Trx with type Sale should not providing RefundTrxId.",
  123. };
  124. return output;
  125. }
  126. if (trx.TransactionType == TransactionTypeEnum.Sale
  127. && trx.Payments.Select(p => p.Method).Except(new[] { PaymentMethodEnum.Cash, PaymentMethodEnum.ClassicCpuCard, PaymentMethodEnum.DirectMembershipProfitAccountFundRedeem }).Any()
  128. && this.paymentApp == null)
  129. {
  130. var output = mapper.Map<TransactionOutputDto>(trx);
  131. output.CommitState = new TransactionCommitState()
  132. {
  133. StateCode = 400,
  134. Reason = "Only support Cash due to no other Mop enabled.",
  135. };
  136. return output;
  137. }
  138. if (trx.TransactionType == TransactionTypeEnum.Sale && trx.Payments.Count >= 2)
  139. {
  140. var output = mapper.Map<TransactionOutputDto>(trx);
  141. output.CommitState = new TransactionCommitState()
  142. {
  143. StateCode = 400,
  144. Reason = "Only support single payment for a trx.",
  145. };
  146. return output;
  147. }
  148. if (trx.TransactionType == TransactionTypeEnum.Sale && trx.Payments.Sum(p => p.ExpectAmount) != trx.NetAmount)
  149. {
  150. var output = mapper.Map<TransactionOutputDto>(trx);
  151. output.CommitState = new TransactionCommitState()
  152. {
  153. StateCode = 400,
  154. Reason = "Payment(s) expect amount(s) must exactly equals Trx.NetAmount since split payment is not support yet.",
  155. };
  156. return output;
  157. }
  158. if (trx.TransactionType == TransactionTypeEnum.Sale)
  159. {
  160. trx.ReceiptId = DateTime.Now.ToString("yyMMddHHmmssfff" + (trx.Operator?.Id.ToString() ?? "a"));
  161. if (trx.Payments.First().Method == PaymentMethodEnum.Cash)
  162. {
  163. trx.Payments.First().PaidAmount = trx.Payments.First().ExpectAmount;
  164. trx.Payments.First().TradeStatus = TradeStatusEnum.SUCCESS;
  165. }
  166. else
  167. {
  168. if (this.enableTestingMode)
  169. trx.NetAmount = new decimal(0.02);
  170. this.logger.LogDebug($"Committing with TrxType.Sale with MOP: {trx.Payments.First().Method }, netAmount: { trx.NetAmount.Value}, authCode: {trx.Payments.First().AuthCode}");
  171. if (trx.Payments.First().Method == PaymentMethodEnum.ClassicCpuCard)
  172. {
  173. var unitOfWorkDb = dbContext;
  174. var redeemInput = new Gateway.POS.Models.MembershipProfitAccountRedeemRequest();
  175. redeemInput.SourceTrx = trx;
  176. redeemInput.CreateByOperatorId = trx.OperatorId;
  177. redeemInput.Description = trx.Comment;
  178. redeemInput.ProfitAccountType = Gateway.POS.Models.MembershipProfitAccountProfitTypeEnum.Fund;
  179. redeemInput.Purpose = trx.Comment;
  180. redeemInput.RedeemAuthCode = trx.Payments.First().AuthCode;
  181. redeemInput.RedeemProfitAmount = trx.NetAmount;
  182. var redeemResult = await this.InternalRedeemMembershipProfitByOpenClassicCpuCardReaderAndReadCard(trx.Payments.First().CardReaderName, redeemInput, dbContext);
  183. if (redeemResult.OverallResultCode == 200)
  184. {
  185. this.logger.LogDebug(" Succeed for Commit with TrxType.Sale with MOP: " + trx.Payments.First().Method + ", netAmount: " + trx.NetAmount.Value);
  186. trx.Payments.First().PaidAmount = trx.NetAmount.Value;// trx.Payments.First().ExpectAmount;
  187. trx.Payments.First().TradeStatus = TradeStatusEnum.SUCCESS;
  188. trx.MembershipProfitAccountRedeem = redeemResult.Data as MembershipProfitAccountRedeem;
  189. }
  190. else
  191. {
  192. this.logger.LogDebug($" Failed for Commit with TrxType.Sale with MOP: {trx.Payments.First().Method}, netAmount: {trx.NetAmount.Value }: {redeemResult.Message.ToString()}");
  193. var output = mapper.Map<TransactionOutputDto>(trx);
  194. output.CommitState = new TransactionCommitState()
  195. {
  196. StateCode = 500,
  197. Reason = $"CPU卡支付失败: {redeemResult.Message}"
  198. };
  199. return output;
  200. }
  201. }
  202. else if (trx.Payments.First().Method == PaymentMethodEnum.DirectMembershipProfitAccountFundRedeem)
  203. {
  204. if (!trx.Payments.First().TargetMembershipSubAccountId.HasValue)
  205. {
  206. var output = mapper.Map<TransactionOutputDto>(trx);
  207. output.CommitState = new TransactionCommitState()
  208. {
  209. StateCode = 400,
  210. Reason = $"DirectMembershipProfitAccountFundRedeem must specify entry TargetMembershipSubAccountId in Payment"
  211. };
  212. return output;
  213. }
  214. var unitOfWorkDb = dbContext;
  215. var redeemInput = new Gateway.POS.Models.MembershipProfitAccountRedeemRequest();
  216. redeemInput.SourceTrx = trx;
  217. redeemInput.CreateByOperatorId = trx.OperatorId;
  218. redeemInput.Description = trx.Comment;
  219. redeemInput.ProfitAccountType = Gateway.POS.Models.MembershipProfitAccountProfitTypeEnum.Fund;
  220. redeemInput.Purpose = trx.Comment;
  221. redeemInput.RedeemAuthCode = trx.Payments.First().AuthCode;
  222. redeemInput.RedeemProfitAmount = trx.NetAmount;
  223. var redeemResult = await this.InternalRedeemMembershipProfit(trx.Payments.First().TargetMembershipSubAccountId.Value, redeemInput, dbContext);
  224. if (redeemResult.OverallResultCode == 200)
  225. {
  226. this.logger.LogDebug(" Succeed for Commit with TrxType.Sale with MOP: " + trx.Payments.First().Method + ", netAmount: " + trx.NetAmount.Value);
  227. trx.Payments.First().PaidAmount = trx.NetAmount.Value;// trx.Payments.First().ExpectAmount;
  228. trx.Payments.First().TradeStatus = TradeStatusEnum.SUCCESS;
  229. trx.MembershipProfitAccountRedeem = redeemResult.Data as MembershipProfitAccountRedeem;
  230. }
  231. else
  232. {
  233. this.logger.LogDebug($" Failed for Commit with TrxType.Sale with MOP: {trx.Payments.First().Method}, netAmount: {trx.NetAmount.Value }: {redeemResult.Message.ToString()}");
  234. var output = mapper.Map<TransactionOutputDto>(trx);
  235. output.CommitState = new TransactionCommitState()
  236. {
  237. StateCode = 500,
  238. Reason = $"DirectMembershipProfitAccountFundRedeem Payment failed with: {redeemResult.Message}"
  239. };
  240. return output;
  241. }
  242. }
  243. else
  244. {
  245. var po = await this.paymentApp.StartPayment(new PaymentOrderDto()
  246. {
  247. AuthCode = trx.Payments.First().AuthCode,
  248. PaymentMethod = trx.Payments.First().Method.ToString(),
  249. NetAmount = trx.NetAmount.Value,
  250. TotalAmount = trx.TotalAmount ?? trx.NetAmount.Value,
  251. OperatorId = trx.OperatorId.ToString(),
  252. SiteId = trx.SiteId,
  253. TerminalId = trx.TerminalId,
  254. Title = "sale from local fusion at " + DateTime.Now.ToLongTimeString(),
  255. IsForRefund = false
  256. });
  257. if (po.TradeStatus == TradeStatusEnum.SUCCESS)
  258. {
  259. this.logger.LogDebug(" Succeed for Commit with TrxType.Sale with MOP: " + trx.Payments.First().Method + ", netAmount: " + trx.NetAmount.Value + ", billNumber: " + po.BillNumber);
  260. trx.Payments.First().PaidAmount = trx.Payments.First().ExpectAmount;
  261. trx.Payments.First().BillNumber = po.BillNumber;
  262. trx.Payments.First().TradeStatus = TradeStatusEnum.SUCCESS;
  263. }
  264. else
  265. {
  266. this.logger.LogDebug($" Failed for Commit with TrxType.Sale with MOP: {trx.Payments.First().Method}, netAmount: {trx.NetAmount.Value }, Payment failed with PaymentResultMessage: {po.PayResults.FirstOrDefault()?.PaymentResultMessage ?? ""}, PaymentResultCode: { po.PayResults.FirstOrDefault()?.PaymentResultCode ?? ""}, PaymentResultErrorDetail: { po.PayResults.FirstOrDefault()?.PaymentResultErrorDetail ?? ""}, PaymentResultRawResult: { po.PayResults.FirstOrDefault()?.PaymentResultRawResult ?? ""}");
  267. var output = mapper.Map<TransactionOutputDto>(trx);
  268. output.CommitState = new TransactionCommitState()
  269. {
  270. StateCode = 500,
  271. Reason = $"Payment failed with PaymentResultMessage: {po.PayResults.FirstOrDefault()?.PaymentResultMessage ?? ""}, PaymentResultCode: {po.PayResults.FirstOrDefault()?.PaymentResultCode ?? ""}, PaymentResultErrorDetail: {po.PayResults.FirstOrDefault()?.PaymentResultErrorDetail ?? ""}, PaymentResultRawResult: {po.PayResults.FirstOrDefault()?.PaymentResultRawResult ?? ""}"
  272. };
  273. return output;
  274. }
  275. }
  276. }
  277. if (trx.TransactionSubType == TransactionSubTypeEnum.MembershipProfitAccount_Recharge_Fund_ToAccount_ById)
  278. {
  279. if (trx.Payments.First().TradeStatus != TradeStatusEnum.SUCCESS)
  280. {
  281. var output = mapper.Map<TransactionOutputDto>(trx);
  282. output.CommitState = new TransactionCommitState()
  283. {
  284. StateCode = 500,
  285. Reason = $"Recharge_Fund_ToAccount failed at Payment stage"
  286. };
  287. return output;
  288. }
  289. if ((trx.Payments.First().TargetMembershipAccountId ?? 0) == 0)
  290. {
  291. var output = mapper.Map<TransactionOutputDto>(trx);
  292. output.CommitState = new TransactionCommitState()
  293. {
  294. StateCode = 500,
  295. Reason = $"Recharge_Fund_ToAccount failed at Recharge stage, must specify the Target Membership Account Id, and a refund may requried since the real payment has done."
  296. };
  297. return output;
  298. }
  299. var unitOfWorkDb = dbContext;
  300. var recharge = new Gateway.POS.Models.MembershipProfitAccountRechargeRequest();
  301. recharge.SourceTrx = trx;
  302. recharge.CreateByOperatorId = trx.OperatorId;
  303. recharge.Description = trx.Comment;
  304. recharge.RechargeSource = ProfitAccountRechargeSourceEnum.BySystemTrx;
  305. recharge.RechargeAmount = trx.Payments.First().PaidAmount;
  306. var rechargeResult = await this.InternalRechargeMembershipAccount(
  307. trx.Payments.First().TargetMembershipAccountId.Value,
  308. MembershipProfitAccountProfitTypeEnum.Fund, recharge, unitOfWorkDb);
  309. if (rechargeResult.OverallResultCode == 200)
  310. {
  311. this.logger.LogDebug(" Succeed for Recharge with TrxType.Sale with MOP: " + trx.Payments.First().Method + ", netAmount: " + trx.NetAmount.Value);
  312. trx.MembershipProfitAccountRecharge = rechargeResult.Data as MembershipProfitAccountRecharge;
  313. }
  314. else
  315. {
  316. var output = mapper.Map<TransactionOutputDto>(trx);
  317. output.CommitState = new TransactionCommitState()
  318. {
  319. StateCode = 500,
  320. Reason = $"Recharge_Fund_ToAccount failed at Recharge stage, and a refund may requried since the real payment has done: {rechargeResult.Message ?? ""}"
  321. };
  322. return output;
  323. }
  324. }
  325. else if (trx.TransactionSubType == TransactionSubTypeEnum.MembershipProfitAccount_Recharge_Fund_ToSubAccount_ById)
  326. {
  327. if (trx.Payments.First().TradeStatus != TradeStatusEnum.SUCCESS)
  328. {
  329. var output = mapper.Map<TransactionOutputDto>(trx);
  330. output.CommitState = new TransactionCommitState()
  331. {
  332. StateCode = 500,
  333. Reason = $"Recharge_Fund_ToSubAccount failed at Payment stage"
  334. };
  335. return output;
  336. }
  337. if ((trx.Payments.First().TargetMembershipSubAccountId ?? 0) == 0)
  338. {
  339. var output = mapper.Map<TransactionOutputDto>(trx);
  340. output.CommitState = new TransactionCommitState()
  341. {
  342. StateCode = 500,
  343. Reason = $"Recharge_Fund_ToSubAccount failed at Recharge stage, must specify the Target Membership SubAccount Id, and a refund may requried since the real payment has done."
  344. };
  345. return output;
  346. }
  347. var unitOfWorkDb = dbContext;
  348. var recharge = new Gateway.POS.Models.MembershipProfitAccountRechargeRequest();
  349. recharge.SourceTrx = trx;
  350. recharge.CreateByOperatorId = trx.OperatorId;
  351. recharge.Description = trx.Comment;
  352. recharge.RechargeSource = ProfitAccountRechargeSourceEnum.BySystemTrx;
  353. recharge.RechargeAmount = trx.Payments.First().PaidAmount;
  354. var rechargeResult = await this.InternalRechargeMembershipSubAccount(
  355. trx.Payments.First().TargetMembershipSubAccountId.Value,
  356. MembershipProfitAccountProfitTypeEnum.Fund, recharge, unitOfWorkDb);
  357. if (rechargeResult.OverallResultCode == 200)
  358. {
  359. this.logger.LogDebug(" Succeed for Recharge with TrxType.Sale with MOP: " + trx.Payments.First().Method + ", netAmount: " + trx.NetAmount.Value);
  360. trx.MembershipProfitAccountRecharge = rechargeResult.Data as MembershipProfitAccountRecharge;
  361. }
  362. else
  363. {
  364. var output = mapper.Map<TransactionOutputDto>(trx);
  365. output.CommitState = new TransactionCommitState()
  366. {
  367. StateCode = 500,
  368. Reason = $"Recharge_Fund_ToSubAccount failed at Recharge stage, and a refund may requried since the real payment has done: {rechargeResult.Message ?? ""}"
  369. };
  370. return output;
  371. }
  372. }
  373. else if (trx.TransactionSubType == TransactionSubTypeEnum.MembershipProfitAccount_Recharge_Fund_ToSubAccount_ByCard)
  374. {
  375. if (trx.Payments.First().TradeStatus != TradeStatusEnum.SUCCESS)
  376. {
  377. var output = mapper.Map<TransactionOutputDto>(trx);
  378. output.CommitState = new TransactionCommitState()
  379. {
  380. StateCode = 500,
  381. Reason = $"Recharge_Fund_ToSubAccount failed at Payment stage"
  382. };
  383. return output;
  384. }
  385. var unitOfWorkDb = dbContext;
  386. var recharge = new Gateway.POS.Models.MembershipProfitAccountRechargeRequest();
  387. recharge.SourceTrx = trx;
  388. recharge.CreateByOperatorId = trx.OperatorId;
  389. recharge.Description = trx.Comment;
  390. recharge.RechargeSource = ProfitAccountRechargeSourceEnum.BySystemTrx;
  391. recharge.RechargeAmount = trx.Payments.First().PaidAmount;
  392. var rechargeResult = await this.InternalRechargeMembershipSubAccountByOpenClassicCpuCardReaderAndReadCard(
  393. trx.Payments.First().CardReaderName,
  394. MembershipProfitAccountProfitTypeEnum.Fund, recharge, unitOfWorkDb);
  395. if (rechargeResult.OverallResultCode == 200)
  396. {
  397. this.logger.LogDebug(" Succeed for Recharge with TrxType.Sale with MOP: " + trx.Payments.First().Method + ", netAmount: " + trx.NetAmount.Value);
  398. trx.MembershipProfitAccountRecharge = rechargeResult.Data as MembershipProfitAccountRecharge;
  399. }
  400. else
  401. {
  402. var output = mapper.Map<TransactionOutputDto>(trx);
  403. output.CommitState = new TransactionCommitState()
  404. {
  405. StateCode = 500,
  406. Reason = $"Recharge_Fund_ToSubAccount failed at Recharge stage, and a refund may requried since the real payment has done: {rechargeResult.Message ?? ""}"
  407. };
  408. return output;
  409. }
  410. }
  411. }
  412. else if (trx.TransactionType == TransactionTypeEnum.Refund)
  413. {
  414. if (input.RefundTrxId == null)
  415. {
  416. var output = mapper.Map<TransactionOutputDto>(trx);
  417. output.CommitState = new TransactionCommitState()
  418. {
  419. StateCode = 400,
  420. Reason = "Must provide original trx id for refund.",
  421. };
  422. return output;
  423. }
  424. var db = new PosAppDbContext();
  425. var originalTrx = await db.Transactions.Include(t => t.Payments).FirstOrDefaultAsync(t => t.Id == input.RefundTrxId);
  426. if (originalTrx == null)
  427. {
  428. var output = mapper.Map<TransactionOutputDto>(trx);
  429. output.CommitState = new TransactionCommitState()
  430. {
  431. StateCode = 400,
  432. Reason = $"Could not find original trx with id: {input.RefundTrxId.Value} for refund.",
  433. };
  434. return output;
  435. }
  436. if (originalTrx.TransactionType != TransactionTypeEnum.Sale)
  437. {
  438. var output = mapper.Map<TransactionOutputDto>(trx);
  439. output.CommitState = new TransactionCommitState()
  440. {
  441. StateCode = 400,
  442. Reason = $"Only can refund a previous exists Sale type trx.",
  443. };
  444. return output;
  445. }
  446. trx.ReceiptId = "R" + (originalTrx.ReceiptId ?? "");
  447. PaymentOrder paymentOrder = null;
  448. if (originalTrx.Payments.First().Method != PaymentMethodEnum.Cash)
  449. {
  450. if (string.IsNullOrEmpty(originalTrx.Payments.First().BillNumber))
  451. {
  452. var output = mapper.Map<TransactionOutputDto>(trx);
  453. output.CommitState = new TransactionCommitState()
  454. {
  455. StateCode = 400,
  456. Reason = $"Original trx does not contains the BillNumber in payment info which is the critical info for refund operation, contact administrator.",
  457. };
  458. return output;
  459. }
  460. this.logger.LogDebug("Committing with TrxType.Refund with MOP: " + trx.Payments.First().Method + ", netAmount: " + trx.NetAmount.Value + ", billNumber: " + originalTrx.Payments.First().BillNumber);
  461. paymentOrder = await this.paymentApp.StartPayment(new PaymentOrderDto()
  462. {
  463. AuthCode = originalTrx.Payments.First().AuthCode,
  464. PaymentMethod = originalTrx.Payments.First().Method.ToString(),
  465. BillNumber = originalTrx.Payments.First().BillNumber,
  466. NetAmount = originalTrx.Payments.First().PaidAmount,
  467. TotalAmount = originalTrx.TotalAmount ?? originalTrx.NetAmount.Value,
  468. OperatorId = originalTrx.OperatorId.ToString(),
  469. SiteId = originalTrx.SiteId,
  470. TerminalId = originalTrx.TerminalId,
  471. Title = "refund from local fusion at " + DateTime.Now.ToLongTimeString(),
  472. IsForRefund = true
  473. });
  474. }
  475. else
  476. {
  477. //cash always let it succeed.
  478. paymentOrder = new PaymentOrder();
  479. paymentOrder.TradeStatus = TradeStatusEnum.SUCCESS;
  480. }
  481. if (paymentOrder.TradeStatus == TradeStatusEnum.SUCCESS)
  482. {
  483. this.logger.LogDebug(" Succeed Committing with TrxType.Refund with MOP: " + trx.Payments.First().Method + ", netAmount: " + trx.NetAmount.Value + ", billNumber: " + originalTrx.Payments.First().BillNumber);
  484. trx.TotalAmount = -originalTrx.TotalAmount;
  485. trx.NetAmount = -originalTrx.NetAmount;
  486. trx.Payments.First().TradeStatus = TradeStatusEnum.SUCCESS;
  487. trx.Payments.First().ExpectAmount = -originalTrx.Payments.First().ExpectAmount;
  488. trx.Payments.First().PaidAmount = -originalTrx.Payments.First().PaidAmount;
  489. }
  490. else
  491. {
  492. this.logger.LogDebug($" Failed for Commit with TrxType.Refund with MOP: {trx.Payments.First().Method}, netAmount: {trx.NetAmount.Value }, billNumber: {originalTrx.Payments.First().BillNumber}, Payment failed with PaymentResultMessage: {paymentOrder.PayResults.FirstOrDefault()?.PaymentResultMessage ?? ""}, PaymentResultCode: { paymentOrder.PayResults.FirstOrDefault()?.PaymentResultCode ?? ""}, PaymentResultErrorDetail: { paymentOrder.PayResults.FirstOrDefault()?.PaymentResultErrorDetail ?? ""}, PaymentResultRawResult: { paymentOrder.PayResults.FirstOrDefault()?.PaymentResultRawResult ?? ""}");
  493. var output = mapper.Map<TransactionOutputDto>(trx);
  494. output.CommitState = new TransactionCommitState()
  495. {
  496. StateCode = 500,
  497. Reason = $"Payment(refund) failed with PaymentResultMessage: {paymentOrder.PayResults.FirstOrDefault()?.PaymentResultMessage ?? ""}, PaymentResultCode: {paymentOrder.PayResults.FirstOrDefault()?.PaymentResultCode ?? ""}, PaymentResultErrorDetail: {paymentOrder.PayResults.FirstOrDefault()?.PaymentResultErrorDetail ?? ""}, PaymentResultRawResult: {paymentOrder.PayResults.FirstOrDefault()?.PaymentResultRawResult ?? ""}"
  498. };
  499. return output;
  500. }
  501. }
  502. dbContext.Transactions.Add(trx);
  503. dbContext.Entry(trx.Operator).State = EntityState.Unchanged;
  504. await dbContext.SaveChangesAsync();
  505. var finalOutput = mapper.Map<TransactionOutputDto>(trx);
  506. finalOutput.CommitState = new TransactionCommitState()
  507. {
  508. StateCode = 200,
  509. Reason = "",
  510. };
  511. return finalOutput;
  512. }
  513. catch (Exception eeee)
  514. {
  515. this.logger.LogInformation($"Commit(trx) exceptioned: {eeee}");
  516. var output = mapper.Map<TransactionOutputDto>(trx);
  517. output.CommitState = new TransactionCommitState()
  518. {
  519. StateCode = 500,
  520. Reason = $"Generic exception in Commit(trx), detail: {eeee}",
  521. };
  522. return output;
  523. }
  524. }
  525. [UniversalApi]
  526. public async Task<List<TransactionOutputDto>> GetTransactionsByTimeRange(DateTime start, DateTime end, int pageRowCount, int pageIndex)
  527. {
  528. using (var db = new PosAppDbContext())
  529. {
  530. var results = await db.Transactions.Include(t => t.Payments).Include(t => t.Operator).Include(t => t.FuelItems).Include(t => t.AppliedDiscounts)
  531. .Include(t => t.MembershipProfitAccountRecharge)
  532. .Include(t => t.MembershipProfitAccountRedeem)
  533. .Where(t => t.ServerSideTimestamp >= start && t.ServerSideTimestamp <= end)
  534. .OrderByDescending(t => t.ServerSideTimestamp)
  535. .Skip(pageRowCount * pageIndex).Take(pageRowCount).ToListAsync();
  536. var mapper = this.services.GetRequiredService<IMapper>();
  537. var dtos = mapper.Map<List<TransactionOutputDto>>(results);
  538. return dtos;
  539. }
  540. }
  541. [UniversalApi]
  542. public async Task<OperatorOutputDto> ValidateOperatorLogon(string operatorName, string password)
  543. {
  544. using (var db = new PosAppDbContext())
  545. {
  546. var result = await db.Operators.FirstOrDefaultAsync(o => o.Name == operatorName && o.Password == password);
  547. if (result == null) return null;
  548. var mapper = this.services.GetRequiredService<IMapper>();
  549. var op = mapper.Map<OperatorOutputDto>(result);
  550. return op;
  551. }
  552. }
  553. [UniversalApi]
  554. public async Task<List<OperatorOutputDto>> GetOperators()
  555. {
  556. using (var db = new PosAppDbContext())
  557. {
  558. var results = await db.Operators.ToListAsync();
  559. if (results == null) return null;
  560. var mapper = this.services.GetRequiredService<IMapper>();
  561. var ops = mapper.Map<List<OperatorOutputDto>>(results);
  562. return ops;
  563. }
  564. }
  565. [UniversalApi]
  566. public async Task<OperatorOutputDto> CreateOperatorByAuthorizeCode(string authorizeCode, string newOpName, string newOpPassword, string newOpDescription)
  567. {
  568. if (authorizeCode != "666888")
  569. return null;
  570. if (string.IsNullOrEmpty(newOpName) || string.IsNullOrEmpty(newOpPassword))
  571. return null;
  572. using (var db = new PosAppDbContext())
  573. {
  574. var newOp = new Operator()
  575. {
  576. Name = newOpName,
  577. Password = newOpPassword,
  578. Description = newOpDescription,
  579. CreatedTimestamp = DateTime.Now
  580. };
  581. db.Operators.Add(newOp);
  582. var c = await db.SaveChangesAsync();
  583. if (c != 1)
  584. {
  585. this.logger.LogInformation($"Create operator with name: {newOpName ?? ""}, pwd: {newOpPassword ?? ""} failed due to effected db row count is not 1.");
  586. return null;
  587. }
  588. var mapper = this.services.GetRequiredService<IMapper>();
  589. var op = mapper.Map<OperatorOutputDto>(newOp);
  590. return op;
  591. }
  592. }
  593. [UniversalApi]
  594. public async Task<OperatorOutputDto> ResetOperatorPasswordByAuthorizeCode(string authorizeCode, string opName, string newOpPassword)
  595. {
  596. if (authorizeCode != "666888")
  597. return null;
  598. if (string.IsNullOrEmpty(opName) || string.IsNullOrEmpty(newOpPassword))
  599. return null;
  600. using (var db = new PosAppDbContext())
  601. {
  602. var updatedOp = await db.Operators.FirstOrDefaultAsync(op => op.Name == opName);
  603. if (updatedOp == null) return null;
  604. updatedOp.Password = newOpPassword;
  605. var c = await db.SaveChangesAsync();
  606. if (c != 1)
  607. {
  608. this.logger.LogInformation($"Reset operator pwd with name: {opName ?? ""}, newPwd: {newOpPassword ?? ""} failed due to effected db row count is not 1.");
  609. return null;
  610. }
  611. var mapper = this.services.GetRequiredService<IMapper>();
  612. var op = mapper.Map<OperatorOutputDto>(updatedOp);
  613. return op;
  614. }
  615. }
  616. [UniversalApi(Description = "Update or insert a discount, providing the non-zero Id property for update, otherwise for insert.")]
  617. public async Task<DiscountDto> UpsertDiscount(DiscountDto input)
  618. {
  619. var dbContext = new PosAppDbContext();
  620. var mapper = this.services.GetRequiredService<IMapper>();
  621. var discount = mapper.Map<Discount>(input);
  622. if (input.Id == null || input.Id == 0)
  623. {
  624. /*create one in db*/
  625. discount.CreatedTime = DateTime.Now;
  626. dbContext.Discounts.Add(discount);
  627. await dbContext.SaveChangesAsync();
  628. }
  629. else
  630. {
  631. /*udpate one in db*/
  632. discount.ModifiedTime = DateTime.Now;
  633. dbContext.Entry(discount).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
  634. dbContext.Entry(discount).Property(c => c.CreatedTime).IsModified = false;
  635. //dbContext.Entry(inputTankOverallConfig).Property(c => c.ModifiedTimeStamp).IsModified = true;
  636. await dbContext.SaveChangesAsync();
  637. }
  638. return mapper.Map<DiscountDto>(discount);
  639. }
  640. [UniversalApi(Description = "Get all discounts")]
  641. public async Task<List<DiscountDto>> GetDiscounts()
  642. {
  643. var dbContext = new PosAppDbContext();
  644. var results = await dbContext.Discounts.ToListAsync();
  645. var mapper = this.services.GetRequiredService<IMapper>();
  646. return mapper.Map<List<DiscountDto>>(results);
  647. }
  648. }
  649. }