Пример #1
0
        public async Task <ActionResult> Cancel(ReceiptResponseModel response)
        {
#if DEBUG
            // For testing local only, we should process the actual payment
            // Live systems will use the side channel message from cybersource direct to record the payment event
            // This will duplicate some log messages. That is OKAY
            await ProviderNotify(response);
#endif

            Log.ForContext("response", response, true).Information("Receipt response received");

            // check signature
            var dictionary = Request.Form.ToDictionary(x => x.Key.ToString(), x => x.Value.ToString());
            if (!_dataSigningService.Check(dictionary, response.Signature))
            {
                Log.Error("Check Signature Failure");
                ErrorMessage = "An error has occurred. Payment not processed. If you experience further problems, contact us.";
                return(StatusCode(500));
            }

            // find matching invoice
            var invoice = _dbContext.Invoices.SingleOrDefault(a => a.Id == response.Req_Reference_Number);
            if (invoice == null)
            {
                Log.Error("Order not found {0}", response.Req_Reference_Number);
                ErrorMessage = "Invoice for payment not found. Please contact technical support.";
                return(PublicNotFound());
            }

            ErrorMessage = "Payment Process Cancelled";
            return(RedirectToAction(nameof(Pay), new { id = invoice.LinkId }));
        }
Пример #2
0
        public async Task <ActionResult> Receipt(ReceiptResponseModel response)
        {
#if DEBUG
            // For testing local only, we should process the actual payment
            // Live systems will use the side channel message from cybersource direct to record the payment event
            // This will duplicate some log messages. That is OKAY
            await ProviderNotify(response);
#endif

            Log.ForContext("response", response, true).Information("Receipt response received");

            // check signature
            var dictionary = Request.Form.ToDictionary(x => x.Key.ToString(), x => x.Value.ToString());
            if (!_dataSigningService.Check(dictionary, response.Signature))
            {
                Log.Error("Check Signature Failure");
                ErrorMessage = "An error has occurred. Payment not processed. If you experience further problems, contact us.";
                return(StatusCode(500));
            }

            // find matching invoice
            var invoice = await _dbContext.Invoices
                          .Include(i => i.Items)
                          .Include(i => i.Team)
                          .Include(i => i.Attachments)
                          .Include(i => i.Coupon)
                          .SingleOrDefaultAsync(a => a.Id == response.Req_Reference_Number);

            if (invoice == null)
            {
                Log.Error("Order not found {0}", response.Req_Reference_Number);
                ErrorMessage = "Invoice for payment not found. Please contact technical support.";
                return(PublicNotFound());
            }

            invoice.UpdateCalculatedValues();

            //TODO: Update invoice in the DB here? Would that cause conflicts if it happens in the "official" ProviderNotify endpoint?

            var responseValid = CheckResponse(response);
            if (!responseValid.IsValid)
            {
                // send them back to the pay page with errors
                ErrorMessage = string.Format("Errors detected: {0}", string.Join(",", responseValid.Errors));
                return(RedirectToAction(nameof(Pay), new { id = invoice.LinkId }));
            }

            // Should be good
            Message = "Payment Processed. Thank You.";

            // Fake payment status
            var model = CreateInvoicePaymentViewModel(invoice);
            model.Paid     = true;
            model.PaidDate = response.AuthorizationDateTime;

            return(View("Pay", model));
        }
Пример #3
0
        public async Task <ActionResult> ProviderNotify(ReceiptResponseModel response)
        {
            Log.ForContext("response", response, true).Information("Provider Notification Received");

            // check signature
            var dictionary = Request.Form.ToDictionary(x => x.Key.ToString(), x => x.Value.ToString());

            if (!_dataSigningService.Check(dictionary, response.Signature))
            {
                Log.Error("Check Signature Failure");
                return(new JsonResult(new { }));
            }

            var payment = ProcessPaymentEvent(response, dictionary);

            //Do payment stuff.
            var order = _context.Orders.SingleOrDefault(a => a.Id == response.Req_Reference_Number);

            if (order == null)
            {
                Log.Error("Order not found {0}", response.Req_Reference_Number);
                return(new JsonResult(new { }));
            }

            if (response.Decision == ReplyCodes.Accept)
            {
                order.ApprovedPayment = payment;
                order.Paid            = true;

                order.History.Add(new History
                {
                    Action      = "Credit Card Payment Accepted",
                    Status      = order.Status,
                    JsonDetails = order.JsonDetails,
                });

                try
                {
                    await _orderMessageService.EnqueuePaidMessage(order); //This will continue to fail unless the order includes the creator
                }
                catch (Exception ex)
                {
                    Log.Error(ex, ex.Message);
                }
            }
            await _context.SaveChangesAsync();

            return(new JsonResult(new { }));
        }
