OrderService.cs 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947
  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 F2FPayDll.Domain;
  18. using Newtonsoft.Json;
  19. using System;
  20. using System.Collections.Generic;
  21. using System.Collections.Specialized;
  22. using System.Configuration;
  23. using System.IO;
  24. using System.Linq;
  25. using System.Text;
  26. using System.Web;
  27. namespace Central.Control.WebApi.Service
  28. {
  29. /// <summary>
  30. ///
  31. /// </summary>
  32. public class OrderService: IOrderService
  33. {
  34. IAlipayTradeService serviceClient = F2FBiz.CreateClientInstance(AliPayConfig.serverUrl, AliPayConfig.appId, AliPayConfig.merchant_private_key, AliPayConfig.version,
  35. AliPayConfig.sign_type, AliPayConfig.alipay_public_key, AliPayConfig.charset);
  36. /// <summary>
  37. /// 支付宝回写地址
  38. /// </summary>
  39. public readonly static string aliNotifyUrl = ConfigurationManager.AppSettings["aliNotifyUrl"].ToString();
  40. /// <summary>
  41. /// 支付中间页地址
  42. /// </summary>
  43. public readonly static string payRedirectUrl = ConfigurationManager.AppSettings["payredirecturl"].ToString();
  44. private readonly IDbContext _dbContent;
  45. private readonly IUserService _userService;
  46. private readonly ILog4NetHelper _log4NetHelper;
  47. private readonly IYWLogService _ywLogService;
  48. /// <summary>
  49. ///
  50. /// </summary>
  51. /// <param name="dbContent"></param>
  52. /// <param name="userService"></param>
  53. /// <param name="log4NetHelper"></param>
  54. /// <param name="ywLogService"></param>
  55. public OrderService(
  56. IDbContext dbContent,
  57. IUserService userService,
  58. ILog4NetHelper log4NetHelper,
  59. IYWLogService ywLogService)
  60. {
  61. _dbContent = dbContent;
  62. _userService = userService;
  63. _log4NetHelper = log4NetHelper;
  64. _ywLogService = ywLogService;
  65. }
  66. /// <summary>
  67. /// 下单
  68. /// </summary>
  69. /// <param name="req"></param>
  70. /// <returns></returns>
  71. public ApiResult<OrderResponseDto> Order(OrderRequestDto req)
  72. {
  73. #region 1、验证参数
  74. if (req == null
  75. || string.IsNullOrWhiteSpace(req.ProductId)
  76. || req.Count <= 0)
  77. {
  78. return new ApiResult<OrderResponseDto>(ApiStatusCode.InvalidParameter, "请求参数不正确");
  79. }
  80. #endregion
  81. #region 2、验证相应数据和库存
  82. var productInfo = (from p in _dbContent.Set<YW_Product>().Where(p => p.IsDelete == 0 && p.Sale == Enum.SaleEnum.On)
  83. join dp in _dbContent.Set<YW_DeviceProduct>().Where(p => p.IsDelete == 0) on p.Id equals dp.ProductId
  84. where p.Id == req.ProductId
  85. select new {
  86. Product = p,
  87. DeviceProduct = dp
  88. }).FirstOrDefault();
  89. if (productInfo == null)
  90. {
  91. return new ApiResult<OrderResponseDto>(ApiStatusCode.RecordNotFound, "未找到下单的商品");
  92. }
  93. #endregion
  94. #region 3、包装盒库存验证
  95. var session = _userService.GetLoginSession();
  96. // 修改
  97. var devicePacking = _dbContent.Set<YW_DevicePacking>().FirstOrDefault(p => p.IsDelete == 0 && p.DeviceId == session.UserId);
  98. if (devicePacking == null)
  99. {
  100. return new ApiResult<OrderResponseDto>(ApiStatusCode.Forbidden, "当前机器未放置或者未设置打包盒包,请联系工作人员添加包装盒");
  101. }
  102. // int orderCount = req.OrderInfo.Sum(p => p.Count);
  103. int packingCount = devicePacking.Stock * devicePacking.Capacity;
  104. if (packingCount < req.Count)
  105. {
  106. return new ApiResult<OrderResponseDto>(ApiStatusCode.Forbidden, "包装盒库存不足,请联系工作人员添加包装盒");
  107. }
  108. // 计算使用的包装盒数量(减包装盒库存)
  109. var exceptStock = req.Count / devicePacking.Capacity;
  110. var yushu = req.Count % devicePacking.Capacity;
  111. if (yushu > 0)
  112. {
  113. exceptStock++;
  114. }
  115. // 扣库存
  116. devicePacking.Stock -= exceptStock;
  117. #endregion
  118. #region 4、商品库存验证
  119. var orderId = IdGenerator.NewId();
  120. if (productInfo.DeviceProduct.Stock < req.Count)
  121. {
  122. return new ApiResult<OrderResponseDto>(ApiStatusCode.Forbidden, $"{productInfo.Product.Name}库存不足,请修改后重新下单");
  123. }
  124. // 扣库存
  125. var deviceProduct = productInfo.DeviceProduct;
  126. deviceProduct.Stock -= req.Count;
  127. #endregion
  128. #region 5、下单
  129. YW_Order order = new YW_Order()
  130. {
  131. Id = orderId,
  132. Name = productInfo.Product.Name,
  133. DeviceId = session.UserId,
  134. Price = deviceProduct.Price * req.Count,
  135. PayStatus = PayStatusEnum.UnPay,
  136. OrderStatus = OrderStatusEnum.UnPay,
  137. // PaySerialId = Snowflake.Instance().GetId().ToString(), // 支付流水号,支付完成会回写
  138. CreateBY = session.UserId
  139. };
  140. // orderDetails
  141. var orderDetails = new YW_OrderDetails()
  142. {
  143. Id = IdGenerator.NewId(),
  144. OrderId = orderId,
  145. ProductId = productInfo.Product.Id,
  146. Name = productInfo.Product.Name,
  147. Code = productInfo.Product.Code,
  148. Img = productInfo.Product.Img,
  149. Info = productInfo.Product.Info,
  150. Price = productInfo.DeviceProduct.Price,
  151. BuyingPrice = productInfo.Product.BuyingPrice,
  152. Count = req.Count,
  153. CreateBY = session.UserId
  154. };
  155. YW_OrderProcess orderProcess = new YW_OrderProcess()
  156. {
  157. Id = IdGenerator.NewId(),
  158. OrderId = order.Id,
  159. DeviceId = session.UserId,
  160. CurrentOrderStatus = OrderStatusEnum.UnPay,
  161. Message = "下单成功",
  162. CreateBY = session.UserId
  163. };
  164. #endregion
  165. #region 6、存库
  166. _dbContent.Set<YW_Order>().Add(order);
  167. _dbContent.Set<YW_OrderProcess>().Add(orderProcess);
  168. _dbContent.Set<YW_OrderDetails>().Add(orderDetails);
  169. // 数据库执行 库存已经改过了,会自动保存
  170. _dbContent.SaveChanges();
  171. #endregion
  172. // 写入订单日志
  173. _ywLogService.WriteOrderLog(order.Id, "下单", "下单成功", session.UserName);
  174. return new ApiResult<OrderResponseDto>(new OrderResponseDto()
  175. {
  176. OrderId = order.Id,
  177. Price = order.Price,
  178. QrCode = string.Format(payRedirectUrl, order.Id)
  179. });
  180. }
  181. /// <summary>
  182. /// 根据id获取商品信息
  183. /// </summary>
  184. /// <param name="orderId"></param>
  185. /// <returns></returns>
  186. public ApiResult<OrderInfoResponseDto> GetQueueOrder(string orderId)
  187. {
  188. var session = _userService.GetLoginSession();
  189. var order = _dbContent.Set<YW_Order>().FirstOrDefault(p => p.IsDelete == 0 && p.Id == orderId && p.DeviceId == session.UserId);
  190. if (order == null)
  191. {
  192. return new ApiResult<OrderInfoResponseDto>(ApiStatusCode.InvalidParameter, "订单不存在");
  193. }
  194. OrderInfoResponseDto result = new OrderInfoResponseDto() {
  195. Id = order.Id,
  196. Name = order.Name,
  197. Price = order.Price,
  198. PayStatus = order.PayStatus,
  199. PaySerialId = order.PaySerialId,
  200. PayWay = order.PayWay
  201. };
  202. return new ApiResult<OrderInfoResponseDto>(result);
  203. }
  204. /// <summary>
  205. /// 获取待烧烤的商品列表
  206. /// <summary>
  207. /// <returns></returns>
  208. public ApiResult<List<QueueOrderResponseDto>> GetQueueOrders()
  209. {
  210. var session = _userService.GetLoginSession();
  211. var orders = _dbContent.Set<YW_Order>()
  212. .Where(p => p.IsDelete == 0
  213. && p.PayStatus == PayStatusEnum.Paid
  214. && p.OrderStatus == OrderStatusEnum.Paid
  215. && p.DeviceId == session.UserId)
  216. .OrderBy(p => p.CreateDT).ToList();
  217. var orderIds = orders.Select(p => p.Id).ToList();
  218. var orderDetails = _dbContent.Set<YW_OrderDetails>().Where(p => p.IsDelete == 0 && orderIds.Contains(p.OrderId)).ToList();
  219. List<QueueOrderResponseDto> result = new List<QueueOrderResponseDto>();
  220. orders.ForEach(item =>
  221. {
  222. var currentOrderDetails = orderDetails.FirstOrDefault(p => p.ProductId == item.Id);
  223. var img = string.Empty;
  224. if (!string.IsNullOrWhiteSpace(currentOrderDetails?.Img))
  225. {
  226. img = $"{ProductService.ImagePrefix}{currentOrderDetails.Img}";
  227. }
  228. QueueOrderResponseDto resultItem = new QueueOrderResponseDto()
  229. {
  230. OrderId = item.Id,
  231. TotalPrice = item.Price,
  232. Name = item.Name,
  233. Code = currentOrderDetails?.Code,
  234. Img = img,
  235. Price = currentOrderDetails?.Price ?? 0,
  236. Count = currentOrderDetails?.Count ?? 0
  237. };
  238. result.Add(resultItem);
  239. });
  240. return new ApiResult<List<QueueOrderResponseDto>>(result);
  241. }
  242. /// <summary>
  243. /// 取消未支付订单
  244. /// </summary>
  245. /// <returns></returns>
  246. public ApiResult CancelUnPayOrder(string orderId)
  247. {
  248. var session = _userService.GetLoginSession();
  249. var order = _dbContent.Set<YW_Order>().FirstOrDefault(p => p.IsDelete == 0 && p.Id == orderId && p.DeviceId == session.UserId);
  250. if (order == null)
  251. {
  252. return new ApiResult(ApiStatusCode.InvalidParameter, "取消的订单不存在");
  253. }
  254. if (order.PayStatus != PayStatusEnum.UnPay && order.OrderStatus != OrderStatusEnum.UnPay)
  255. {
  256. return new ApiResult(ApiStatusCode.InvalidParameter, "当前订单不是未支付状态,不能取消");
  257. }
  258. // 需要试试查询支付宝或者
  259. if (order.PayWay == PayWayEnum.AliPay)
  260. {
  261. var tradeQueryResult = serviceClient.tradeQuery(order.Id);
  262. if (tradeQueryResult.Status == ResultEnum.SUCCESS && tradeQueryResult.response.TradeStatus == "TRADE_SUCCESS")
  263. {
  264. // 已经支付成功
  265. return new ApiResult(ApiStatusCode.InvalidParameter, "当前订单已经支付成功,不能取消");
  266. }
  267. }
  268. order.PayStatus = PayStatusEnum.Cancel;
  269. order.OrderStatus = OrderStatusEnum.Cancel;
  270. _dbContent.SaveChanges();
  271. return new ApiResult();
  272. }
  273. /// <summary>
  274. /// 回写流程状态
  275. /// </summary>
  276. /// <param name="req"></param>
  277. /// <returns></returns>
  278. public ApiResult OrderProcess(OrderProcessRequestDto req)
  279. {
  280. if (req == null)
  281. {
  282. return new ApiResult(ApiStatusCode.InvalidParameter, "请求参数不正确");
  283. }
  284. var order = _dbContent.Set<YW_Order>().FirstOrDefault(p => p.Id == req.OrderId);
  285. if (order == null)
  286. {
  287. return new ApiResult(ApiStatusCode.InvalidParameter, "未找到相应订单");
  288. }
  289. if (order.PayStatus != PayStatusEnum.Paid)
  290. {
  291. return new ApiResult(ApiStatusCode.InvalidParameter, "未支付订单不能回写流程");
  292. }
  293. if (order.OrderStatus == OrderStatusEnum.UnPay
  294. || order.OrderStatus == OrderStatusEnum.Cancel)
  295. {
  296. return new ApiResult(ApiStatusCode.InvalidParameter, "未支付或已取消订单不能回写流程");
  297. }
  298. var session = _userService.GetLoginSession();
  299. // 修改订单状态
  300. order.OrderStatus = req.OrderStatus;
  301. // 记录订单流程
  302. YW_OrderProcess orderProcess = new YW_OrderProcess()
  303. {
  304. Id = IdGenerator.NewId(),
  305. OrderId = req.OrderId,
  306. DeviceId = session.UserId,
  307. CurrentOrderStatus = req.OrderStatus,
  308. Message = req.Message,
  309. CreateBY = session.UserId
  310. };
  311. _dbContent.Set<YW_OrderProcess>().Add(orderProcess);
  312. _dbContent.SaveChanges();
  313. // 写入订单日志
  314. _ywLogService.WriteOrderLog(order.Id, "流程回写", $"回写流程状态{req.OrderStatus.ToString()}", session.UserId);
  315. return new ApiResult();
  316. }
  317. /// <summary>
  318. /// 获取支付宝支付链接
  319. /// </summary>
  320. /// <param name="id"></param>
  321. /// <returns></returns>
  322. public string AliPayProcess(string id)
  323. {
  324. // 1、先查询订单是否存在
  325. var order = _dbContent.Set<YW_Order>().FirstOrDefault(p => p.IsDelete == 0 && p.Id == id);
  326. if (order == null)
  327. {
  328. return "http://api.rgtech.ltd/html/ordernotexist.html";
  329. }
  330. if (order.PayStatus == PayStatusEnum.Cancel)
  331. {
  332. // 订单已取消,不能支付再次支付
  333. return "http://api.rgtech.ltd/html/ordercancel.html";
  334. }
  335. if (order.PayWay != PayWayEnum.UnKnow && order.PayWay != PayWayEnum.AliPay)
  336. {
  337. // 上次请求非支付宝支付、返回失败
  338. return "http://api.rgtech.ltd/html/errorpayway.html";
  339. }
  340. string result = "http://api.rgtech.ltd/html/error.html";
  341. // 查询订单详情
  342. var orderDetails = _dbContent.Set<YW_OrderDetails>().Where(p => p.IsDelete == 0 && p.OrderId == order.Id).ToList();
  343. #region 拼写支付宝支付参数
  344. AlipayTradePrecreateContentBuilder builder = new AlipayTradePrecreateContentBuilder();
  345. //收款账号
  346. builder.seller_id = AliPayConfig.pid;
  347. //订单编号
  348. builder.out_trade_no = order.Id;
  349. //订单总金额
  350. builder.total_amount = order.Price.ToString();
  351. //参与优惠计算的金额
  352. //builder.discountable_amount = "";
  353. //不参与优惠计算的金额
  354. //builder.undiscountable_amount = "";
  355. //订单名称
  356. builder.subject = order.Name;
  357. //自定义超时时间
  358. builder.timeout_express = "2m";
  359. //订单描述
  360. builder.body = "";
  361. //门店编号,很重要的参数,可以用作之后的营销
  362. builder.store_id = order.DeviceId;
  363. //操作员编号,很重要的参数,可以用作之后的营销
  364. //builder.operator_id = "test";
  365. //传入商品信息详情
  366. List<GoodsInfo> gList = orderDetails?.Select(od => new GoodsInfo()
  367. {
  368. goods_id = od.Id,
  369. goods_name = od.Name,
  370. price = od.Price.ToString(),
  371. quantity = od.Count.ToString()
  372. }).ToList() ?? new List<GoodsInfo>();
  373. builder.goods_detail = gList;
  374. //系统商接入可以填此参数用作返佣
  375. //ExtendParams exParam = new ExtendParams();
  376. //exParam.sysServiceProviderId = "20880000000000";
  377. //builder.extendParams = exParam;
  378. #endregion
  379. //如果需要接收扫码支付异步通知,那么请把下面两行注释代替本行。
  380. //推荐使用轮询撤销机制,不推荐使用异步通知,避免单边账问题发生。
  381. //AlipayF2FPrecreateResult precreateResult = new AlipayF2FPrecreateResult();
  382. AlipayF2FPrecreateResult precreateResult = serviceClient.tradePrecreate(builder, aliNotifyUrl);//商户接收异步通知的地址,采用异步回写通知
  383. switch (precreateResult.Status)
  384. {
  385. case ResultEnum.SUCCESS:// 申请支付成功,写入订单支付方式(以便于其他人再次扫码使用不同支付方式)
  386. order.PayWay = PayWayEnum.AliPay;
  387. order.ModifyDT = DateTime.Now;
  388. _dbContent.SaveChanges();
  389. result = precreateResult.response.QrCode;
  390. break;
  391. case ResultEnum.FAILED:
  392. case ResultEnum.UNKNOWN:
  393. default:
  394. // 请求失败,记录日志
  395. _log4NetHelper.Error($"[AliPayProcess]-tradePrecreate失败:{JsonConvert.SerializeObject(precreateResult)}");
  396. break;
  397. }
  398. return result;
  399. }
  400. /// <summary>
  401. /// 支付宝支付回写(已测试好,勿动)
  402. /// </summary>
  403. /// <returns></returns>
  404. public string AliPayNotify()
  405. {
  406. _log4NetHelper.Info("[AliPayNotify]-start");
  407. SortedDictionary<string, string> sPara = GetRequestPost();
  408. _log4NetHelper.Info("[AliPayNotify]-[原始请求参数]-" + JsonConvert.SerializeObject(sPara));
  409. Notify aliNotify = new Notify(AliPayConfig.charset, AliPayConfig.sign_type, AliPayConfig.pid, AliPayConfig.mapiUrl, AliPayConfig.alipay_public_key);
  410. string notifyId = HttpContext.Current.Request.Form["notify_id"];
  411. string sign = HttpContext.Current.Request.Form["sign"];
  412. _log4NetHelper.Info($"[AliPayNotify]-[基础参数]-notify_id:{notifyId};sign{sign}");
  413. //对异步通知进行验签
  414. bool verifyResult = aliNotify.Verify(sPara, notifyId, sign);
  415. if (verifyResult && CheckParams()) //验签成功 && 关键业务参数校验成功
  416. {
  417. _log4NetHelper.Info("[AliPayNotify]-验签成功");
  418. string paySerialId = HttpContext.Current.Request.Form["trade_no"] ?? string.Empty;
  419. string outTradeNo = HttpContext.Current.Request.Form["out_trade_no"] ?? string.Empty;
  420. string payResponse = JsonConvert.SerializeObject(sPara);
  421. // 写入支付日志-里面有trycatch
  422. _ywLogService.WritePayCallLog(paySerialId, outTradeNo, PayWayEnum.AliPay, payResponse);
  423. PayStatusEnum payStatus = PayStatusEnum.Fail;
  424. if (HttpContext.Current.Request.Form["trade_status"] == "TRADE_SUCCESS")
  425. {
  426. payStatus = PayStatusEnum.Paid;
  427. }
  428. PayWriteBackRequestDto payWriteBack = new PayWriteBackRequestDto()
  429. {
  430. PaySerialId = paySerialId,
  431. OrderId = outTradeNo,
  432. PayStatus = payStatus,
  433. PayWay = PayWayEnum.AliPay,
  434. BuyerId = HttpContext.Current.Request.Form["buyer_id"] ?? string.Empty,
  435. BuyAccount = HttpContext.Current.Request.Form["buyer_logon_id"] ?? string.Empty,
  436. SellerId = HttpContext.Current.Request.Form["seller_id"] ?? string.Empty,
  437. SellAccount = HttpContext.Current.Request.Form["seller_email"] ?? string.Empty,
  438. TotalAmount = ConvertHelper.ToDecimal(HttpContext.Current.Request.Form["total_amount"]),
  439. PayAmount = ConvertHelper.ToDecimal(HttpContext.Current.Request.Form["buyer_pay_amount"]),
  440. PayTime = ConvertHelper.ToDateTime(HttpContext.Current.Request.Form["gmt_payment"]),
  441. PayResponse = payResponse
  442. };
  443. // 回写到实际表中
  444. var result = PayWriteBack(payWriteBack);
  445. if (!result.IsSuccess)
  446. {
  447. // 失败了
  448. _log4NetHelper.Info($"[AliPayNotify]-[支付回写验证失败]-{result.Message}");
  449. return "false";
  450. }
  451. return "sucess";
  452. }
  453. else
  454. {
  455. _log4NetHelper.Info("[AliPayNotify]-验签失败!!!");
  456. return "false";
  457. }
  458. }
  459. /// <summary>
  460. /// 申请退款
  461. /// </summary>
  462. /// <returns></returns>
  463. public ApiResult OrderRefund(string orderId)
  464. {
  465. var order = _dbContent.Set<YW_Order>().FirstOrDefault(p => p.IsDelete == 0 && p.Id == orderId);
  466. if (order.PayStatus != PayStatusEnum.Paid)
  467. {
  468. // 订单状态不可退款
  469. return new ApiResult(ApiStatusCode.InvalidParameter, "当前订单状态不可退款");
  470. }
  471. ApiResult result = null;
  472. switch (order.PayWay)
  473. {
  474. case PayWayEnum.WeChatPay:
  475. result = new ApiResult(ApiStatusCode.Forbidden, "微信支付暂未接入,无法发起退款");
  476. break;
  477. case PayWayEnum.AliPay:
  478. // 支付宝退款
  479. result = AliPayRefund(order.Id, order.PaySerialId, order.Price, order.Id, "客户发起退款");
  480. break;
  481. default:
  482. result = new ApiResult(ApiStatusCode.Forbidden, "未知支付方式,无法在线自动退款");
  483. break;
  484. }
  485. if (result.IsSuccess)
  486. {
  487. // 修改订单状态
  488. order.PayStatus = PayStatusEnum.Refunding;
  489. order.OrderStatus = OrderStatusEnum.Cancel;
  490. order.ModifyDT = DateTime.Now;
  491. order.ModifyBY = "用户申请";
  492. _dbContent.SaveChanges();
  493. // 写入订单流程记录
  494. _ywLogService.WriteOrderProcess(order.Id, order.DeviceId, OrderStatusEnum.Cancel, "申请退款", "用户申请");
  495. }
  496. return result;
  497. }
  498. /// <summary>
  499. /// 轮询查询退款信息,并更新订单的退款记录
  500. /// </summary>
  501. /// <returns></returns>
  502. public ApiResult OrderRefundLoop()
  503. {
  504. // 1、查询出所有退款中的订单
  505. // 2、请求支付宝退款接口查询是否已退款成功
  506. // 3、退款成功或失败时,回写到订单中
  507. var modifyDt = DateTime.Now.AddMinutes(-2);// 查询2分钟以前的订单,因为退款需要一定时间,马上查询会返回失败
  508. var order = _dbContent.Set<YW_Order>().Where(p => p.IsDelete == 0 && p.PayStatus == PayStatusEnum.Refunding && p.ModifyDT < modifyDt).OrderBy(p => p.ModifyDT).FirstOrDefault();
  509. if (order == null)
  510. {
  511. return new ApiResult(ApiStatusCode.InvalidParameter, "无退款中订单");
  512. }
  513. // 更新modifydt以便下次捞取别的订单
  514. order.ModifyDT = DateTime.Now;
  515. // 测试
  516. //var order = new YW_Order()
  517. //{
  518. // Id = "GBJUJCBWD3E26SIV",
  519. // PaySerialId = "2022040322001434260501434677",
  520. // DeviceId = string.Empty,// "cb8b354c-b713-4dd7-9752-a6f3217004a0",
  521. // PayWay = PayWayEnum.AliPay
  522. //};
  523. ApiResult result = null;
  524. switch (order.PayWay)
  525. {
  526. case PayWayEnum.WeChatPay:
  527. result = new ApiResult(ApiStatusCode.Forbidden, "微信支付暂未接入,无法进行查询");
  528. break;
  529. case PayWayEnum.AliPay:
  530. // 查询支付宝退款
  531. result = AliPayRefundQuery(order.Id, order.PaySerialId, order.Price);
  532. break;
  533. default:
  534. result = new ApiResult(ApiStatusCode.Forbidden, "未知支付方式,无法进行查询");
  535. break;
  536. }
  537. if (result.IsSuccess)
  538. {
  539. // 修改订单状态
  540. order.PayStatus = PayStatusEnum.Refunded;
  541. order.OrderStatus = OrderStatusEnum.Cancel;
  542. order.ModifyDT = DateTime.Now;
  543. order.ModifyBY = "用户申请";
  544. // 写入订单流程记录
  545. _ywLogService.WriteOrderProcess(order.Id, order.DeviceId, OrderStatusEnum.Cancel, "退款成功", "用户申请");
  546. }
  547. else
  548. {
  549. // 退款失败,将订单更新成错误,并记录订单日志
  550. // order.PayStatus = PayStatusEnum.Fail;
  551. order.OrderStatus = OrderStatusEnum.Cancel;
  552. order.ModifyDT = DateTime.Now;
  553. order.ModifyBY = "用户申请";
  554. // 写入订单日志
  555. _ywLogService.WriteOrderLog(order.Id, "订单退款失败", result.Message, "用户申请");
  556. }
  557. _dbContent.SaveChanges();
  558. return result;
  559. }
  560. #region privatemethods
  561. /// <summary>
  562. /// 支付回写(回写到实际表中)
  563. /// </summary>
  564. /// <param name="req"></param>
  565. /// <returns></returns>
  566. private ApiResult PayWriteBack(PayWriteBackRequestDto req)
  567. {
  568. if (req == null
  569. || string.IsNullOrWhiteSpace(req.PaySerialId))
  570. {
  571. return new ApiResult(ApiStatusCode.InvalidParameter, "请求参数不正确");
  572. }
  573. var order = _dbContent.Set<YW_Order>().FirstOrDefault(p => p.Id == req.OrderId);
  574. if (order == null)
  575. {
  576. return new ApiResult(ApiStatusCode.InvalidParameter, "未找到相应订单");
  577. }
  578. if (order.Price != req.TotalAmount)
  579. {
  580. // 写入订单日志
  581. string msg = $"回写总金额与订单金额不匹配,订单号{order.Id};订单价格{order.Price};支付流水号{req.PaySerialId};支付订单总金额{req.TotalAmount}";
  582. _ywLogService.WriteOrderLog(order.Id, "支付回写", msg, req.PayWay.ToString());
  583. return new ApiResult(ApiStatusCode.InvalidParameter, msg);
  584. }
  585. if (order.Price != req.PayAmount)
  586. {
  587. // 写入订单日志
  588. string msg = $"回写支付金额与订单金额不匹配,订单号{order.Id};订单价格{order.Price};支付流水号{req.PaySerialId};支付订单已付金额{req.PayAmount}";
  589. _ywLogService.WriteOrderLog(order.Id, "支付回写", msg, req.PayWay.ToString());
  590. return new ApiResult(ApiStatusCode.InvalidParameter, msg);
  591. }
  592. var status = GetStatusByPayWriteBack(req.PayStatus);
  593. // 修改订单状态
  594. order.PayStatus = status.Item1;
  595. order.OrderStatus = status.Item2;
  596. order.PaySerialId = req.PaySerialId;
  597. // 记录订单支付流程
  598. YW_PayCall payCall = new YW_PayCall()
  599. {
  600. Id = IdGenerator.NewId(),
  601. OrderId = req.OrderId,
  602. PayStatus = status.Item1,
  603. PaySerialId = req.PaySerialId,
  604. PayWay = req.PayWay,
  605. BuyerId = req.BuyerId,
  606. BuyAccount = req.BuyAccount,
  607. SellerId = req.SellerId,
  608. SellAccount = req.SellAccount,
  609. PayAmount = req.PayAmount,
  610. PayTime = req.PayTime,
  611. PayResponse = req.PayResponse,
  612. CreateBY = req.BuyAccount
  613. };
  614. _dbContent.Set<YW_PayCall>().Add(payCall);
  615. // 写入订单流程信息
  616. _ywLogService.WriteOrderProcess(req.OrderId, order.DeviceId, order.OrderStatus, "订单回写成功", "支付回写");
  617. _dbContent.SaveChanges();
  618. // 记录订单日志
  619. _ywLogService.WriteOrderLog(order.Id, "支付回写", "回写成功", req.PayWay.ToString());
  620. return new ApiResult();
  621. }
  622. /// <summary>
  623. /// 支付宝退款
  624. /// </summary>
  625. /// <param name="orderId"></param>
  626. /// <param name="paySerialId"></param>
  627. /// <param name="refundAmount"></param>
  628. /// <param name="refundReason"></param>
  629. /// <returns></returns>
  630. private ApiResult AliPayRefund(string orderId,string paySerialId,decimal refundAmount, string out_request_no, string refundReason)
  631. {
  632. AlipayTradeRefundContentBuilder builder = new AlipayTradeRefundContentBuilder()
  633. {
  634. trade_no = paySerialId,
  635. out_trade_no = orderId,
  636. refund_amount = refundAmount.ToString(),
  637. out_request_no = out_request_no,
  638. refund_reason = refundReason,
  639. };
  640. ApiResult result = null;
  641. // 支付宝退款
  642. _log4NetHelper.Error($"[AliPayRefund]-tradeRefund-start:request:{JsonConvert.SerializeObject(builder)}");
  643. AlipayF2FRefundResult refundResult = serviceClient.tradeRefund(builder);
  644. _log4NetHelper.Error($"[AliPayRefund]-tradeRefund-end:request:{JsonConvert.SerializeObject(refundResult)}");
  645. switch (refundResult.Status)
  646. {
  647. case ResultEnum.SUCCESS:
  648. // 退款成功-记录日志
  649. _ywLogService.WriteOrderLog(orderId, "申请退款", $"申请退款成功:退款金额{refundResult.response?.SendBackFee}", "用户申请");
  650. result = new ApiResult();
  651. break;
  652. default:
  653. result = new ApiResult(ApiStatusCode.InternalServerError, "请求支付宝退款失败" + refundResult.response?.SubMsg);
  654. // 退款失败-记录订单日志
  655. _ywLogService.WriteOrderLog(orderId, "申请退款", $"申请退款失败:{refundResult.response?.SubMsg}", "用户申请");
  656. // 请求失败,记录log4net日志
  657. _log4NetHelper.Error($"[AliPayRefund]-tradeRefund失败:request:{JsonConvert.SerializeObject(builder)};response:{JsonConvert.SerializeObject(refundResult)}");
  658. break;
  659. }
  660. return result;
  661. }
  662. /// <summary>
  663. /// 支付宝退款查询
  664. /// </summary>
  665. /// <param name="orderId"></param>
  666. /// <param name="paySerialId"></param>
  667. /// <param name="orderPrice"></param>
  668. /// <returns></returns>
  669. private ApiResult AliPayRefundQuery(string orderId, string paySerialId, decimal orderPrice)
  670. {
  671. var builder = new AlipayTradeFastpayRefundQueryContentBuilder()
  672. {
  673. trade_no = paySerialId,
  674. out_trade_no = orderId,
  675. out_request_no = orderId
  676. };
  677. ApiResult result = null;
  678. // 支付宝退款
  679. _log4NetHelper.Error($"[AliPayRefund]-AliPayRefundQuery-start:request:{JsonConvert.SerializeObject(builder)}");
  680. var refundQueryResult = serviceClient.tradeFastpayRefundQuery(builder);
  681. _log4NetHelper.Error($"[AliPayRefund]-AliPayRefundQuery-end:response:{JsonConvert.SerializeObject(refundQueryResult)}");
  682. switch (refundQueryResult.Status)
  683. {
  684. case ResultEnum.SUCCESS:
  685. string content = "成功";
  686. if (ConvertHelper.ToDecimal(refundQueryResult.response.RefundAmount) == orderPrice)
  687. {
  688. // 退款成功
  689. result = new ApiResult();
  690. content = $"退款成功:退款金额{refundQueryResult.response?.RefundAmount}";
  691. }
  692. else
  693. {
  694. // 退款失败
  695. result = new ApiResult(ApiStatusCode.InternalServerError, "请求支付宝退款失败" + refundQueryResult.response?.SubMsg);
  696. content = $"退款失败:{refundQueryResult.response?.SubMsg}";
  697. }
  698. // 记录订单日志
  699. _ywLogService.WriteOrderLog(orderId, "退款查询", content, "用户申请");
  700. break;
  701. default:
  702. result = new ApiResult(ApiStatusCode.InternalServerError, "请求支付宝退款失败" + refundQueryResult.response?.SubMsg);
  703. // 退款失败-记录订单日志
  704. _ywLogService.WriteOrderLog(orderId, "退款查询", $"退款失败:{refundQueryResult.response?.SubMsg}", "用户申请");
  705. // 请求失败,记录log4net日志
  706. _log4NetHelper.Error($"[AliPayRefund]-AliPayRefundQuery失败:request:{JsonConvert.SerializeObject(builder)};response:{JsonConvert.SerializeObject(refundQueryResult)}");
  707. break;
  708. }
  709. return result;
  710. }
  711. /// <summary>
  712. /// 支付测试
  713. /// </summary>
  714. /// <returns></returns>
  715. public ApiResult<AlipayF2FPrecreateResult> PayTest()
  716. {
  717. AlipayTradePrecreateContentBuilder builder = BuildPrecreateContent();
  718. string out_trade_no = builder.out_trade_no;
  719. //如果需要接收扫码支付异步通知,那么请把下面两行注释代替本行。
  720. //推荐使用轮询撤销机制,不推荐使用异步通知,避免单边账问题发生。
  721. // AlipayF2FPrecreateResult precreateResult = serviceClient.tradePrecreate(builder);
  722. string notify_url = aliNotifyUrl; //商户接收异步通知的地址
  723. AlipayF2FPrecreateResult precreateResult = serviceClient.tradePrecreate(builder, notify_url);
  724. //以下返回结果的处理供参考。
  725. //payResponse.QrCode即二维码对于的链接
  726. //将链接用二维码工具生成二维码打印出来,顾客可以用支付宝钱包扫码支付。
  727. string result = "";
  728. return new ApiResult<AlipayF2FPrecreateResult>(precreateResult);
  729. //switch (precreateResult.Status)
  730. //{
  731. // case ResultEnum.SUCCESS:
  732. // DoWaitProcess(precreateResult);
  733. // txtRCCode.Text = precreateResult.response.QrCode;
  734. // break;
  735. // case ResultEnum.FAILED:
  736. // result = precreateResult.response.Body;
  737. // Response.Redirect("result.aspx?Text=" + result);
  738. // break;
  739. // case ResultEnum.UNKNOWN:
  740. // if (precreateResult.response == null)
  741. // {
  742. // result = "配置或网络异常,请检查后重试";
  743. // }
  744. // else
  745. // {
  746. // result = "系统异常,请更新外部订单后重新发起请求";
  747. // }
  748. // Response.Redirect("result.aspx?Text=" + result);
  749. // break;
  750. //}
  751. }
  752. /// <summary>
  753. /// 构造支付请求数据
  754. /// </summary>
  755. /// <returns>请求数据集</returns>
  756. private AlipayTradePrecreateContentBuilder BuildPrecreateContent()
  757. {
  758. AlipayTradePrecreateContentBuilder builder = new AlipayTradePrecreateContentBuilder();
  759. //收款账号
  760. builder.seller_id = AliPayConfig.pid;
  761. //订单编号-----------------------
  762. builder.out_trade_no = DateTime.Now.ToString("yyyyMMddHHmmss") + "0000" + (new Random()).Next(1, 10000).ToString();
  763. //订单总金额-----------------------------
  764. builder.total_amount = "0.02";
  765. //参与优惠计算的金额
  766. //builder.discountable_amount = "";
  767. //不参与优惠计算的金额
  768. //builder.undiscountable_amount = "";
  769. //订单名称-------------------------------
  770. builder.subject = "smj_webapi测试订单";
  771. //自定义超时时间
  772. builder.timeout_express = "5m";
  773. //订单描述
  774. builder.body = "";
  775. //门店编号,很重要的参数,可以用作之后的营销--------------------
  776. builder.store_id = "test store id";
  777. //操作员编号,很重要的参数,可以用作之后的营销---------------------
  778. builder.operator_id = "test";
  779. //传入商品信息详情
  780. List<GoodsInfo> gList = new List<GoodsInfo>();
  781. GoodsInfo goods = new GoodsInfo();
  782. goods.goods_id = "goods id";
  783. goods.goods_name = "goods name";
  784. goods.price = "0.02";
  785. goods.quantity = "1";
  786. gList.Add(goods);
  787. builder.goods_detail = gList;
  788. //系统商接入可以填此参数用作返佣
  789. //ExtendParams exParam = new ExtendParams();
  790. //exParam.sysServiceProviderId = "20880000000000";
  791. //builder.extendParams = exParam;
  792. return builder;
  793. }
  794. /// <summary>
  795. ///
  796. /// </summary>
  797. /// <param name="payStatus"></param>
  798. /// <returns></returns>
  799. private Tuple<PayStatusEnum, OrderStatusEnum> GetStatusByPayWriteBack(PayStatusEnum payStatus)
  800. {
  801. // 临时:payStatus 0失败,1成功
  802. OrderStatusEnum orderStatus = OrderStatusEnum.UnPay;
  803. switch (payStatus)
  804. {
  805. case PayStatusEnum.Paid:// 成功
  806. orderStatus = OrderStatusEnum.Paid;
  807. break;
  808. default:// 失败
  809. break;
  810. }
  811. return new Tuple<PayStatusEnum, OrderStatusEnum>(payStatus, orderStatus);
  812. }
  813. /// <summary>
  814. /// 获取支付宝POST过来通知消息,并以“参数名=参数值”的形式组成数组
  815. /// </summary>
  816. /// <returns>request回来的信息组成的数组</returns>
  817. private SortedDictionary<string, string> GetRequestPost()
  818. {
  819. int i = 0;
  820. SortedDictionary<string, string> sArray = new SortedDictionary<string, string>();
  821. NameValueCollection coll;
  822. //Load Form variables into NameValueCollection variable.
  823. coll = HttpContext.Current.Request.Form;
  824. // Get names of all forms into a string array.
  825. String[] requestItem = coll.AllKeys;
  826. for (i = 0; i < requestItem.Length; i++)
  827. {
  828. sArray.Add(requestItem[i], HttpContext.Current.Request.Form[requestItem[i]]);
  829. }
  830. return sArray;
  831. }
  832. /// <summary>
  833. /// 对支付宝异步通知的关键参数进行校验
  834. /// </summary>
  835. /// <returns></returns>
  836. private bool CheckParams()
  837. {
  838. bool ret = true;
  839. //获得调用方的appid;
  840. //如果是非授权模式,appid是商户的appid;如果是授权模式(token调用),appid是系统商的appid
  841. string app_id = HttpContext.Current.Request.Form["app_id"];
  842. if (app_id != AliPayConfig.appId)
  843. {
  844. ret = false;
  845. _log4NetHelper.Info($"[支付回写][appid认证失败]:request_appid:{app_id};sys_appid:{AliPayConfig.appId}");
  846. }
  847. return ret;
  848. }
  849. #endregion
  850. public AlipayF2FQueryResult tradeQuery(string orderId)
  851. {
  852. return serviceClient.tradeQuery(orderId);
  853. }
  854. }
  855. }