OrderService.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. using Ant.Core.Utils;
  2. using Ant.Core.WebApi.Enum;
  3. using Ant.Core.WebApi.Model;
  4. using Aop.Api.Util;
  5. using Central.Control.WebApi.Config;
  6. using Central.Control.WebApi.DbEntity;
  7. using Central.Control.WebApi.EFDbContext;
  8. using Central.Control.WebApi.Enum;
  9. using Central.Control.WebApi.Log4net;
  10. using Central.Control.WebApi.Models.Request;
  11. using Central.Control.WebApi.Models.Response;
  12. using Central.Control.WebApi.Service.Interface;
  13. using Com.Alipay;
  14. using Com.Alipay.Business;
  15. using Com.Alipay.Domain;
  16. using Com.Alipay.Model;
  17. using Newtonsoft.Json;
  18. using System;
  19. using System.Collections.Generic;
  20. using System.Collections.Specialized;
  21. using System.Configuration;
  22. using System.IO;
  23. using System.Linq;
  24. using System.Web;
  25. namespace Central.Control.WebApi.Service
  26. {
  27. /// <summary>
  28. ///
  29. /// </summary>
  30. public class OrderService: IOrderService
  31. {
  32. IAlipayTradeService serviceClient = F2FBiz.CreateClientInstance(AliPayConfig.serverUrl, AliPayConfig.appId, AliPayConfig.merchant_private_key, AliPayConfig.version,
  33. AliPayConfig.sign_type, AliPayConfig.alipay_public_key, AliPayConfig.charset);
  34. /// <summary>
  35. /// 支付宝回写地址
  36. /// </summary>
  37. public readonly static string aliNotifyUrl = ConfigurationManager.AppSettings["aliNotifyUrl"].ToString();
  38. private readonly IDbContext _dbContent;
  39. private readonly IUserService _userService;
  40. private readonly ILog4NetHelper _log4NetHelper;
  41. /// <summary>
  42. ///
  43. /// </summary>
  44. /// <param name="dbContent"></param>
  45. /// <param name="userService"></param>
  46. /// <param name="log4NetHelper"></param>
  47. public OrderService(
  48. IDbContext dbContent,
  49. IUserService userService,
  50. ILog4NetHelper log4NetHelper)
  51. {
  52. _dbContent = dbContent;
  53. _userService = userService;
  54. _log4NetHelper = log4NetHelper;
  55. }
  56. /// <summary>
  57. /// 下单
  58. /// </summary>
  59. /// <param name="req"></param>
  60. /// <returns></returns>
  61. public ApiResult<OrderResponseDto> Order(OrderRequestDto req)
  62. {
  63. #region 1、验证参数
  64. if (req == null
  65. || req.OrderInfo == null
  66. || req.OrderInfo.Count() == 0)
  67. {
  68. return new ApiResult<OrderResponseDto>(ApiStatusCode.InvalidParameter, "请求参数不正确");
  69. }
  70. #endregion
  71. #region 2、验证相应数据和库存
  72. var productIds = req.OrderInfo.Select(p => p.ProductId).ToList();
  73. var products = _dbContent.Set<YW_Product>().Where(p => p.IsDelete == 0 && p.Sale == Enum.SaleEnum.On && productIds.Contains(p.Id)).ToList();
  74. if (products.Count() <= 0)
  75. {
  76. return new ApiResult<OrderResponseDto>(ApiStatusCode.RecordNotFound, "未找到下单的商品");
  77. }
  78. #endregion
  79. #region 3、包装盒库存验证
  80. var session = _userService.GetLoginSession();
  81. // 修改
  82. var devicePackings = _dbContent.Set<YW_DevicePacking>().Where(p => p.IsDelete == 0 && p.DeviceId == session.UserId).ToList();
  83. if (devicePackings == null || devicePackings.Count() == 0)
  84. {
  85. return new ApiResult<OrderResponseDto>(ApiStatusCode.Forbidden, "包装盒库存不足,请联系工作人员添加包装盒");
  86. }
  87. int orderCount = req.OrderInfo.Sum(p => p.Count);
  88. int packingCount = devicePackings.Sum(p => p.Stock * p.Capacity);
  89. if (packingCount < orderCount)
  90. {
  91. return new ApiResult<OrderResponseDto>(ApiStatusCode.Forbidden, "包装盒库存不足,请联系工作人员添加包装盒");
  92. }
  93. // 计算使用的包装盒数量(减包装盒库存)
  94. var orderCountIndex = orderCount;
  95. foreach (var devicePacking in devicePackings)
  96. {
  97. if (orderCountIndex <= 0)
  98. {
  99. break;// 数量为0了,则直接跳出循环
  100. }
  101. int currentPackingStock = devicePacking.Stock;
  102. for (int i = 0; i < currentPackingStock; i++)
  103. {
  104. // 循环库存
  105. if (orderCountIndex <= 0)
  106. {
  107. break;// 数量为0了,则直接跳出循环
  108. }
  109. orderCountIndex = orderCountIndex - devicePacking.Capacity;
  110. devicePacking.Stock--;// 减库存
  111. }
  112. }
  113. #endregion
  114. #region 4、商品库存验证
  115. var orderId = IdGenerator.NewId();
  116. List<YW_OrderDetails> orderDetailAdds = new List<YW_OrderDetails>();
  117. List<YW_OrderProcess> orderProcessAdds = new List<YW_OrderProcess>();
  118. foreach (var o in req.OrderInfo)
  119. {
  120. var currentProduct = products.FirstOrDefault(p => p.Id == o.ProductId);
  121. if (currentProduct == null)
  122. {
  123. return new ApiResult<OrderResponseDto>(ApiStatusCode.RecordNotFound, $"{o.ProductId}商品不存在");
  124. }
  125. if (currentProduct.Stock < o.Count)
  126. {
  127. return new ApiResult<OrderResponseDto>(ApiStatusCode.Forbidden, $"{currentProduct.Name}商品库存不足,请修改后重新下单");
  128. }
  129. // 扣库存
  130. currentProduct.Stock = currentProduct.Stock - o.Count;
  131. // orderDetails
  132. orderDetailAdds.Add(new YW_OrderDetails()
  133. {
  134. Id = IdGenerator.NewId(),
  135. OrderId = orderId,
  136. ProductId = currentProduct.Id,
  137. Name = currentProduct.Name,
  138. Code = currentProduct.Code,
  139. Img = currentProduct.Img,
  140. Info = currentProduct.Info,
  141. Price = currentProduct.Price,
  142. BuyingPrice = currentProduct.BuyingPrice,
  143. Count = o.Count,
  144. CreateBY = session.UserId
  145. });
  146. }
  147. #endregion
  148. #region 5、下单
  149. YW_Order order = new YW_Order()
  150. {
  151. Id = orderId,
  152. DeviceId = session.UserId,
  153. Price = orderDetailAdds.Sum(p => p.Price * p.Count),
  154. PayStatus = PayStatusEnum.UnPay,
  155. OrderStatus = OrderStatusEnum.UnPay,
  156. // PaySerialId = Snowflake.Instance().GetId().ToString(), // 支付流水号,支付完成会回写
  157. CreateBY = session.UserId
  158. };
  159. YW_OrderProcess orderProcess = new YW_OrderProcess()
  160. {
  161. Id = IdGenerator.NewId(),
  162. OrderId = order.Id,
  163. DeviceId = session.UserId,
  164. CurrentOrderStatus = OrderStatusEnum.UnPay,
  165. Message = "下单成功",
  166. CreateBY = session.Id
  167. };
  168. #endregion
  169. #region 6、存库
  170. _dbContent.Set<YW_Order>().Add(order);
  171. _dbContent.Set<YW_OrderProcess>().Add(orderProcess);
  172. _dbContent.Set<YW_OrderDetails>().AddRange(orderDetailAdds);
  173. // 数据库执行 库存已经改过了,会自动保存
  174. _dbContent.SaveChanges();
  175. #endregion
  176. #region 5、支付宝支付--目前直接入支付宝支付测试,后期需要替换成中间页面
  177. #endregion
  178. return new ApiResult<OrderResponseDto>(new OrderResponseDto()
  179. {
  180. OrderId = order.Id,
  181. Price = order.Price
  182. });
  183. }
  184. /// <summary>
  185. /// 获取待烧烤的商品列表
  186. /// <summary>
  187. /// <returns></returns>
  188. public ApiResult<List<QueueOrderResponseDto>> GetQueueOrders()
  189. {
  190. var orders = _dbContent.Set<YW_Order>()
  191. .Where(p => p.IsDelete == 0
  192. && p.PayStatus == PayStatusEnum.Paid
  193. && p.OrderStatus == OrderStatusEnum.Paid)
  194. .OrderBy(p => p.CreateDT).ToList();
  195. var orderIds = orders.Select(p => p.Id).ToList();
  196. var orderDetails = _dbContent.Set<YW_OrderDetails>().Where(p => p.IsDelete == 0 && orderIds.Contains(p.OrderId)).ToList();
  197. List<QueueOrderResponseDto> result = new List<QueueOrderResponseDto>();
  198. orders.ForEach(item => {
  199. QueueOrderResponseDto resultItem = new QueueOrderResponseDto()
  200. {
  201. OrderId = item.Id,
  202. Price = item.Price,
  203. OrderDetails = orderDetails
  204. .Where(p => p.OrderId == item.Id)
  205. .Select(p => SafeClone<YW_OrderDetails, QueueOrderDetailsDto>.Trans(p))
  206. .ToList()
  207. };
  208. resultItem.OrderDetails?.ForEach(p => p.Img = $"{ProductService.ImagePrefix}{p.Img}");
  209. result.Add(resultItem);
  210. });
  211. return new ApiResult<List<QueueOrderResponseDto>>(result);
  212. }
  213. /// <summary>
  214. /// 回写流程状态
  215. /// </summary>
  216. /// <param name="req"></param>
  217. /// <returns></returns>
  218. public ApiResult OrderProcess(OrderProcessRequestDto req)
  219. {
  220. if (req == null)
  221. {
  222. return new ApiResult(ApiStatusCode.InvalidParameter, "请求参数不正确");
  223. }
  224. var order = _dbContent.Set<YW_Order>().FirstOrDefault(p => p.Id == req.OrderId);
  225. if (order == null)
  226. {
  227. return new ApiResult(ApiStatusCode.InvalidParameter, "未找到相应订单");
  228. }
  229. if (order.PayStatus != PayStatusEnum.Paid)
  230. {
  231. return new ApiResult(ApiStatusCode.InvalidParameter, "未支付订单不能回写流程");
  232. }
  233. if (order.OrderStatus == OrderStatusEnum.UnPay
  234. || order.OrderStatus == OrderStatusEnum.Cancel)
  235. {
  236. return new ApiResult(ApiStatusCode.InvalidParameter, "未支付或已取消订单不能回写流程");
  237. }
  238. var seesion = _userService.GetLoginSession();
  239. // 修改订单状态
  240. order.OrderStatus = req.OrderStatus;
  241. // 记录订单流程
  242. YW_OrderProcess orderProcess = new YW_OrderProcess()
  243. {
  244. Id = IdGenerator.NewId(),
  245. OrderId = req.OrderId,
  246. DeviceId = seesion.UserId,
  247. CurrentOrderStatus = req.OrderStatus,
  248. Message = req.Message,
  249. CreateBY = seesion.UserId
  250. };
  251. _dbContent.Set<YW_OrderProcess>().Add(orderProcess);
  252. _dbContent.SaveChanges();
  253. return new ApiResult();
  254. }
  255. /// <summary>
  256. /// 支付宝回写
  257. /// </summary>
  258. /// <returns></returns>
  259. public string AliPayNotify()
  260. {
  261. _log4NetHelper.Info("[AliPayNotify]-start");
  262. SortedDictionary<string, string> sPara = GetRequestPost();
  263. _log4NetHelper.Info("[AliPayNotify]-[原始请求参数]-" + JsonConvert.SerializeObject(sPara));
  264. Notify aliNotify = new Notify(AliPayConfig.charset, AliPayConfig.sign_type, AliPayConfig.pid, AliPayConfig.mapiUrl, AliPayConfig.alipay_public_key);
  265. string notifyId = HttpContext.Current.Request.Form["notify_id"];
  266. string sign = HttpContext.Current.Request.Form["sign"];
  267. _log4NetHelper.Info($"[AliPayNotify]-[基础参数]-notify_id:{notifyId};sign{sign}");
  268. //对异步通知进行验签
  269. bool verifyResult = aliNotify.Verify(sPara, notifyId, sign);
  270. if (verifyResult && CheckParams()) //验签成功 && 关键业务参数校验成功
  271. {
  272. _log4NetHelper.Info("[AliPayNotify]-验签成功");
  273. PayStatusEnum payStatus = PayStatusEnum.Fail;
  274. if (HttpContext.Current.Request.Form["trade_status"] == "TRADE_SUCCESS")
  275. {
  276. payStatus = PayStatusEnum.Paid;
  277. }
  278. PayWriteBackRequestDto payWriteBack = new PayWriteBackRequestDto()
  279. {
  280. PaySerialId = HttpContext.Current.Request.Form["trade_no"] ?? string.Empty,
  281. OrderId = HttpContext.Current.Request.Form["out_trade_no"] ?? string.Empty,
  282. PayStatus = payStatus,
  283. PayWay = PayWayEnum.AliPay,
  284. BuyerId = HttpContext.Current.Request.Form["buyer_id"] ?? string.Empty,
  285. BuyAccount = HttpContext.Current.Request.Form["buyer_logon_id"] ?? string.Empty,
  286. SellerId = HttpContext.Current.Request.Form["seller_id"] ?? string.Empty,
  287. SellAccount = HttpContext.Current.Request.Form["seller_email"] ?? string.Empty,
  288. TotalAmount = ConvertHelper.ToDecimal(HttpContext.Current.Request.Form["total_amount"]),
  289. PayAmount = ConvertHelper.ToDecimal(HttpContext.Current.Request.Form["buyer_pay_amount"]),
  290. PayTime = ConvertHelper.ToDateTime(HttpContext.Current.Request.Form["gmt_payment"]),
  291. PayResponse = JsonConvert.SerializeObject(sPara)
  292. };
  293. // 回写到实际表中
  294. var result = PayWriteBack(payWriteBack);
  295. if (!result.IsSuccess)
  296. {
  297. // 失败了
  298. _log4NetHelper.Info($"[AliPayNotify]-[支付回写验证失败]-{result.Message}");
  299. return "false";
  300. }
  301. return "sucess";
  302. }
  303. else
  304. {
  305. _log4NetHelper.Info("[AliPayNotify]-验签失败!!!");
  306. return "false";
  307. }
  308. }
  309. /// <summary>
  310. /// 支付回写(雏形,需要根据实际需要接入支付宝或微信)
  311. /// </summary>
  312. /// <param name="req"></param>
  313. /// <returns></returns>
  314. private ApiResult PayWriteBack(PayWriteBackRequestDto req)
  315. {
  316. if (req == null
  317. || string.IsNullOrWhiteSpace(req.PaySerialId))
  318. {
  319. return new ApiResult(ApiStatusCode.InvalidParameter, "请求参数不正确");
  320. }
  321. var order = _dbContent.Set<YW_Order>().FirstOrDefault(p => p.Id == req.OrderId);
  322. if (order == null)
  323. {
  324. return new ApiResult(ApiStatusCode.InvalidParameter, "未找到相应订单");
  325. }
  326. if (order.Price != req.TotalAmount)
  327. {
  328. // 需要写入订单日志------------------------
  329. return new ApiResult(ApiStatusCode.InvalidParameter, $"回写总金额与订单金额不匹配,订单号{order.Id};订单价格{order.Price};支付流水号{req.PaySerialId};支付订单总金额{req.TotalAmount}");
  330. }
  331. if (order.Price != req.PayAmount)
  332. {
  333. // 需要写入订单日志------------------------
  334. return new ApiResult(ApiStatusCode.InvalidParameter, $"回写支付金额与订单金额不匹配,订单号{order.Id};订单价格{order.Price};支付流水号{req.PaySerialId};支付订单已付金额{req.PayAmount}");
  335. }
  336. var status = GetStatusByPayWriteBack(req.PayStatus);
  337. // 修改订单状态
  338. order.PayStatus = status.Item1;
  339. order.OrderStatus = status.Item2;
  340. order.PaySerialId = req.PaySerialId;
  341. // 记录订单流程
  342. YW_PayCall payCall = new YW_PayCall()
  343. {
  344. Id = IdGenerator.NewId(),
  345. OrderId = req.OrderId,
  346. PayStatus = status.Item1,
  347. PaySerialId = req.PaySerialId,
  348. PayWay = req.PayWay,
  349. BuyerId = req.BuyerId,
  350. BuyAccount = req.BuyAccount,
  351. SellerId = req.SellerId,
  352. SellAccount = req.SellAccount,
  353. PayAmount = req.PayAmount,
  354. PayTime = req.PayTime,
  355. PayResponse = req.PayResponse,
  356. CreateBY = req.BuyAccount
  357. };
  358. _dbContent.Set<YW_PayCall>().Add(payCall);
  359. _dbContent.SaveChanges();
  360. return new ApiResult();
  361. }
  362. /// <summary>
  363. /// 支付测试
  364. /// </summary>
  365. /// <returns></returns>
  366. public ApiResult<AlipayF2FPrecreateResult> PayTest()
  367. {
  368. AlipayTradePrecreateContentBuilder builder = BuildPrecreateContent();
  369. string out_trade_no = builder.out_trade_no;
  370. //如果需要接收扫码支付异步通知,那么请把下面两行注释代替本行。
  371. //推荐使用轮询撤销机制,不推荐使用异步通知,避免单边账问题发生。
  372. // AlipayF2FPrecreateResult precreateResult = serviceClient.tradePrecreate(builder);
  373. string notify_url = aliNotifyUrl; //商户接收异步通知的地址
  374. AlipayF2FPrecreateResult precreateResult = serviceClient.tradePrecreate(builder, notify_url);
  375. //以下返回结果的处理供参考。
  376. //payResponse.QrCode即二维码对于的链接
  377. //将链接用二维码工具生成二维码打印出来,顾客可以用支付宝钱包扫码支付。
  378. string result = "";
  379. return new ApiResult<AlipayF2FPrecreateResult>(precreateResult);
  380. //switch (precreateResult.Status)
  381. //{
  382. // case ResultEnum.SUCCESS:
  383. // DoWaitProcess(precreateResult);
  384. // txtRCCode.Text = precreateResult.response.QrCode;
  385. // break;
  386. // case ResultEnum.FAILED:
  387. // result = precreateResult.response.Body;
  388. // Response.Redirect("result.aspx?Text=" + result);
  389. // break;
  390. // case ResultEnum.UNKNOWN:
  391. // if (precreateResult.response == null)
  392. // {
  393. // result = "配置或网络异常,请检查后重试";
  394. // }
  395. // else
  396. // {
  397. // result = "系统异常,请更新外部订单后重新发起请求";
  398. // }
  399. // Response.Redirect("result.aspx?Text=" + result);
  400. // break;
  401. //}
  402. }
  403. /// <summary>
  404. /// 构造支付请求数据
  405. /// </summary>
  406. /// <returns>请求数据集</returns>
  407. private AlipayTradePrecreateContentBuilder BuildPrecreateContent()
  408. {
  409. AlipayTradePrecreateContentBuilder builder = new AlipayTradePrecreateContentBuilder();
  410. //收款账号
  411. builder.seller_id = AliPayConfig.pid;
  412. //订单编号-----------------------
  413. builder.out_trade_no = DateTime.Now.ToString("yyyyMMddHHmmss") + "0000" + (new Random()).Next(1, 10000).ToString();
  414. //订单总金额-----------------------------
  415. builder.total_amount = "0.02";
  416. //参与优惠计算的金额
  417. //builder.discountable_amount = "";
  418. //不参与优惠计算的金额
  419. //builder.undiscountable_amount = "";
  420. //订单名称-------------------------------
  421. builder.subject = "smj_webapi测试订单";
  422. //自定义超时时间
  423. builder.timeout_express = "5m";
  424. //订单描述
  425. builder.body = "";
  426. //门店编号,很重要的参数,可以用作之后的营销--------------------
  427. builder.store_id = "test store id";
  428. //操作员编号,很重要的参数,可以用作之后的营销---------------------
  429. builder.operator_id = "test";
  430. //传入商品信息详情
  431. List<GoodsInfo> gList = new List<GoodsInfo>();
  432. GoodsInfo goods = new GoodsInfo();
  433. goods.goods_id = "goods id";
  434. goods.goods_name = "goods name";
  435. goods.price = "0.02";
  436. goods.quantity = "1";
  437. gList.Add(goods);
  438. builder.goods_detail = gList;
  439. //系统商接入可以填此参数用作返佣
  440. //ExtendParams exParam = new ExtendParams();
  441. //exParam.sysServiceProviderId = "20880000000000";
  442. //builder.extendParams = exParam;
  443. return builder;
  444. }
  445. /// <summary>
  446. ///
  447. /// </summary>
  448. /// <param name="payStatus"></param>
  449. /// <returns></returns>
  450. private Tuple<PayStatusEnum, OrderStatusEnum> GetStatusByPayWriteBack(PayStatusEnum payStatus)
  451. {
  452. // 临时:payStatus 0失败,1成功
  453. OrderStatusEnum orderStatus = OrderStatusEnum.UnPay;
  454. switch (payStatus)
  455. {
  456. case PayStatusEnum.UnPay:// 成功
  457. orderStatus = OrderStatusEnum.Paid;
  458. break;
  459. default:// 失败
  460. break;
  461. }
  462. return new Tuple<PayStatusEnum, OrderStatusEnum>(payStatus, orderStatus);
  463. }
  464. /// <summary>
  465. /// 获取支付宝POST过来通知消息,并以“参数名=参数值”的形式组成数组
  466. /// </summary>
  467. /// <returns>request回来的信息组成的数组</returns>
  468. private SortedDictionary<string, string> GetRequestPost()
  469. {
  470. int i = 0;
  471. SortedDictionary<string, string> sArray = new SortedDictionary<string, string>();
  472. NameValueCollection coll;
  473. //Load Form variables into NameValueCollection variable.
  474. coll = HttpContext.Current.Request.Form;
  475. // Get names of all forms into a string array.
  476. String[] requestItem = coll.AllKeys;
  477. for (i = 0; i < requestItem.Length; i++)
  478. {
  479. sArray.Add(requestItem[i], HttpContext.Current.Request.Form[requestItem[i]]);
  480. }
  481. return sArray;
  482. }
  483. /// <summary>
  484. /// 对支付宝异步通知的关键参数进行校验
  485. /// </summary>
  486. /// <returns></returns>
  487. private bool CheckParams()
  488. {
  489. bool ret = true;
  490. //获得调用方的appid;
  491. //如果是非授权模式,appid是商户的appid;如果是授权模式(token调用),appid是系统商的appid
  492. string app_id = HttpContext.Current.Request.Form["app_id"];
  493. if (app_id != AliPayConfig.appId)
  494. {
  495. ret = false;
  496. _log4NetHelper.Info($"[支付回写][appid认证失败]:request_appid:{app_id};sys_appid:{AliPayConfig.appId}");
  497. }
  498. return ret;
  499. }
  500. }
  501. }