Пример #4
0
        private PaymentEvent ProcessPaymentEvent(ReceiptResponseModel response, Dictionary <string, string> dictionary)
        {
            var paymentEvent = new PaymentEvent
            {
                Transaction_Id       = response.Transaction_Id,
                Auth_Amount          = response.Auth_Amount,
                Decision             = response.Decision,
                Reason_Code          = response.Reason_Code,
                Req_Reference_Number = response.Req_Reference_Number,
                ReturnedResults      = JsonConvert.SerializeObject(dictionary)
            };

            _context.PaymentEvents.Add(paymentEvent);

            return(paymentEvent);
        }
Пример #5
0
        public ActionResult Receipt(ReceiptResponseModel response)
        {
            Log.ForContext("response", response, true).Information("Receipt response received");

            // check signature
            var dictionary = Request.Form.ToDictionary(x => x.Key.ToString(), x => x.Value.ToString());

            if (!_dataSigningService.Check(dictionary, response.Signature))
            {
                Log.Error("Check Signature Failure");
                ErrorMessage = "An error has occurred. Payment not processed. If you experience further problems, contact us.";
                return(RedirectToAction("Index", "Home"));
            }

            //var test = ProcessPaymentEvent(response, dictionary); //For testing locally, can enable this.
            //_context.SaveChanges();

            var order = _context.Orders.SingleOrDefault(a => a.Id == response.Req_Reference_Number);

            if (order == null)
            {
                Log.Error("Order not found {0}", response.Req_Reference_Number);
                ErrorMessage = "Order for payment not found. Please contact technical support.";
                return(NotFound(response.Req_Reference_Number));
            }
            //Note, don't check who has access as anyone may pay.

            var responseValid = CheckResponse(response);

            if (!responseValid.IsValid)
            {
                ErrorMessage     = string.Format("Errors detected: {0}", string.Join(",", responseValid.Errors));
                ViewBag.Declined = true;
                return(View(response));
                //return RedirectToAction("PaymentError", new {id = ErrorMessage}); //For some reason, the ErrorMessage is getting lost in the redirect
            }

            //Should be good,
            Message = "Payment Processed. Thank You.";

            ViewBag.ShareId = order.ShareIdentifier;

            //ViewBag.PaymentDictionary = dictionary; //Debugging. Remove when not needed

            return(View(response));
        }
Пример #6
0
        public ActionResult Cancel(ReceiptResponseModel response)
        {
            {
                Log.ForContext("response", response, true).Information("Cancel response received");

                // check signature
                var dictionary = Request.Form.ToDictionary(x => x.Key.ToString(), x => x.Value.ToString());
                if (!_dataSigningService.Check(dictionary, response.Signature))
                {
                    Log.Error("Check Signature Failure");
                    ErrorMessage = "An error has occurred. Payment not processed. If you experience further problems, contact us.";
                    return(RedirectToAction("Index", "Home"));
                }
                //ViewBag.PaymentDictionary = dictionary; //Debugging. Remove when not needed
                //ProcessPaymentEvent(response, dictionary); //TODO: Do we want to try to write cancel events?

                return(View(response));
            }
        }
