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);
        }
Пример #3
0
        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"));
 }
Пример #14
0
 public static bool DoNotIgnore(this ShopifyTransaction transaction)
 {
     return(transaction.ShopifyGateway != Gateway.Manual &&
            transaction.ShopifyStatus == TransactionStatus.Success &&
            (transaction.IsPayment() || transaction.IsRefund()));
 }
Пример #15
0
 public static bool Ignore(this ShopifyTransaction transaction)
 {
     return(!transaction.DoNotIgnore());
 }
Пример #16
0
 public static string ReleasingTransaction(ShopifyTransaction transaction)
 {
     return($"Releasing Acumatica record for {transaction.LogDescriptor()}");
 }
Пример #17
0
 public static string CreateAcumaticaCustomerRefund(ShopifyTransaction transaction)
 {
     return($"Creating Acumatica Customer Refund from {transaction.LogDescriptor()}");
 }
Пример #18
0
 public static string UpdateAcumaticaPayment(ShopifyTransaction transaction)
 {
     return($"Updating Acumatica Payment from {transaction.LogDescriptor()}");
 }
Пример #19
0
 public static bool NeedsSync(this ShopifyTransaction transaction)
 {
     return(transaction.IsSyncableToPayment &&
            (!transaction.ExistsInAcumatica() || !transaction.IsReleased()));
 }
Пример #20
0
 // Monster Transaction record extensions
 //
 public static bool IsPayment(this ShopifyTransaction transaction)
 {
     return(transaction.ShopifyKind == TransactionKind.Capture ||
            transaction.ShopifyKind == TransactionKind.Sale);
 }
Пример #21
0
 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);
        }
Пример #23
0
 public static long OrderId(this ShopifyTransaction transactionRecord)
 {
     return(transactionRecord.ShopifyOrder.ShopifyOrderId);
 }
Пример #24
0
 public static bool ExistsInAcumatica(this ShopifyTransaction transaction)
 {
     return(transaction.AcumaticaPayment != null);
 }
Пример #25
0
 public void InsertTransaction(ShopifyTransaction transaction)
 {
     Entities.ShopifyTransactions.Add(transaction);
     Entities.SaveChanges();
 }
Пример #26
0
 public static bool NeedManualApply(this ShopifyTransaction record)
 {
     return(!record.IsPureCancel ||
            (record.HasShippingRefund() && record.ShopifyOrder.HasInvoicedShipments()));
 }
Пример #27
0
 public static bool IsRefund(this ShopifyTransaction transaction)
 {
     return(transaction.ShopifyKind == TransactionKind.Refund);
 }
Пример #28
0
 public static string DetectedNewShopifyTransaction(ShopifyTransaction transaction)
 {
     return($"Detected new {transaction.LogDescriptor()}");
 }
Пример #29
0
 public static string LogDescriptor(this ShopifyTransaction transaction)
 {
     return($"Shopify Transaction {transaction.ShopifyKind} " +
            $"({transaction.ShopifyTransactionId})");
 }
Пример #30
0
 public static bool IsReleased(this ShopifyTransaction transaction)
 {
     return(transaction.AcumaticaPayment != null && !transaction.AcumaticaPayment.NeedRelease);
 }