public void SendEarlyRefundTicket(EarlyRefundTicket ticket, RefundReason reason)
 {
     if (Logger.IsTrace)
     {
         Logger.Trace($"{Session.RemoteNodeId} NDM sending: earlyrefundticket");
     }
     Send(new EarlyRefundTicketMessage(ticket, reason));
 }
Пример #2
0
 public RefundReason SecondRefundReason()
 {
     var secondRefundReason = new RefundReason
     {
         RefundReasonId = 2,
         RefundReasonValue = "Other reasons"
     };
     return secondRefundReason;
 }
Пример #3
0
 public RefundReason FirstRefundReason()
 {
     var firstRefundReason = new RefundReason
     {
         RefundReasonId = 1,
         RefundReasonValue = "Item not as described"
     };
     return firstRefundReason;
 }
Пример #4
0
        public void MapSetsRefundReasonCorrectly()
        {
            const RefundReason expectedRefundReason = RefundReason.Other;

            Refund refund = _refundFactory.Map(new RefundEntity {
                ReasonId = (short)expectedRefundReason
            });

            Assert.AreEqual(expectedRefundReason, refund.RefundReason, "Refund RefundReason mapped incorrectly.");
        }
Пример #5
0
        public async Task set_early_refund_ticket_should_fail_if_deposit_does_not_exits()
        {
            const RefundReason reason = RefundReason.DataDiscontinued;
            var ticket        = new EarlyRefundTicket(TestItem.KeccakA, 0, null);
            var refundService = new RefundService(_ndmBridge, _abiEncoder, _depositRepository, _contractAddress, LimboLogs.Instance);
            await refundService.SetEarlyRefundTicketAsync(ticket, reason);

            await _depositRepository.Received().GetAsync(ticket.DepositId);

            await _depositRepository.DidNotReceiveWithAnyArgs().UpdateAsync(null);
        }
Пример #6
0
        public void MapMapsRefundReasonToEntityRefundReasonId()
        {
            const RefundReason expectedRefundReason = RefundReason.Other;
            Refund             refund = GetValidRefund();

            refund.RefundReason = expectedRefundReason;

            RefundEntity refundEntity = _refundFactory.Map(refund);

            Assert.AreEqual((short)expectedRefundReason, refundEntity.ReasonId,
                            "RefundEntity ReasonID mapped incorrectly.");
        }
Пример #7
0
 /// <summary>
 /// Process a product Return / Refund.
 /// </summary>
 /// <remarks>
 /// <para>This method does not wrap a single, dedicated "ReturnEvent" call in the point-js API.</para>
 /// <para>Instead:</para>
 /// <para>
 /// If no arguments are provided, we simply call <see cref="ShowGUI"/> with the shortcut "refund".
 /// Otherwise, if a Touch Transaction ID and Reason is supplied, we call the GUI-less
 /// <see cref="AddItem"/>
 /// with the shortcut = "refund" and a Dictionary containing the Touch Transaction ID and Reason.
 /// </para>
 /// </remarks>
 /// <param name="transactionId"></param>
 /// <param name="reason"></param>
 public void Refund(string transactionId = "", RefundReason reason = RefundReason.OTHER)
 {
     if (string.IsNullOrEmpty(transactionId))
     {
         ShowGui("refund");
     }
     else
     {
         var data = new Dictionary <string, string>();
         data.Add("transactionId", transactionId);
         data.Add("reason", reason.ToString());
         AddItem("refund", data);
     }
 }