Пример #7
0
        private async Task <PaymentEvent> ProcessPaymentEvent(ReceiptResponseModel response, Dictionary <string, string> dictionary)
        {
            // create and record event
            var paymentEvent = new PaymentEvent
            {
                Processor         = "CyberSource",
                ProcessorId       = response.Transaction_Id,
                Decision          = response.Decision,
                OccuredAt         = response.AuthorizationDateTime,
                BillingFirstName  = response.Req_Bill_To_Forename.SafeTruncate(60),
                BillingLastName   = response.Req_Bill_To_Surname.SafeTruncate(60),
                BillingEmail      = response.Req_Bill_To_Email.SafeTruncate(1500),
                BillingCompany    = response.Req_Bill_To_Company_Name.SafeTruncate(60),
                BillingPhone      = response.Req_Bill_To_Phone.SafeTruncate(100),
                BillingStreet1    = response.Req_Bill_To_Address_Line1.SafeTruncate(400),
                BillingStreet2    = response.Req_Bill_To_Address_Line2.SafeTruncate(400),
                BillingCity       = response.Req_Bill_To_Address_City.SafeTruncate(50),
                BillingState      = response.Req_Bill_To_Address_State.SafeTruncate(64),
                BillingCountry    = response.Req_Bill_To_Address_Country.SafeTruncate(2),
                BillingPostalCode = response.Req_Bill_To_Address_Postal_Code.SafeTruncate(10),
                CardType          = response.Req_Card_Type.SafeTruncate(3),
                CardNumber        = response.Req_Card_Number.SafeTruncate(20),
                CardExpiry        = response.CardExpiration,
                ReturnedResults   = JsonConvert.SerializeObject(dictionary),
            };

            if (decimal.TryParse(response.Auth_Amount, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out decimal amount))
            {
                paymentEvent.Amount = amount;
            }

            _dbContext.PaymentEvents.Add(paymentEvent);
            await _dbContext.SaveChangesAsync();

            return(paymentEvent);
        }
Пример #8
0
        /// <summary>
        /// These are copied from Give and moved into a private method.
        /// They have only had basic testing done against them.
        /// </summary>
        /// <param name="response"></param>
        /// <returns></returns>
        private CheckResponseResults CheckResponse(ReceiptResponseModel response)
        {
            var rtValue = new CheckResponseResults();

            //Ok, check response
            // general error, bad request
            if (string.Equals(response.Decision, ReplyCodes.Error) ||
                response.Reason_Code == ReasonCodes.BadRequestError ||
                response.Reason_Code == ReasonCodes.MerchantAccountError)
            {
                Log.ForContext("decision", response.Decision).ForContext("reason", response.Reason_Code)
                .Warning("Unsuccessful Reply");
                rtValue.Errors.Add("An error has occurred. If you experience further problems, please contact us");
            }

            // this is only possible on a hosted payment page
            if (string.Equals(response.Decision, ReplyCodes.Cancel))
            {
                Log.ForContext("decision", response.Decision).ForContext("reason", response.Reason_Code).Warning("Cancelled Reply");
                rtValue.Errors.Add("The payment process was canceled before it could complete. If you experience further problems, please contact us");
            }

            // manual review required
            if (string.Equals(response.Decision, ReplyCodes.Review))
            {
                Log.ForContext("decision", response.Decision).ForContext("reason", response.Reason_Code).Warning("Manual Review Reply");
                rtValue.Errors.Add("Error with Credit Card. Please contact issuing bank. If you experience further problems, please contact us");
            }

            // bad cc information, return to payment page
            if (string.Equals(response.Decision, ReplyCodes.Decline))
            {
                if (response.Reason_Code == ReasonCodes.AvsFailure)
                {
                    Log.ForContext("decision", response.Decision).ForContext("reason", response.Reason_Code).Warning("Avs Failure");
                    rtValue.Errors.Add("We’re sorry, but it appears that the billing address that you entered does not match the billing address registered with your card. Please verify that the billing address and zip code you entered are the ones registered with your card issuer and try again. If you experience further problems, please contact us");
                }

                if (response.Reason_Code == ReasonCodes.BankTimeoutError ||
                    response.Reason_Code == ReasonCodes.ProcessorTimeoutError)
                {
                    Log.ForContext("decision", response.Decision).ForContext("reason", response.Reason_Code).Error("Bank Timeout Error");
                    rtValue.Errors.Add("Error contacting Credit Card issuing bank. Please wait a few minutes and try again. If you experience further problems, please contact us");
                }
                else
                {
                    Log.ForContext("decision", response.Decision).ForContext("reason", response.Reason_Code).Warning("Declined Card Error");
                    rtValue.Errors.Add("We’re sorry but your credit card was declined. Please use an alternative credit card and try submitting again. If you experience further problems, please contact us");
                }
            }

            // good cc info, partial payment
            if (string.Equals(response.Decision, ReplyCodes.Accept) &&
                response.Reason_Code == ReasonCodes.PartialApproveError)
            {
                //I Don't think this can happen.
                //TODO: credit card was partially billed. flag transaction for review
                //TODO: send to general error page
                Log.ForContext("decision", response.Decision).ForContext("reason", response.Reason_Code).Error("Partial Payment Error");
                rtValue.Errors.Add("We’re sorry but a Partial Payment Error was detected. Please contact us");
            }

            if (rtValue.Errors.Count <= 0)
            {
                if (response.Decision != ReplyCodes.Accept)
                {
                    Log.Error("Got past all the other checks. But it still wasn't Accepted");
                    rtValue.Errors.Add("Unknown Error. Please contact us.");
                }
                else
                {
                    rtValue.IsValid = true;
                }
            }
            return(rtValue);
        }
