private ValidationResult ReadyToCreatePayment(ShopifyTransaction currentTransaction) { var context = BuildContext(currentTransaction); //!x.ShopifyOrder.ShopifyAreAllItemsRefunded && var validation = BuildBaseValidation() .Add(x => x.AcumaticaSalesOrder != null, "Acumatica Sales Order not created yet", instantFailure: true) .Add(x => !x.ShopifyOrder.HasSyncWithUnknownNbr(), "Acumatica Sales Order appears corrupted; please re-run", instantFailure: true) .Add(x => x.ValidPaymentGateway, $"Does not have a valid payment gateway; please check configuration") .Add(x => !x.CurrentTransaction.ExistsInAcumatica(), "Payment has already been created in Acumatica") .Add(x => x.CurrentTransaction.IsPayment(), $"Transaction is not a capture or sale"); //.Add(x => x.AcumaticaSalesOrder == null || x.AcumaticaSalesOrder.AcumaticaIsTaxValid, // "Acumatica Sales Order Taxes are invalid"); return(validation.Run(context)); }
private AcumaticaPayment RetrievePaymentWithMissingId(ShopifyTransaction transactionRecord) { var acumaticaPayment = transactionRecord.AcumaticaPayment; if (acumaticaPayment.AcumaticaRefNbr == AcumaticaSyncConstants.UnknownRefNbr) { var paymentRef = transactionRecord.ShopifyTransactionId.ToString(); var payments = _paymentClient.RetrievePaymentByPaymentRef(paymentRef); if (payments.Count == 0) { _syncOrderRepository.DeleteErroneousPaymentRecord(transactionRecord.MonsterId); throw new Exception($"Shopify Transaction {transactionRecord.MonsterId} sync to Acumatica Payment false record detected"); } if (payments.Count > 1) { throw new Exception($"Multiple Acumatica Payment records with Payment Ref {paymentRef}"); } var correctedPaymentNbr = payments.First().ReferenceNbr.value; _syncOrderRepository.UpdatePaymentRecordRefNbr(transactionRecord.MonsterId, correctedPaymentNbr); acumaticaPayment.AcumaticaRefNbr = correctedPaymentNbr; } return(acumaticaPayment); }
private void PullTransactionsFromShopify(ShopifyOrder orderRecord) { var transactionsJson = _orderApi.RetrieveTransactions(orderRecord.ShopifyOrderId); var transactions = transactionsJson.DeserializeFromJson <TransactionList>(); var order = _shopifyJsonService.RetrieveOrder(orderRecord.ShopifyOrderId); foreach (var transaction in transactions.transactions) { var transactionRecord = _orderRepository.RetrieveTransaction(transaction.id); if (transactionRecord != null) { transactionRecord.LastUpdated = DateTime.UtcNow; _orderRepository.SaveChanges(); continue; } using (var dbTransaction = _orderRepository.BeginTransaction()) { var record = new ShopifyTransaction(); if (transaction.kind == TransactionKind.Refund) { record.IsSyncableToPayment = true; var refund = order.RefundByTransaction(transaction.id); if (refund != null) { record.ShopifyRefundId = refund.id; record.IsPureCancel = refund.IsPureCancel; } } if (transaction.kind == TransactionKind.Capture || transaction.kind == TransactionKind.Sale) { record.IsSyncableToPayment = true; } record.ShopifyOrderId = transaction.order_id; record.ShopifyTransactionId = transaction.id; record.ShopifyStatus = transaction.status; record.ShopifyKind = transaction.kind; record.ShopifyGateway = transaction.gateway; record.ShopifyAmount = transaction.amount; record.ShopifyOrderMonsterId = orderRecord.MonsterId; record.DateCreated = DateTime.UtcNow; record.LastUpdated = DateTime.UtcNow; _logService.Log(LogBuilder.DetectedNewShopifyTransaction(record)); _orderRepository.InsertTransaction(record); _shopifyJsonService.Upsert( ShopifyJsonType.Transaction, transaction.id, transaction.SerializeToJson()); dbTransaction.Commit(); } } orderRecord.NeedsTransactionGet = false; _orderRepository.SaveChanges(); }
private PaymentValidationContext BuildContext(ShopifyTransaction currentTransaction) { var output = new PaymentValidationContext(); output.CurrentTransaction = currentTransaction; output.ValidPaymentGateway = _settingsRepository.GatewayExistsInConfig(currentTransaction.ShopifyGateway); return(output); }
private ValidationResult ReadyToRelease(ShopifyTransaction currentTransaction) { var context = BuildContext(currentTransaction); var validation = BuildBaseValidation() .Add(x => x.CurrentTransaction.ExistsInAcumatica(), "Transaction has not been synced to Acumatica yet") .Add(x => !x.CurrentTransaction.IsReleased(), "This Transaction has been released already"); return(validation.Run(context)); }
private ValidationResult ReadyToUpdatePayment(ShopifyTransaction currentTransaction) { var context = BuildContext(currentTransaction); var validation = BuildBaseValidation() .Add(x => x.ShopifyOrder.OriginalPaymentNeedsUpdateForRefund(), "Payment has already been updated in Acumatica") .Add(x => x.CurrentTransaction.ExistsInAcumatica(), "Payment has not been synced") .Add(x => x.CurrentTransaction.IsPayment(), $"Transaction is not a capture or sale"); return(validation.Run(context)); }
public PaymentWrite BuildPaymentForCreate(ShopifyTransaction transactionRecord) { var transaction = _shopifyJsonService.RetrieveTransaction(transactionRecord.ShopifyTransactionId); var gateway = _settingsRepository.RetrievePaymentGatewayByShopifyId(transaction.gateway); // Locate the Acumatica Customer // var shopifyCustomerId = transactionRecord.CustomerId(); var customer = _syncOrderRepository.RetrieveCustomer(shopifyCustomerId); var acumaticaCustId = customer.AcumaticaCustId(); // Build the Payment Ref and Description // var orderRecord = _syncOrderRepository.RetrieveShopifyOrder(transactionRecord.OrderId()); var order = _shopifyJsonService.RetrieveOrder(orderRecord.ShopifyOrderId); // Create the payload for Acumatica // var payment = new PaymentWrite(); payment.CustomerID = acumaticaCustId.ToValue(); payment.Hold = false.ToValue(); payment.Type = PaymentType.Payment.ToValue(); payment.PaymentRef = $"{transaction.id}".ToValue(); var createdAtUtc = (transaction.created_at ?? order.created_at).UtcDateTime; var acumaticaDate = _acumaticaTimeZoneService.ToAcumaticaTimeZone(createdAtUtc); payment.ApplicationDate = acumaticaDate.Date.ToValue(); // Amount computations // payment.PaymentAmount = ((double)transaction.amount).ToValue(); var appliedToOrder = orderRecord.TheoreticalPaymentRemaining(); // Applied to Documents // var acumaticaOrderRef = orderRecord.AcumaticaSalesOrderId(); if (acumaticaOrderRef.HasValue() && orderRecord.IsNotCancelledOrAllRefunded()) { payment.OrdersToApply = PaymentOrdersRef.ForOrder(acumaticaOrderRef, SalesOrderType.SO, (double)appliedToOrder); } payment.PaymentMethod = gateway.AcumaticaPaymentMethod.ToValue(); payment.CashAccount = gateway.AcumaticaCashAccount.ToValue(); payment.Description = $"Payment for Shopify Order #{orderRecord.ShopifyOrderNumber}".ToValue(); return(payment); }
private PaymentWrite BuildCustomerRefund(ShopifyTransaction transactionRecord) { var transaction = _shopifyJsonService.RetrieveTransaction(transactionRecord.ShopifyTransactionId); var paymentGateway = _settingsRepository.RetrievePaymentGatewayByShopifyId(transaction.gateway); // Locate the Acumatica Customer // var shopifyCustomerId = transactionRecord.CustomerId(); var customer = _syncOrderRepository.RetrieveCustomer(shopifyCustomerId); var acumaticaCustId = customer.AcumaticaCustId(); // Build the Payment Ref and Description // var order = _syncOrderRepository.RetrieveShopifyOrder(transactionRecord.OrderId()); // Create the payload for Acumatica // var refundPayment = new PaymentWrite(); refundPayment.CustomerID = acumaticaCustId.ToValue(); refundPayment.Type = PaymentType.CustomerRefund.ToValue(); refundPayment.PaymentRef = $"{transaction.id}".ToValue(); // Reference to the original Payment // if (transactionRecord.NeedManualApply()) { refundPayment.Hold = true.ToValue(); refundPayment.PaymentAmount = ((double)transaction.amount).ToValue(); } else { var acumaticaPayment = order.PaymentTransaction().AcumaticaPayment; var balance = _paymentClient.RetrievePaymentBalance(acumaticaPayment.AcumaticaRefNbr, PaymentType.Payment); var amountToApply = transaction.amount > balance ? balance : transaction.amount; refundPayment.Hold = false.ToValue(); refundPayment.PaymentAmount = ((double)amountToApply).ToValue(); refundPayment.DocumentsToApply = PaymentDocumentsToApply.ForDocument( acumaticaPayment.AcumaticaRefNbr, acumaticaPayment.AcumaticaDocType, (double)amountToApply); } // Amounts // refundPayment.PaymentMethod = paymentGateway.AcumaticaPaymentMethod.ToValue(); refundPayment.CashAccount = paymentGateway.AcumaticaCashAccount.ToValue(); refundPayment.Description = $"Refund for Order #{order.ShopifyOrderNumber} (TransId #{transaction.id})".ToValue(); return(refundPayment); }
private ValidationResult ReadyToReleaseRefundPayment(ShopifyTransaction currentTransaction) { var context = BuildContext(currentTransaction); var validation = BuildBaseValidation() .Add(x => x.CurrentTransaction.ExistsInAcumatica(), "Refund has not been synced yet") .Add(x => x.OriginalPaymentTransaction.ExistsInAcumatica(), $"Payment has not been synced yet") .Add(x => x.CurrentTransaction.IsRefund(), $"Transaction Kind is not a refund"); //.Add(x => x.CurrentTransaction.AcumaticaPayment.AcumaticaRefNbr != AcumaticaSyncConstants.UnknownRefNbr, // $"Acumatica Payment/Customer Refund has unknown Reference Number"); return(validation.Run(context)); }
private ValidationResult ReadyToCreateRefundPayment(ShopifyTransaction currentTransaction) { var context = BuildContext(currentTransaction); var validation = BuildBaseValidation() .Add(x => !x.CurrentTransaction.ExistsInAcumatica(), "Refund has been synced already") .Add(x => x.OriginalPaymentTransaction.ExistsInAcumatica(), $"Original Payment has not been synced yet") .Add(x => !x.ShopifyOrder.OriginalPaymentNeedsUpdateForRefund(), "Original Payment needs to be updated in Acumatica before syncing Refund") .Add(x => !x.ShopifyOrder.HasUnreleasedTransactions(), "There are unreleased Payments/Refunds") .Add(x => x.CurrentTransaction.IsRefund(), $"Transaction Kind is not a refund"); return(validation.Run(context)); }
private void PushPaymentReleaseAndUpdateSync(ShopifyTransaction transactionRecord) { try { // Workarounds for Acumatica bug that prevents storage of Payment Nbr // var acumaticaPayment = RetrievePaymentWithMissingId(transactionRecord); // Release the actual Payment // _paymentClient.ReleasePayment(acumaticaPayment.AcumaticaRefNbr, acumaticaPayment.AcumaticaDocType); _syncOrderRepository.PaymentIsReleased(acumaticaPayment.ShopifyTransactionMonsterId); _syncOrderRepository.ResetOrderErrorCount(transactionRecord.ShopifyOrderId); } catch (Exception ex) { _systemLogger.Error(ex); _logService.Log($"Encounter error syncing {transactionRecord.LogDescriptor()}"); _syncOrderRepository.IncreaseOrderErrorCount(transactionRecord.ShopifyOrderId); return; } }
private PaymentWrite BuildPaymentForUpdate(ShopifyTransaction transactionRecord) { // Build the Payment Ref and Description // var order = _syncOrderRepository.RetrieveShopifyOrder(transactionRecord.OrderId()); var acumaticaOrderRef = order.AcumaticaSalesOrderId(); var paymentNbr = transactionRecord.AcumaticaPayment.AcumaticaRefNbr; // Get the balance from Acumatica in case other Invoices have grabbed some of the monies!! // var balance = _paymentClient.RetrievePaymentBalance(paymentNbr, PaymentType.Payment); var amountToApply = balance < order.TheoreticalPaymentRemaining() ? balance : order.TheoreticalPaymentRemaining(); var payment = new PaymentWrite(); payment.ReferenceNbr = paymentNbr.ToValue(); payment.Type = PaymentType.Payment.ToValue(); payment.OrdersToApply = PaymentOrdersRef.ForOrder(acumaticaOrderRef, SalesOrderType.SO, (double)amountToApply); return(payment); }
/// <summary> /// Capture a previously authorized order for the full amount /// </summary> /// <param name="transaction">The transaction.</param> /// <returns></returns> public ShopifyTransaction Capture(ShopifyTransaction transaction, long orderID) { return(Create(transaction, $"/admin/orders/{orderID}/transactions.json")); }
public static bool DoNotIgnore(this ShopifyTransaction transaction) { return(transaction.ShopifyGateway != Gateway.Manual && transaction.ShopifyStatus == TransactionStatus.Success && (transaction.IsPayment() || transaction.IsRefund())); }
public static bool Ignore(this ShopifyTransaction transaction) { return(!transaction.DoNotIgnore()); }
public static string ReleasingTransaction(ShopifyTransaction transaction) { return($"Releasing Acumatica record for {transaction.LogDescriptor()}"); }
public static string CreateAcumaticaCustomerRefund(ShopifyTransaction transaction) { return($"Creating Acumatica Customer Refund from {transaction.LogDescriptor()}"); }
public static string UpdateAcumaticaPayment(ShopifyTransaction transaction) { return($"Updating Acumatica Payment from {transaction.LogDescriptor()}"); }
public static bool NeedsSync(this ShopifyTransaction transaction) { return(transaction.IsSyncableToPayment && (!transaction.ExistsInAcumatica() || !transaction.IsReleased())); }
// Monster Transaction record extensions // public static bool IsPayment(this ShopifyTransaction transaction) { return(transaction.ShopifyKind == TransactionKind.Capture || transaction.ShopifyKind == TransactionKind.Sale); }
public static bool HasShippingRefund(this ShopifyTransaction transaction) { return(transaction.ShopifyRefundId.HasValue && transaction.ShopifyOrder.Refund(transaction.ShopifyRefundId.Value).HasShipping()); }
private void PushPaymentAndWriteSync(ShopifyTransaction transactionRecord, PaymentWrite payment) { // Push to Acumatica // string resultJson; try { resultJson = _paymentClient.WritePayment(payment.SerializeToJson()); _syncOrderRepository.ResetOrderErrorCount(transactionRecord.ShopifyOrderId); } catch (Exception ex) { _systemLogger.Error(ex); _logService.Log($"Encounter error syncing {transactionRecord.LogDescriptor()}"); _syncOrderRepository.IncreaseOrderErrorCount(transactionRecord.ShopifyOrderId); return; } var resultPayment = resultJson.DeserializeFromJson <PaymentWrite>(); var existingRecord = _syncOrderRepository.RetreivePayment(transactionRecord.MonsterId); if (existingRecord == null) { // Create Monster Sync Record // var paymentRecord = new AcumaticaPayment(); paymentRecord.ShopifyTransactionMonsterId = transactionRecord.MonsterId; if (resultPayment == null) { // Workaround for Acumatica Bug // paymentRecord.AcumaticaRefNbr = AcumaticaSyncConstants.UnknownRefNbr; } else { paymentRecord.AcumaticaRefNbr = resultPayment.ReferenceNbr.value; } paymentRecord.AcumaticaDocType = payment.Type.value; paymentRecord.AcumaticaAmount = (decimal)payment.PaymentAmount.value; paymentRecord.AcumaticaAppliedToOrder = (decimal)payment.AmountAppliedToOrder; if (transactionRecord.IsRefund()) { if (transactionRecord.NeedManualApply()) { // Users are tasked with creating their Return for Credit // paymentRecord.NeedRelease = false; paymentRecord.NeedManualApply = true; } else { paymentRecord.NeedRelease = true; paymentRecord.NeedManualApply = false; } } if (!transactionRecord.IsRefund()) { paymentRecord.NeedRelease = true; paymentRecord.NeedManualApply = false; } paymentRecord.DateCreated = DateTime.UtcNow; paymentRecord.LastUpdated = DateTime.UtcNow; _syncOrderRepository.InsertPayment(paymentRecord); } else { existingRecord.AcumaticaAppliedToOrder = (decimal)payment.AmountAppliedToOrder; existingRecord.LastUpdated = DateTime.UtcNow; _syncOrderRepository.SaveChanges(); } _syncOrderRepository.UpdateShopifyOriginalPaymentNeedPut(transactionRecord.ShopifyOrderMonsterId, false); _syncOrderRepository.ResetOrderErrorCount(transactionRecord.ShopifyOrderId); }
public static long OrderId(this ShopifyTransaction transactionRecord) { return(transactionRecord.ShopifyOrder.ShopifyOrderId); }
public static bool ExistsInAcumatica(this ShopifyTransaction transaction) { return(transaction.AcumaticaPayment != null); }
public void InsertTransaction(ShopifyTransaction transaction) { Entities.ShopifyTransactions.Add(transaction); Entities.SaveChanges(); }
public static bool NeedManualApply(this ShopifyTransaction record) { return(!record.IsPureCancel || (record.HasShippingRefund() && record.ShopifyOrder.HasInvoicedShipments())); }
public static bool IsRefund(this ShopifyTransaction transaction) { return(transaction.ShopifyKind == TransactionKind.Refund); }
public static string DetectedNewShopifyTransaction(ShopifyTransaction transaction) { return($"Detected new {transaction.LogDescriptor()}"); }
public static string LogDescriptor(this ShopifyTransaction transaction) { return($"Shopify Transaction {transaction.ShopifyKind} " + $"({transaction.ShopifyTransactionId})"); }
public static bool IsReleased(this ShopifyTransaction transaction) { return(transaction.AcumaticaPayment != null && !transaction.AcumaticaPayment.NeedRelease); }