Пример #8
0
        public async Task set_early_refund_ticket_should_succeed_if_deposit_exists()
        {
            const RefundReason reason = RefundReason.DataDiscontinued;
            var deposit        = new Deposit(TestItem.KeccakA, 1, 1, 1);
            var depositDetails = new DepositDetails(deposit, null, null, null, 0, null, 0);
            var ticket         = new EarlyRefundTicket(deposit.Id, 0, null);
            var refundService  = new RefundService(_ndmBridge, _abiEncoder, _depositRepository, _contractAddress, LimboLogs.Instance);

            _depositRepository.GetAsync(ticket.DepositId).Returns(depositDetails);
            await refundService.SetEarlyRefundTicketAsync(ticket, reason);

            depositDetails.EarlyRefundTicket.Should().Be(ticket);
            await _depositRepository.Received().GetAsync(ticket.DepositId);

            await _depositRepository.Received().UpdateAsync(depositDetails);
        }
Пример #9
0
        public async Task SetEarlyRefundTicketAsync(EarlyRefundTicket ticket, RefundReason reason)
        {
            var depositDetails = await _depositRepository.GetAsync(ticket.DepositId);

            if (depositDetails is null)
            {
                return;
            }

            depositDetails.SetEarlyRefundTicket(ticket);
            await _depositRepository.UpdateAsync(depositDetails);

            if (_logger.IsInfo)
            {
                _logger.Info($"Early refund claim for deposit: '{ticket.DepositId}', reason: '{reason}'.");
            }
        }
Пример #10
0
 public Task SetEarlyRefundTicketAsync(EarlyRefundTicket ticket, RefundReason reason)
 => _refundService.SetEarlyRefundTicketAsync(ticket, reason);
Пример #11
0
        public async Task <IActionResult> ReturnOrder(OrderViewModel viewModel, RefundStatus refundStatus, RefundReason refundReason)
        {
            return(await ModifyOrderView("ReturnDone", viewModel, async order =>
            {
                #region Fraud Protection Service
                var refund = new Refund
                {
                    RefundId = Guid.NewGuid().ToString(),
                    Amount = order.Total,
                    Currency = order.RiskPurchase?.Currency,
                    BankEventTimestamp = DateTimeOffset.Now,
                    PurchaseId = order.RiskPurchase.PurchaseId,
                    Reason = refundReason.ToString(),
                    Status = refundStatus.ToString(),
                    UserId = order.RiskPurchase.User.UserId,
                };

                var correlationId = _fraudProtectionService.NewCorrelationId;
                var response = await _fraudProtectionService.PostRefund(refund, correlationId);

                var fraudProtectionIO = new FraudProtectionIOModel(correlationId, refund, response, "Refund");
                TempData.Put(FraudProtectionIOModel.TempDataKey, fraudProtectionIO);
                #endregion

                order.ReturnOrChargebackReason = refundReason.ToString();
                order.Status = refundStatus == RefundStatus.Approved ? OrderStatus.ReturnCompleted : OrderStatus.ReturnRejected;
                order.RiskRefund = refund;
                order.RiskRefundResponse = response;
            }));
        }
 public EarlyRefundTicketMessage(EarlyRefundTicket ticket, RefundReason reason)
 {
     Ticket = ticket;
     Reason = reason;
 }