Пример #9
0
        public async Task <ActionResult> ProviderNotify(ReceiptResponseModel response)
        {
            Log.ForContext("response", response, true).Information("Provider Notification Received");

            // check signature
            var dictionary = Request.Form.ToDictionary(x => x.Key.ToString(), x => x.Value.ToString());

            if (!_dataSigningService.Check(dictionary, response.Signature))
            {
                Log.Error("Check Signature Failure");
                return(new JsonResult(new { }));
            }

            // record payment process in db
            var payment = await ProcessPaymentEvent(response, dictionary);

            // try to find matching invoice
            var invoice = _dbContext.Invoices
                          .Include(i => i.Items)
                          .Include(i => i.Team)
                          .Include(i => i.Coupon)
                          .SingleOrDefault(a => a.Id == response.Req_Reference_Number);

            if (invoice == null)
            {
                Log.Error("Invoice not found {0}", response.Req_Reference_Number);
                return(new JsonResult(new { }));
            }

            // associate invoice
            payment.Invoice = invoice;

            if (response.Decision == ReplyCodes.Accept)
            {
                if (invoice.Coupon != null)
                {
                    invoice.ManualDiscount = invoice.GetDiscountAmount(); //Before it is set to paid. Even this way, depending when it happens, the notify might happen after the coupon expires.
                }
                invoice.Status             = Invoice.StatusCodes.Paid;
                invoice.Paid               = true;
                invoice.PaidAt             = response.AuthorizationDateTime;
                invoice.PaymentType        = PaymentTypes.CreditCard;
                invoice.PaymentProcessorId = response.Transaction_Id;

                invoice.UpdateCalculatedValues(); //Need to do after it is paid because they may not have got an expired discount?

                // record action
                var action = new History()
                {
                    Type           = HistoryActionTypes.PaymentCompleted.TypeCode,
                    ActionDateTime = DateTime.UtcNow,
                };
                invoice.History.Add(action);

                // send email
                try
                {
                    await _emailService.SendReceipt(invoice, payment);
                }
                catch (Exception err)
                {
                    Log.Error(err, "Error while trying to send receipt.");
                }

                // process notifications
                try
                {
                    await _notificationService.SendPaidNotification(new PaidNotification()
                    {
                        InvoiceId = invoice.Id
                    });
                }
                catch (Exception ex)
                {
                    Log.Error(ex, "Error while sending notification.");
                }
            }
            else
            {
                // record action
                var action = new History()
                {
                    Type           = HistoryActionTypes.PaymentFailed.TypeCode,
                    ActionDateTime = DateTime.UtcNow,
                };
                invoice.History.Add(action);
            }

            await _dbContext.SaveChangesAsync();

            return(new JsonResult(new { }));
        }