private bool consumeHold(IAssetCallback assetCallbacks, GloebitAPIWrapper.ITransactionAlert transactionAlerts, out string returnMsg)
        {
            if (this.canceled)
            {
                // Should never get a delayed consume after a cancel.  return false.
                returnMsg = "Consume: already canceled";
                return(false);
            }
            if (!this.enacted)
            {
                // Should never get a consume before we've enacted.  return false.
                returnMsg = "Consume: Not yet enacted";
                return(false);
            }
            if (this.consumed)
            {
                // already consumed. return true.
                returnMsg = "Cosume: Already consumed";
                return(true);
            }

            // First reception of consume for asset.
            // Send alert that CONSUME_GLOEBIT has completed
            transactionAlerts.AlertTransactionStageCompleted(this, GloebitAPI.TransactionStage.CONSUME_GLOEBIT, String.Empty);
            // Do specific consume functionality for the Asset / local transaction part
            this.consumed = assetCallbacks.processAssetConsumeHold(this, out returnMsg);
            if (this.consumed)
            {
                this.finishedTime = DateTime.UtcNow;
                GloebitTransactionData.Instance.Store(this);

                // send alert that CONSUME_ASSET has completed
                // TODO: should we pass the returnMsg instead of String.Empty, or is this always empty on success? -- investigate
                transactionAlerts.AlertTransactionStageCompleted(this, GloebitAPI.TransactionStage.CONSUME_ASSET, String.Empty);
                // transaction officially complete at this point
                transactionAlerts.AlertTransactionSucceeded(this);
            }
            return(this.consumed);
        }
        private bool cancelHold(IAssetCallback assetCallbacks, GloebitAPIWrapper.ITransactionAlert transactionAlerts, out string returnMsg)
        {
            if (this.consumed)
            {
                // Should never get a delayed cancel after a consume.  return false.
                returnMsg = "Cancel: already consumed";
                return(false);
            }
            if (!this.enacted)
            {
                // Hasn't enacted.  No work to undo.  return true.
                returnMsg = "Cancel: not yet enacted";
                // don't return here.  Still want to process cancel which will need to assess if enacted.
                //return true;
            }
            if (this.canceled)
            {
                // already canceled. return true.
                returnMsg = "Cancel: already canceled";
                return(true);
            }
            // First reception of cancel for asset.
            // Send alert that CANCEL_GLOEBIT has completed
            transactionAlerts.AlertTransactionStageCompleted(this, GloebitAPI.TransactionStage.CANCEL_GLOEBIT, String.Empty);
            // Do specific cancel functionality for the Asset / local transaction part
            this.canceled = assetCallbacks.processAssetCancelHold(this, out returnMsg); // Do I need to grab the money module for this?
            if (this.canceled)
            {
                this.finishedTime = DateTime.UtcNow;
                GloebitTransactionData.Instance.Store(this);

                // send alert that CANCEL_ASSET has completed
                // TODO: should we pass the returnMsg instead of String.Empty, or is this always empty on success? -- investigate
                transactionAlerts.AlertTransactionStageCompleted(this, GloebitAPI.TransactionStage.CANCEL_ASSET, String.Empty);
            }
            return(this.canceled);
        }
        private bool enactHold(IAssetCallback assetCallbacks, GloebitAPIWrapper.ITransactionAlert transactionAlerts, out string returnMsg)
        {
            if (this.canceled)
            {
                // getting a delayed enact sent before cancel.  return false.
                returnMsg = "Enact: already canceled";
                return(false);
            }
            if (this.consumed)
            {
                // getting a delayed enact sent before consume.  return true.
                returnMsg = "Enact: already consumed";
                return(true);
            }
            if (this.enacted)
            {
                // already enacted. return true.
                returnMsg = "Enact: already enacted";
                return(true);
            }
            // First reception of enact for asset.
            // Send alert that ENACT_GLOEBIT has completed
            transactionAlerts.AlertTransactionStageCompleted(this, GloebitAPI.TransactionStage.ENACT_GLOEBIT, String.Empty);
            // Do specific enact functionality for the Asset / local transaction part
            this.enacted = assetCallbacks.processAssetEnactHold(this, out returnMsg);

            // TODO: remove this after testing.
            m_log.InfoFormat("[GLOEBITMONEYMODULE] GloebitTransaction.enactHold: {0}", this.enacted);
            if (this.enacted)
            {
                m_log.InfoFormat("TransactionID: {0}", this.TransactionID);
                m_log.DebugFormat("PayerID: {0}", this.PayerID);
                m_log.DebugFormat("PayeeID: {0}", this.PayeeID);
                m_log.DebugFormat("PartID: {0}", this.PartID);
                m_log.DebugFormat("PartName: {0}", this.PartName);
                m_log.DebugFormat("CategoryID: {0}", this.CategoryID);
                m_log.DebugFormat("SaleType: {0}", this.SaleType);
                m_log.DebugFormat("Amount: {0}", this.Amount);
                m_log.DebugFormat("PayerEndingBalance: {0}", this.PayerEndingBalance);
                m_log.DebugFormat("enacted: {0}", this.enacted);
                m_log.DebugFormat("consumed: {0}", this.consumed);
                m_log.DebugFormat("canceled: {0}", this.canceled);
                m_log.DebugFormat("cTime: {0}", this.cTime);
                m_log.DebugFormat("enactedTime: {0}", this.enactedTime);
                m_log.DebugFormat("finishedTime: {0}", this.finishedTime);

                // TODO: Should we store and update the time even if it fails to track time enact attempted/failed?
                this.enactedTime = DateTime.UtcNow;
                GloebitTransactionData.Instance.Store(this);

                // send alert that ENACT_ASSET has completed
                // TODO: should we pass the returnMsg instead of String.Empty, or is this always empty on success? -- investigate
                transactionAlerts.AlertTransactionStageCompleted(this, GloebitAPI.TransactionStage.ENACT_ASSET, String.Empty);
            }
            else if (returnMsg != "pending")
            {
                // If pending, this is not a permanent failure and enact will be retried
                // not sure that platform should ever need to fail with pending, but being extra cautious
                // Local Asset Enact failed - Fire alert
                transactionAlerts.AlertTransactionFailed(this, GloebitAPI.TransactionStage.ENACT_ASSET, GloebitAPI.TransactionFailure.ENACTING_ASSET_FAILED, returnMsg, null);
            }
            return(this.enacted);
        }
        /**************************************************/
        /******* ASSET STATE MACHINE **********************/
        /**************************************************/

        public static bool ProcessStateRequest(string transactionIDstr, string stateRequested, IAssetCallback assetCallbacks, GloebitAPIWrapper.ITransactionAlert transactionAlerts, out string returnMsg)
        {
            bool result = false;

            // Retrieve asset
            GloebitTransaction myTxn = GloebitTransaction.Get(UUID.Parse(transactionIDstr));

            // If no matching transaction, return false
            // TODO: is this what we want to return?
            if (myTxn == null)
            {
                returnMsg = "No matching transaction found.";
                return(false);
            }

            // Attempt to avoid race conditions (not sure if even possible)
            bool alreadyProcessing = false;

            lock (s_pendingTransactionMap) {
                alreadyProcessing = s_pendingTransactionMap.ContainsKey(transactionIDstr);
                if (!alreadyProcessing)
                {
                    // add to race condition protection
                    s_pendingTransactionMap[transactionIDstr] = myTxn;
                }
            }
            if (alreadyProcessing)
            {
                returnMsg = "pending";  // DO NOT CHANGE --- this message needs to be returned to Gloebit to know it is a retryable error
                return(false);
            }

            // Call proper state processor
            switch (stateRequested)
            {
            case "enact":
                result = myTxn.enactHold(assetCallbacks, transactionAlerts, out returnMsg);
                break;

            case "consume":
                result = myTxn.consumeHold(assetCallbacks, transactionAlerts, out returnMsg);
                if (result)
                {
                    lock (s_transactionMap) {
                        s_transactionMap.Remove(transactionIDstr);
                    }
                }
                break;

            case "cancel":
                result = myTxn.cancelHold(assetCallbacks, transactionAlerts, out returnMsg);
                if (result)
                {
                    lock (s_transactionMap) {
                        s_transactionMap.Remove(transactionIDstr);
                    }
                }
                break;

            default:
                // no recognized state request
                returnMsg = "Unrecognized state request";
                result    = false;
                break;
            }

            // remove from race condition protection
            lock (s_pendingTransactionMap) {
                s_pendingTransactionMap.Remove(transactionIDstr);
            }
            return(result);
        }