public virtual async Task HandleRefundTransaction(HSOrderWorksheet worksheet, HSPayment creditCardPayment, HSPaymentTransaction creditCardPaymentTransaction, decimal totalToRefund, RMA rma) { try { CardConnectRefundRequest requestedRefund = new CardConnectRefundRequest() { currency = worksheet.Order.xp.Currency.ToString(), merchid = creditCardPaymentTransaction.xp.CardConnectResponse.merchid, retref = creditCardPaymentTransaction.xp.CardConnectResponse.retref, amount = totalToRefund.ToString("F2"), }; CardConnectRefundResponse response = await _cardConnect.Refund(requestedRefund); HSPayment newCreditCardPayment = new HSPayment() { Amount = response.amount }; await _oc.Payments.CreateTransactionAsync(OrderDirection.Incoming, rma.SourceOrderID, creditCardPayment.ID, CardConnectMapper.Map(newCreditCardPayment, response)); } catch (CreditCardRefundException ex) { throw new CatalystBaseException(new ApiError { ErrorCode = "Payment.FailedToRefund", Message = ex.ApiError.Message }); } }
public async Task EmailVoidAuthorizationFailedAsync(HSPayment payment, string transactionID, HSOrder order, CreditCardVoidException ex) { var templateData = new EmailTemplate <SupportTemplateData>() { Data = new SupportTemplateData { OrderID = order.ID, DynamicPropertyName1 = "BuyerID", DynamicPropertyValue1 = order.FromCompanyID, DynamicPropertyName2 = "Username", DynamicPropertyValue2 = order.FromUser.Username, DynamicPropertyName3 = "PaymentID", DynamicPropertyValue3 = payment.ID, DynamicPropertyName4 = "TransactionID", DynamicPropertyValue4 = transactionID, ErrorJsonString = JsonConvert.SerializeObject(ex.ApiError) }, Message = new EmailDisplayText() { EmailSubject = "Manual intervention required for this order", DynamicText = "Error encountered while trying to void authorization on this order. Please contact customer and help them manually void authorization" } }; var toList = new List <EmailAddress>(); var supportEmails = _settings?.SendgridSettings?.CriticalSupportEmails.Split(","); foreach (var email in supportEmails) { toList.Add(new EmailAddress { Email = email }); } await SendSingleTemplateEmailMultipleRcpts(_settings?.SendgridSettings?.FromEmail, toList, _settings?.SendgridSettings?.CriticalSupportTemplateID, templateData); }
private HSPaymentTransaction GetValidTransaction(string orderID, HSPayment payment) { var ordered = payment.Transactions.OrderBy(x => x.DateExecuted); var transaction = payment.Transactions .OrderBy(x => x.DateExecuted) .LastOrDefault(x => x.Type == "CreditCard" && x.Succeeded); if (transaction == null) { throw new PaymentCaptureJobException("No valid payment authorization on the order", orderID, payment.ID); } if (transaction?.xp?.CardConnectResponse == null) { throw new PaymentCaptureJobException("Missing transaction.xp.CardConnectResponse", orderID, payment.ID, transaction.ID); } var authHasBeenVoided = payment.Transactions.Any(t => t.Type == "CreditCardVoidAuthorization" && t.Succeeded && t.xp?.CardConnectResponse?.retref == transaction.xp?.CardConnectResponse?.retref ); if (authHasBeenVoided) { throw new PaymentCaptureJobException("Payment authorization has been voided", orderID, payment.ID, transaction.ID); } return(transaction); }
public void LogVoidAuthorizationFailed(HSPayment payment, string transactionID, HSOrder order, CreditCardVoidException ex) { // track in app insights // to find go to Transaction Search > Event Type = Event > Filter by any of these custom properties or event name "Payment.VoidAuthorizationFailed" var customProperties = new Dictionary <string, string> { { "Message", "Attempt to void authorization on payment failed" }, { "OrderID", order.ID }, { "BuyerID", order.FromCompanyID }, { "UserEmail", order.FromUser.Email }, { "PaymentID", payment.ID }, { "TransactionID", transactionID }, { "ErrorResponse", JsonConvert.SerializeObject(ex.ApiError, Formatting.Indented) } }; _telemetry.TrackEvent("Payment.VoidAuthorizationFailed", customProperties); }
private async Task UpdatePoPaymentAsync(HSPayment requestedPayment, HSPayment existingPayment, HSOrderWorksheet worksheet) { var paymentAmount = worksheet.Order.Total; if (existingPayment == null) { requestedPayment.Amount = paymentAmount; await _oc.Payments.CreateAsync <HSPayment>(OrderDirection.Incoming, worksheet.Order.ID, requestedPayment); } else { await _oc.Payments.PatchAsync <HSPayment>(OrderDirection.Incoming, worksheet.Order.ID, existingPayment.ID, new PartialPayment { Amount = paymentAmount }); } }
public async Task VoidTransactionAsync(HSPayment payment, HSOrder order, string userToken) { var transactionID = ""; try { if (payment.Accepted == true) { var transaction = payment.Transactions .Where(x => x.Type == "CreditCard") .OrderBy(x => x.DateExecuted) .LastOrDefault(t => t.Succeeded); var retref = transaction?.xp?.CardConnectResponse?.retref; if (retref != null) { transactionID = transaction.ID; var userCurrency = await _hsExchangeRates.GetCurrencyForUser(userToken); var response = await _cardConnect.VoidAuthorization(new CardConnectVoidRequest { currency = userCurrency.ToString(), merchid = GetMerchantID(userCurrency), retref = transaction.xp.CardConnectResponse.retref }); await WithRetry().ExecuteAsync(() => _oc.Payments.CreateTransactionAsync(OrderDirection.Incoming, order.ID, payment.ID, CardConnectMapper.Map(payment, response))); } } } catch (CreditCardVoidException ex) { await _supportAlerts.VoidAuthorizationFailed(payment, transactionID, order, ex); await WithRetry().ExecuteAsync(() => _oc.Payments.CreateTransactionAsync(OrderDirection.Incoming, order.ID, payment.ID, CardConnectMapper.Map(payment, ex.Response))); throw new OrderCloudIntegrationException(new ApiError { ErrorCode = "Payment.FailedToVoidAuthorization", Message = ex.ApiError.Message }); } }
private async Task UpdateCCPaymentAsync(HSPayment requestedPayment, HSPayment existingPayment, HSOrderWorksheet worksheet, string userToken) { var paymentAmount = worksheet.Order.Total; if (existingPayment == null) { requestedPayment.Amount = paymentAmount; requestedPayment.Accepted = false; requestedPayment.Type = requestedPayment.Type; await _oc.Payments.CreateAsync <HSPayment>(OrderDirection.Outgoing, worksheet.Order.ID, requestedPayment, userToken); // need user token because admins cant see personal credit cards } else if (existingPayment.CreditCardID == requestedPayment.CreditCardID && existingPayment.Amount == paymentAmount) { // do nothing, payment doesnt need updating return; } else if (existingPayment.CreditCardID == requestedPayment.CreditCardID) { await _ccCommand.VoidTransactionAsync(existingPayment, worksheet.Order, userToken); await _oc.Payments.PatchAsync <HSPayment>(OrderDirection.Incoming, worksheet.Order.ID, existingPayment.ID, new PartialPayment { Accepted = false, Amount = paymentAmount, xp = requestedPayment.xp }); } else { // we need to delete payment because you can't have payments totaling more than order total and you can't set payments to $0 await DeleteCreditCardPaymentAsync(existingPayment, worksheet.Order, userToken); requestedPayment.Amount = paymentAmount; requestedPayment.Accepted = false; await _oc.Payments.CreateAsync <HSPayment>(OrderDirection.Outgoing, worksheet.Order.ID, requestedPayment, userToken); // need user token because admins cant see personal credit cards } }
private async Task CapturePaymentAsync(HSOrder order, HSPayment payment, HSPaymentTransaction transaction) { try { var response = await _cardConnect.Capture(new CardConnectCaptureRequest { merchid = transaction.xp.CardConnectResponse.merchid, retref = transaction.xp.CardConnectResponse.retref, currency = order.xp.Currency.ToString() }); await _oc.Payments.CreateTransactionAsync(OrderDirection.Incoming, order.ID, payment.ID, CardConnectMapper.Map(payment, response)); } catch (CardConnectInquireException ex) { throw new PaymentCaptureJobException("Error inquiring payment. Message: {ex.ApiError.Message}, ErrorCode: {ex.ApiError.ErrorCode}", order.ID, payment.ID, transaction.ID); } catch (CardConnectCaptureException ex) { await _oc.Payments.CreateTransactionAsync(OrderDirection.Incoming, order.ID, payment.ID, CardConnectMapper.Map(payment, ex.Response)); throw new PaymentCaptureJobException($"Error capturing payment. Message: {ex.ApiError.Message}, ErrorCode: {ex.ApiError.ErrorCode}", order.ID, payment.ID, transaction.ID); } }
public virtual async Task HandleRefund(RMA rma, CosmosListPage <RMA> allRMAsOnThisOrder, HSOrderWorksheet worksheet, DecodedToken decodedToken) { // Get payment info from the order ListPage <HSPayment> paymentResponse = await _oc.Payments.ListAsync <HSPayment>(OrderDirection.Incoming, rma.SourceOrderID); HSPayment creditCardPayment = paymentResponse.Items.FirstOrDefault(payment => payment.Type == OrderCloud.SDK.PaymentType.CreditCard); if (creditCardPayment == null) { // Items were not paid for with a credit card. No refund to process via CardConnect. return; } HSPaymentTransaction creditCardPaymentTransaction = creditCardPayment.Transactions .OrderBy(x => x.DateExecuted) .LastOrDefault(x => x.Type == "CreditCard" && x.Succeeded); decimal purchaseOrderTotal = (decimal)paymentResponse.Items .Where(payment => payment.Type == OrderCloud.SDK.PaymentType.PurchaseOrder) .Select(payment => payment.Amount) .Sum(); // Refund via CardConnect CardConnectInquireResponse inquiry = await _cardConnect.Inquire(new CardConnectInquireRequest { merchid = creditCardPaymentTransaction.xp.CardConnectResponse.merchid, orderid = rma.SourceOrderID, set = "1", currency = worksheet.Order.xp.Currency.ToString(), retref = creditCardPaymentTransaction.xp.CardConnectResponse.retref }); decimal shippingRefund = rma.Type == RMAType.Cancellation ? GetShippingRefundIfCancellingAll(worksheet, rma, allRMAsOnThisOrder) : 0M; decimal lineTotalToRefund = rma.LineItems .Where(li => li.IsResolved && !li.IsRefunded && li.RefundableViaCreditCard && (li.Status == RMALineItemStatus.PartialQtyComplete || li.Status == RMALineItemStatus.Complete) ).Select(li => li.LineTotalRefund) .Sum(); decimal totalToRefund = lineTotalToRefund + shippingRefund; // Update Total Credited on RMA rma.TotalCredited += totalToRefund; // Transactions that are queued for capture can only be fully voided, and we are only allowing partial voids moving forward. if (inquiry.voidable == "Y" && inquiry.setlstat == QUEUED_FOR_CAPTURE) { throw new CatalystBaseException(new ApiError { ErrorCode = "Payment.FailedToVoidAuthorization", Message = "This customer's credit card transaction is currently queued for capture and cannot be refunded at this time. Please try again later." }); } // If voidable, but not refundable, void the refund amount off the original order total if (inquiry.voidable == "Y") { await HandleVoidTransaction(worksheet, creditCardPayment, creditCardPaymentTransaction, totalToRefund, rma); } // If refundable, but not voidable, do a refund if (inquiry.voidable == "N") { await HandleRefundTransaction(worksheet, creditCardPayment, creditCardPaymentTransaction, totalToRefund, rma); } }
public async Task VoidAuthorizationFailed(HSPayment payment, string transactionID, HSOrder order, CreditCardVoidException ex) { LogVoidAuthorizationFailed(payment, transactionID, order, ex); await _sendgrid.EmailVoidAuthorizationFailedAsync(payment, transactionID, order, ex); }
private async Task DeleteCreditCardPaymentAsync(HSPayment payment, HSOrder order, string userToken) { await _ccCommand.VoidTransactionAsync(payment, order, userToken); await _oc.Payments.DeleteAsync(OrderDirection.Incoming, order.ID, payment.ID); }