Пример #13
0
        public async Task <Result <List <OrderRefund> > > ProceedRefund(int orderId, RefundReason refundReason, OrderStatus?setOrderStatus = null, decimal?requestRefundAmount = null)
        {
            if (_me.IsAnonymous)
            {
                return(new Failure <List <OrderRefund> >("需要先登录"));
            }

            var order = _db.Orders
                        .Include(x => x.Payments).ThenInclude(x => x.Refunds)
                        .Where(x => x.BuyerId == _me.Id)
                        .Where(x => x.Id == orderId)
                        .SingleOrDefault();

            if (order == null)
            {
                return(new Failure <List <OrderRefund> >("退款失败,未找到订单"));
            }

            const int daysToRefundAfterOrderCompleted = 30;

            if (order.CompletedTime.HasValue && order.CompletedTime < DateTime.Now.AddDays(daysToRefundAfterOrderCompleted))
            {
                return(new Failure <List <OrderRefund> >($"只能在订单完成后的 {daysToRefundAfterOrderCompleted} 天内进行退款"));
            }

            if (_alipay == null && order.Payments.Any(x => x.Choise == PaymentChoise.Alipay))
            {
                throw new ArgumentNullException(nameof(_alipay));
            }
            if (_wechat == null && order.Payments.Any(x => x.Choise == PaymentChoise.WeChat))
            {
                throw new ArgumentNullException(nameof(_wechat));
            }

            var totalPaid = order.Payments
                            .Where(x => x.Status == PaymentStatus.Paid)
                            .Sum(x => x.Amount);

            var refundedBefore = order.Payments.SelectMany(x => x.Refunds)
                                 .Where(x => x.Status != RefundStatus.Cancelled)
                                 .Sum(x => x.Amount);

            var refundMaxAllowed = totalPaid - refundedBefore;

            if (requestRefundAmount.HasValue && requestRefundAmount.Value > refundMaxAllowed)
            {
                return(new Failure <List <OrderRefund> >("超出总共可退款金额"));
            }

            var refundRemained = requestRefundAmount ?? refundMaxAllowed;
            var pending        = new List <OrderRefund>();

            //遍历该订单下的付款记录,依次轮询并使用可退款金额,并插入待退款记录(OrderRefund)
            foreach (var payment in order.Payments)
            {
                if (payment.Status != PaymentStatus.Paid)
                {
                    continue;
                }

                var refundable = payment.Amount - payment.Refunds.Where(x => x.Status != RefundStatus.Cancelled).Sum(x => x.Amount);
                var refunding  = Math.Min(refundRemained, refundable);

                var refund = new OrderRefund {
                    RefundNo = OrderIdGen.New(),
                    Amount   = refunding,
                    Reason   = refundReason,
                    Status   = RefundStatus.Await
                };

                payment.Refunds.Add(refund);
                pending.Add(refund);

                refundRemained -= refunding;

                if (refundRemained <= 0)
                {
                    break;
                }
            }

            order.Logs.Add(new OrderLog {
                Remarks = $"发生退款 {refundRemained:f2} 元"
            });

            if (setOrderStatus.HasValue && setOrderStatus != order.Status)
            {
                order.Logs.Add(new OrderLog {
                    StatusFrom      = order.Status,
                    StatusChangedTo = setOrderStatus,
                    Remarks         = $"因退款,订单状态发生变化"
                });
                order.Status = setOrderStatus.Value;
            }

            //先保存一次数据
            await _db.Normalize().SaveChangesAsync();

            //保存之后,再实际请求API进行退款;若退款成功,再改变新增退款记录的状态并保存
            foreach (var refund in pending)
            {
                Result result = null !;

                switch (refund.OriginalPayment.Choise)
                {
                case PaymentChoise.WeChat:
                    result = _wechat !.PayService().Refund(
                        refund.OriginalPayment.AppId !,
                        refund.OriginalPayment.PaymentNo,
                        refund.RefundNo,
                        refund.OriginalPayment.Amount,
                        refund.Amount,
                        refund.Reason.ToLabel()
                        );
                    break;

                case PaymentChoise.Alipay:
                    result = _alipay !.Refund(
                        refund.OriginalPayment.PaymentNo,
                        refund.RefundNo,
                        refund.Amount,
                        refund.Reason.ToLabel()
                        );
                    break;
                }

                refund.Status        = result.Ok ? RefundStatus.Refunded : RefundStatus.Checking;
                refund.ResultMessage = result.Message;
            }

            //再次保存数据,更新刚新插入 OrderRefund 的状态
            await _db.Normalize().SaveChangesAsync();

            //返回执行结果
            if (pending.Any(x => x.Status != RefundStatus.Refunded))
            {
                var choises = string.Join("及", pending.Select(x => x.OriginalPayment.Choise).Distinct().ToArray());
                return(new Success <List <OrderRefund> >($"已提交退款,但{choises}返回信息未成功", pending));
            }
            return(new Success <List <OrderRefund> >(pending));
        }