From 089c82f671db5f032d02b7e1778066a5a4c5ccc8 Mon Sep 17 00:00:00 2001 From: Mike Dickson Date: Thu, 3 Aug 2023 14:33:56 -0400 Subject: [PATCH] Rework Gloebit Subscriptions and Transactions to use the native database type (strings) for ID based entries instead of UUIDs. You can't hand a UUID to the database layer because it doesnt know how to convert it (or if it should). This means the MoneyModule is doing some conversions to/from UUIDs using ToString and UUID.Parse when persisting data but that actually correct unless we revise the database types to use a MySQL Guid (in which case we would have to convert to that). Bumped Build Version to 8615. --- OpenSim/Framework/VersionInfo.cs | 2 +- .../Gloebit/GloebitMoneyModule/GloebitAPI.cs | 17 +- .../GloebitMoneyModule/GloebitAPIWrapper.cs | 64 ++- .../GloebitMoneyModule/GloebitMoneyModule.cs | 429 +++++++++++++----- .../GloebitMoneyModule/GloebitSubscription.cs | 22 +- .../GloebitMoneyModule/GloebitTransaction.cs | 57 ++- 6 files changed, 450 insertions(+), 141 deletions(-) diff --git a/OpenSim/Framework/VersionInfo.cs b/OpenSim/Framework/VersionInfo.cs index 7cd61a4c25e..38bfeae6a4d 100644 --- a/OpenSim/Framework/VersionInfo.cs +++ b/OpenSim/Framework/VersionInfo.cs @@ -39,7 +39,7 @@ public class VersionInfo { public const string VersionNumber = "0.9.2.2"; public const string AssemblyVersionNumber = "0.9.2.2"; - public const string Release = "8603"; + public const string Release = "8615"; public const Flavour VERSION_FLAVOUR = Flavour.Dev; diff --git a/addon-modules/Gloebit/GloebitMoneyModule/GloebitAPI.cs b/addon-modules/Gloebit/GloebitMoneyModule/GloebitAPI.cs index 7109fb9265f..e7da369f190 100644 --- a/addon-modules/Gloebit/GloebitMoneyModule/GloebitAPI.cs +++ b/addon-modules/Gloebit/GloebitMoneyModule/GloebitAPI.cs @@ -836,8 +836,8 @@ public bool CreateSubscription(GloebitSubscription subscription, Uri baseURI) { m_log.DebugFormat("[GLOEBITMONEYMODULE] GloebitAPI.CreateSubscription about to BeginGetResponse"); // **** Asynchronously make web request **** // IAsyncResult r = request.BeginGetResponse(GloebitWebResponseCallback, - new GloebitRequestState(request, - delegate(OSDMap responseDataMap) { + new GloebitRequestState(request, delegate(OSDMap responseDataMap) + { m_log.DebugFormat("[GLOEBITMONEYMODULE] GloebitAPI.CreateSubscription response: {0}", responseDataMap); @@ -849,16 +849,21 @@ public bool CreateSubscription(GloebitSubscription subscription, Uri baseURI) { string status = responseDataMap["status"]; m_log.InfoFormat("[GLOEBITMONEYMODULE] GloebitAPI.CreateSubscription success: {0} reason: {1} status: {2}", success, reason, status); - if (success) { + if (success) + { string subscriptionIDStr = responseDataMap["id"]; bool enabled = (bool) responseDataMap["enabled"]; - subscription.SubscriptionID = UUID.Parse(subscriptionIDStr); + subscription.SubscriptionID = subscriptionIDStr; subscription.Enabled = enabled; GloebitSubscriptionData.Instance.UpdateFromGloebit(subscription); - if (status == "duplicate") { + + if (status == "duplicate") + { m_log.DebugFormat("[GLOEBITMONEYMODULE] GloebitAPI.CreateSubscription duplicate request to create subscription"); } - } else { + } + else + { switch(reason) { case "Unexpected DB insert integrity error. Please try again.": m_log.ErrorFormat("[GLOEBITMONEYMODULE] GloebitAPI.CreateSubscription failed from {0}", reason); diff --git a/addon-modules/Gloebit/GloebitMoneyModule/GloebitAPIWrapper.cs b/addon-modules/Gloebit/GloebitMoneyModule/GloebitAPIWrapper.cs index 3f2c47dece5..c741ecbd02e 100644 --- a/addon-modules/Gloebit/GloebitMoneyModule/GloebitAPIWrapper.cs +++ b/addon-modules/Gloebit/GloebitMoneyModule/GloebitAPIWrapper.cs @@ -433,9 +433,19 @@ public bool SubmitTransaction(GloebitTransaction txn, string description, OSDMap // TODO: Should we wrap TransactU2U or request.BeginGetResponse in Try/Catch? bool result = false; - if (u2u) { - result = m_api.TransactU2U(txn, description, descMap, GloebitUser.Get(m_key, txn.PayerID), GloebitUser.Get(m_key, txn.PayeeID), m_platformAccessors.resolveAgentEmail(txn.PayeeID), m_platformAccessors.GetBaseURI()); - } else { + if (u2u) + { + result = m_api.TransactU2U( + txn, + description, + descMap, + GloebitUser.Get(m_key, txn.PayerID), + GloebitUser.Get(m_key, txn.PayeeID), + m_platformAccessors.resolveAgentEmail(UUID.Parse(txn.PayeeID)), + m_platformAccessors.GetBaseURI()); + } + else + { result = m_api.Transact(txn, description, descMap, GloebitUser.Get(m_key, txn.PayerID), m_platformAccessors.GetBaseURI()); } @@ -477,18 +487,33 @@ public bool SubmitSyncTransaction(GloebitTransaction txn, string description, OS // TODO: Should we wrap TransactU2U or request.GetResponse in Try/Catch? GloebitAPI.TransactionStage stage = GloebitAPI.TransactionStage.BUILD; GloebitAPI.TransactionFailure failure = GloebitAPI.TransactionFailure.NONE; - bool result = m_api.TransactU2USync(txn, description, descMap, GloebitUser.Get(m_key, txn.PayerID), GloebitUser.Get(m_key, txn.PayeeID), m_platformAccessors.resolveAgentEmail(txn.PayeeID), m_platformAccessors.GetBaseURI(), out stage, out failure); - if (!result) { + bool result = m_api.TransactU2USync( + txn, + description, + descMap, + GloebitUser.Get(m_key, txn.PayerID), + GloebitUser.Get(m_key, txn.PayeeID), + m_platformAccessors.resolveAgentEmail(UUID.Parse(txn.PayeeID)), + m_platformAccessors.GetBaseURI(), + out stage, + out failure); + + if (!result) + { m_log.ErrorFormat("[GLOEBITMONEYMODULE] SubmitSyncTransaction failed in stage: {0} with failure: {1}", stage, failure); - if (stage == GloebitAPI.TransactionStage.SUBMIT) { + if (stage == GloebitAPI.TransactionStage.SUBMIT) + { // currently need to handle these errors here as the TransactU2UCallback is not called unless submission is successful and we receive a response m_transactionAlerts.AlertTransactionFailed(txn, GloebitAPI.TransactionStage.SUBMIT, failure, String.Empty, new OSDMap()); } - } else { + } + else + { // TODO: figure out how/where to send this alert in a synchronous transaction. Maybe it should always come from the API. // m_transactionAlerts.AlertTransactionStageCompleted(txn, GloebitAPI.TransactionStage.SUBMIT, String.Empty); } + return result; } @@ -746,8 +771,10 @@ public Hashtable transactionState_func(Hashtable requestData) { public UUID CreateSubscription(UUID appSubID, string subName, string subDesc) { m_log.InfoFormat("[GLOEBITMONEYMODULE] GloebitAPIWrapper.CreateSubscription for appSubID:{0}, subName:{1}, subDesc:{2}", appSubID, subName, subDesc); + // Validate that subName and subDesc are not empty or null as Gloebit requires both for a Subscription creation - if (String.IsNullOrEmpty(subName) || String.IsNullOrEmpty(subDesc)) { + if (String.IsNullOrEmpty(subName) || String.IsNullOrEmpty(subDesc)) + { m_log.WarnFormat("[GLOEBITMONEYMODULE] GloebitAPIWrapper.CreateSubscription - Can not create subscription because subscription name or description is blank - Name:{0} Description:{1}", subName, subDesc); //TODO: should this throw an exception? return UUID.Zero; @@ -755,7 +782,8 @@ public UUID CreateSubscription(UUID appSubID, string subName, string subDesc) // If no local appSubID provided, then generate one randomly bool idIsRandom = false; - if (appSubID == UUID.Zero) { + if (appSubID == UUID.Zero) + { // Create a transaction ID appSubID = UUID.Random(); idIsRandom = true; @@ -763,28 +791,38 @@ public UUID CreateSubscription(UUID appSubID, string subName, string subDesc) // Create a local subscription GloebitSubscription sub = null; + // Double check that a local subscription hasn't already been created sub = GloebitSubscription.Get(appSubID, m_key, m_url); - if(sub != null) { + + if (sub != null) + { m_log.WarnFormat("[GLOEBITMONEYMODULE] GloebitAPIWrapper.CreateSubscription found existing local sub for appSubID:{0}", appSubID); + // TODO: Should we check to see if there is a SubscriptionID on sub which would mean that this was already created on Gloebit as well? // For now, we'll assume that this could be an attempt to recreate after an issue and that Gloebit will return the Subscription ID // on a duplicate create request and that this will refresh that ID for the app. - if(idIsRandom) { + if(idIsRandom) + { m_log.ErrorFormat("[GLOEBITMONEYMODULE] GloebitAPIWrapper.CreateSubscription randomly generated appSubID:{0} conflicted with existing sub", appSubID); return UUID.Zero; } + // TODO: Should consider checking that name and desc match, but can't do so until we verify that OpenSim integration doesn't need adjustment. // Can't recall if the UUID of an object is changed when the name or desc are updated. If not, we need to handle that in GMM first. } - if(sub == null) { + + if (sub == null) + { m_log.DebugFormat("[GLOEBITMONEYMODULE] GloebitAPIWrapper.CreateSubscription - creating local subscription for {0}", subName); + // Create local sub in cache and db - sub = GloebitSubscription.Init(appSubID, m_key, m_url.ToString(), subName, subDesc); + sub = GloebitSubscription.Init(appSubID.ToString(), m_key, m_url.ToString(), subName, subDesc); } // Ask Gloebit to create this subscription on the server m_api.CreateSubscription(sub, m_platformAccessors.GetBaseURI()); + // TODO: should we handle false return from api call? return appSubID; } diff --git a/addon-modules/Gloebit/GloebitMoneyModule/GloebitMoneyModule.cs b/addon-modules/Gloebit/GloebitMoneyModule/GloebitMoneyModule.cs index af48cb8e094..8abd4c19b60 100644 --- a/addon-modules/Gloebit/GloebitMoneyModule/GloebitMoneyModule.cs +++ b/addon-modules/Gloebit/GloebitMoneyModule/GloebitMoneyModule.cs @@ -959,7 +959,9 @@ public bool ObjectGiveMoney(UUID objectID, UUID fromID, UUID toID, int amount, U // Check subscription table. If not exists, send create call to Gloebit m_log.DebugFormat("[GLOEBITMONEYMODULE] ObjectGiveMoney - looking for local subscription"); GloebitSubscription sub = GloebitSubscription.Get(objectID, m_key, m_apiUrl); - if (sub == null || sub.SubscriptionID == UUID.Zero) { + + if (sub == null || (sub.SubscriptionID == UUID.Zero.ToString())) + { // null means we haven't attempted creation yet // SubscriptionID == UUID.Zero means we have a local sub, but haven't created this on Gloebit through API. Likely a previous creation request failed // In either case, we need to create a subscription on Gloebit before proceeding @@ -998,7 +1000,8 @@ public bool ObjectGiveMoney(UUID objectID, UUID fromID, UUID toID, int amount, U // Check that user has authed Gloebit and token is on file. GloebitUser payerUser = GloebitUser.Get(m_key, fromID); - if (payerUser != null && String.IsNullOrEmpty(payerUser.GloebitToken)) { + if (payerUser != null && String.IsNullOrEmpty(payerUser.GloebitToken)) + { // send message asking to auth Gloebit. alertUsersSubscriptionTransactionFailedForGloebitAuthorization(fromID, toID, amount, sub); reason = "Owner has not authorized this app with Gloebit."; @@ -1008,13 +1011,15 @@ public bool ObjectGiveMoney(UUID objectID, UUID fromID, UUID toID, int amount, U // Checks done. Ready to build and submit transaction. OSDMap descMap = buildOpenSimTransactionDescMap(regionname, regionID, "ObjectGiveMoney", part); - + UUID subId = UUID.Parse(sub.SubscriptionID); + GloebitTransaction txn = buildTransaction(transactionID: txnID, transactionType: TransactionType.OBJECT_PAYS_USER, - payerID: fromID, payeeID: toID, amount: amount, subscriptionID: sub.SubscriptionID, + payerID: fromID, payeeID: toID, amount: amount, subscriptionID: subId, partID: objectID, partName: part.Name, partDescription: part.Description, categoryID: UUID.Zero, localID: 0, saleType: 0); - if (txn == null) { + if (txn == null) + { // build failed, likely due to a reused transactionID. Shouldn't happen, but possible in ObjectGiveMoney. IClientAPI payerClient = LocateClientObject(fromID); alertUsersTransactionPreparationFailure(TransactionType.OBJECT_PAYS_USER, TransactionPrecheckFailure.EXISTING_TRANSACTION_ID, payerClient); @@ -1559,12 +1564,45 @@ private GloebitTransaction buildTransaction(UUID transactionID, TransactionType partDescription = String.Empty; } - GloebitTransaction txn = GloebitTransaction.Create(transactionID, payerID, payerName, payeeID, payeeName, amount, (int)transactionType, transactionTypeString, isSubscriptionDebit, subscriptionID, partID, partName, partDescription, categoryID, localID, saleType); + GloebitTransaction txn = GloebitTransaction.Create( + transactionID.ToString(), + payerID.ToString(), + payerName, + payeeID.ToString(), + payeeName, + amount, + (int)transactionType, + transactionTypeString, + isSubscriptionDebit, + subscriptionID.ToString(), + partID.ToString(), + partName, + partDescription, + categoryID.ToString(), + localID, + saleType); - if (txn == null && isRandomID) { + if (txn == null && isRandomID) + { // Try one more time in case the incredibly unlikely event of a UUID.Random overlap has occurred. transactionID = UUID.Random(); - txn = GloebitTransaction.Create(transactionID, payerID, payerName, payeeID, payeeName, amount, (int)transactionType, transactionTypeString, isSubscriptionDebit, subscriptionID, partID, partName, partDescription, categoryID, localID, saleType); + txn = GloebitTransaction.Create( + transactionID.ToString(), + payerID.ToString(), + payerName, + payeeID.ToString(), + payeeName, + amount, + (int)transactionType, + transactionTypeString, + isSubscriptionDebit, + subscriptionID.ToString(), + partID.ToString(), + partName, + partDescription, + categoryID.ToString(), + localID, + saleType); } return txn; @@ -1701,18 +1739,23 @@ public bool processAssetEnactHold(GloebitTransaction txn, out string returnMsg) return false; } break; + case TransactionType.USER_PAYS_USER: // 5001 - OnMoneyTransfer - Pay User // nothing to enact break; + case TransactionType.USER_PAYS_OBJECT: // 5008 - OnMoneyTransfer - Pay Object // need to alert the object that it has been paid. ObjectPaid handleObjectPaid = OnObjectPaid; - if(handleObjectPaid != null) { - handleObjectPaid(txn.PartID, txn.PayerID, txn.Amount); + if(handleObjectPaid != null) + { + handleObjectPaid(UUID.Parse(txn.PartID), UUID.Parse(txn.PayerID), txn.Amount); // This doesn't provide a return or ability to query state, so we assume success - } else { + } + else + { // This really shouldn't happen, as it would mean that the OpenSim region is not properly set up // However, we won't fail here as expectation is unclear // We have received this when a sim has another active money module which didn't respect the config and tried to enable on @@ -1720,27 +1763,34 @@ public bool processAssetEnactHold(GloebitTransaction txn, out string returnMsg) m_log.ErrorFormat("[GLOEBITMONEYMODULE].processAssetEnactHold - IMoneyModule OnObjectPaid event not properly subscribed. Object payment may have failed."); } break; + case TransactionType.OBJECT_PAYS_USER: // 5009 - ObjectGiveMoney // nothing to enact. break; + case TransactionType.USER_BUYS_LAND: // 5002 - OnLandBuy // Need to transfer land bool transferred = transferLand(txn, out returnMsg); - if (!transferred) { + + if (!transferred) + { // Local Asset Enact failed - set returnMsg returnMsg = String.Format("Asset enact failed: {0}", returnMsg); // remove land asset from map since cancel will not get called // TODO: should we do this here, or // - adjust ProcessAssetCancelHold to always be called and check state to see if something needs to be undone? // - do this from AlertTransactionFailed() - lock(m_landAssetMap) { - m_landAssetMap.Remove(txn.TransactionID); + lock(m_landAssetMap) + { + m_landAssetMap.Remove(UUID.Parse(txn.TransactionID)); } + return false; } break; + case TransactionType.USER_BUYS_LANDPASS: // 5006 - OnParcelBuyPass prior to 0.9.1; MoveMoney after; // NOTE: new flow: delivery is outside of our system. Nothing to enact. @@ -1753,18 +1803,22 @@ public bool processAssetEnactHold(GloebitTransaction txn, out string returnMsg) } } break; + case TransactionType.FEE_GROUP_CREATION: // 1002 - ApplyCharge // Nothing to do since the group was already created. Ideally, this would create group or finalize creation. break; + case TransactionType.FEE_UPLOAD_ASSET: // 1101 - ApplyUploadCharge // Nothing to do since the asset was already uploaded. Ideally, this would upload asset or finalize upload. break; + case TransactionType.FEE_CLASSIFIED_AD: // 1103 - ApplyCharge // Nothing to do since the ad was already placed. Ideally, this would create ad finalize ad. break; + default: m_log.ErrorFormat("[GLOEBITMONEYMODULE] processAssetEnactHold called on unknown transaction type: {0}", txn.TransactionType); // TODO: should we throw an exception? return null? just continue? @@ -1778,54 +1832,65 @@ public bool processAssetEnactHold(GloebitTransaction txn, out string returnMsg) return true; } - public bool processAssetConsumeHold(GloebitTransaction txn, out string returnMsg) { - + public bool processAssetConsumeHold(GloebitTransaction txn, out string returnMsg) + { m_log.InfoFormat("[GLOEBITMONEYMODULE].processAssetConsumeHold SUCCESS - transaction complete"); // If we've gotten this call, then the Gloebit components have enacted successfully // all transferred funds have been committed. // ITransactionAlert.AlertTransactionStageCompleted for CONSUME_GLOEBIT just fired - switch ((TransactionType)txn.TransactionType) { + switch ((TransactionType)txn.TransactionType) + { case TransactionType.USER_BUYS_OBJECT: // 5000 - ObjectBuy // nothing to finalize break; + case TransactionType.USER_PAYS_USER: // 5001 - OnMoneyTransfer - Pay User // nothing to finalize break; + case TransactionType.USER_PAYS_OBJECT: // 5008 - OnMoneyTransfer - Pay Object // nothing to finalize break; + case TransactionType.OBJECT_PAYS_USER: // 5009 - ObjectGiveMoney // nothing to finalize break; + case TransactionType.USER_BUYS_LAND: // 5002 - OnLandBuy // Remove land asset from map - lock(m_landAssetMap) { - m_landAssetMap.Remove(txn.TransactionID); + lock(m_landAssetMap) + { + m_landAssetMap.Remove(UUID.Parse(txn.TransactionID)); } break; + case TransactionType.USER_BUYS_LANDPASS: // 5006 - OnParcelBuyPass pre 0.9.1; MoveMoney after; // nothing to finalize break; + case TransactionType.FEE_GROUP_CREATION: // 1002 - ApplyCharge // Nothing to do since the group was already created. Ideally, this would finalize creation. break; + case TransactionType.FEE_UPLOAD_ASSET: // 1101 - ApplyUploadCharge // Nothing to do since the asset was already uploaded. Ideally, this would finalize upload. break; + case TransactionType.FEE_CLASSIFIED_AD: // 1103 - ApplyCharge // Nothing to do since the ad was already placed. Ideally, this would finalize ad. break; + default: m_log.ErrorFormat("[GLOEBITMONEYMODULE] processAssetConsumeHold called on unknown transaction type: {0}", txn.TransactionType); // TODO: should we throw an exception? return null? just continue? @@ -1846,43 +1911,53 @@ public bool processAssetCancelHold(GloebitTransaction txn, out string returnMsg) { m_log.InfoFormat("[GLOEBITMONEYMODULE].processAssetCancelHold SUCCESS - transaction rolled back"); // nothing to cancel - either enact of asset failed or was never called if we're here. - switch ((TransactionType)txn.TransactionType) { + switch ((TransactionType)txn.TransactionType) + { case TransactionType.USER_BUYS_OBJECT: // 5000 - ObjectBuy // no mechanism for reversing delivery break; + case TransactionType.USER_PAYS_USER: // 5001 - OnMoneyTransfer - Pay User // nothing to cancel break; + case TransactionType.USER_PAYS_OBJECT: // 5008 - OnMoneyTransfer - Pay Object // no mechanism for notifying object break; + case TransactionType.OBJECT_PAYS_USER: // 5009 - ObjectGiveMoney // nothing to cancel break; + case TransactionType.USER_BUYS_LAND: // 5002 - OnLandBuy // nothing to cancel, if we're here, it is because land was not transferred successfully. break; + case TransactionType.USER_BUYS_LANDPASS: // 5006 - OnParcelBuyPass pre 0.9.1; MoveMoney after; // nothing to cancel, if we're here, it is because landpass was not granted successfully. break; + case TransactionType.FEE_GROUP_CREATION: // 1002 - ApplyCharge // TODO: can we delete the group? break; + case TransactionType.FEE_UPLOAD_ASSET: // 1101 - ApplyUploadCharge // TODO: can we delete the asset? break; + case TransactionType.FEE_CLASSIFIED_AD: // 1103 - ApplyCharge // TODO: can we delete the ad? break; + default: m_log.ErrorFormat("[GLOEBITMONEYMODULE] processAssetCancelHold called on unknown transaction type: {0}", txn.TransactionType); // TODO: should we throw an exception? return null? just continue? @@ -1920,9 +1995,12 @@ public void AlertTransactionFailed(GloebitTransaction txn, GloebitAPI.Transactio // Handle any functional/flow requirements of any failures. // Since OpenSim uses subscriptions in an odd way and doesn't yet store subscription authorizations, those need special handling here. - if (stage == GloebitAPI.TransactionStage.VALIDATE) { - IClientAPI payerClient = LocateClientObject(txn.PayerID); - switch(failure) { + if (stage == GloebitAPI.TransactionStage.VALIDATE) + { + IClientAPI payerClient = LocateClientObject(UUID.Parse(txn.PayerID)); + + switch(failure) + { case GloebitAPI.TransactionFailure.SUBSCRIPTION_AUTH_NOT_FOUND: /* No sub_auth has been created for this user for this subscription */ m_log.InfoFormat("[GLOEBITMONEYMODULE].AlertTransactionFailed No subscription authorization in place. Asking payer to auth. transactionID:{0}, app-subscription-id:{1} PayerID:{2} PayerName:{3}", txn.TransactionID, txn.SubscriptionID, txn.PayerID, txn.PayerName); // TODO: Should we store auths so we know if we need to create it or just to ask user to auth after failed transaction? @@ -1930,35 +2008,75 @@ public void AlertTransactionFailed(GloebitTransaction txn, GloebitAPI.Transactio // Ask user if they would like to authorize // Don't call CreateSubscriptionAuthorization unless they do. If this is fraud, the user will not want to see a pending auth on Gloebit. - if (payerClient != null) { - Dialog.Send(new CreateSubscriptionAuthorizationDialog(payerClient, txn.PayerID, txn.PayerName, txn.PartID, txn.PartName, txn.PartDescription, txn.TransactionID, txn.PayeeID, txn.PayeeName, txn.Amount, txn.SubscriptionID, m_apiW, BaseURI)); - } else { + if (payerClient != null) + { + Dialog.Send(new CreateSubscriptionAuthorizationDialog( + payerClient, + UUID.Parse(txn.PayerID), + txn.PayerName, + UUID.Parse(txn.PartID), + txn.PartName, + txn.PartDescription, + UUID.Parse(txn.TransactionID), + UUID.Parse(txn.PayeeID), + txn.PayeeName, + txn.Amount, + UUID.Parse(txn.SubscriptionID), + m_apiW, + BaseURI)); + } + else + { // TODO: does the message eventually make it if the user is offline? Is there a way to send a Dialog to a user the next time they log in? // Should we just create the subscription_auth in this case? // TODO: This is an issue with restoring of auto-debit scripted objects with new UUID. If owner isn't online, they won't get asked to re-auth on failure. } break; + case GloebitAPI.TransactionFailure.SUBSCRIPTION_AUTH_PENDING: /* User has not yet approved or declined the authorization for this subscription */ // User has been asked and chose to auth already. // Subscription-authorization has already been created. // User has not yet responded to that request, so send a dialog again to ask for auth and allow reporting of fraud. m_log.InfoFormat("[GLOEBITMONEYMODULE].AlertTransactionFailed subscription authorization pending. Asking payer to auth again. transactionID:{0}, app-subscription-id:{1} PayerID:{2} PayerName:{3}", txn.TransactionID, txn.SubscriptionID, txn.PayerID, txn.PayerName); + // Send request to user again - if (payerClient != null) { + if (payerClient != null) + { UUID subscriptionAuthID = UUID.Zero; // pull from extraData because not in transaction - if (extraData.ContainsKey("subscription-authorization-id")) { + if (extraData.ContainsKey("subscription-authorization-id")) + { subscriptionAuthID = UUID.Parse(extraData["subscription-authorization-id"]); - } else { + } + else + { m_log.Error("[GLOEBITMONEYMODULE].AlertTransactionFailed subscription-authorization-id expected, but missing from extraData"); } - Dialog.Send(new PendingSubscriptionAuthorizationDialog(payerClient, txn.PayerID, txn.PayerName, txn.PartID, txn.PartName, txn.PartDescription, txn.TransactionID, txn.PayeeID, txn.PayeeName, txn.Amount, txn.SubscriptionID, subscriptionAuthID, m_apiW, BaseURI)); - } else { + + Dialog.Send(new PendingSubscriptionAuthorizationDialog( + payerClient, + UUID.Parse(txn.PayerID), + txn.PayerName, + UUID.Parse(txn.PartID), + txn.PartName, + txn.PartDescription, + UUID.Parse(txn.TransactionID), + UUID.Parse(txn.PayeeID), + txn.PayeeName, + txn.Amount, + UUID.Parse(txn.SubscriptionID), + subscriptionAuthID, + m_apiW, + BaseURI)); + } + else + { // TODO: does the message eventually make it if the user is offline? Is there a way to send a Dialog to a user the next time they log in? // Should we just create the subscription_auth in this case? } break; + case GloebitAPI.TransactionFailure.SUBSCRIPTION_AUTH_DECLINED: /* User has declined the authorization for this subscription */ m_log.InfoFormat("[GLOEBITMONEYMODULE].AlertTransactionFailed subscription authorization declined. Asking payer to auth again. transactionID:{0}, app-subscription-id:{1} PayerID:{2} PayerName:{3}", txn.TransactionID, txn.SubscriptionID, txn.PayerID, txn.PayerName); @@ -1966,13 +2084,17 @@ public void AlertTransactionFailed(GloebitTransaction txn, GloebitAPI.Transactio // Send dialog asking user to auth or report --- needs different message. string subscriptionAuthIDStr = String.Empty; // pull from extraData because not in transaction - if (extraData.ContainsKey("subscription-authorization-id")) { + if (extraData.ContainsKey("subscription-authorization-id")) + { subscriptionAuthIDStr = extraData["subscription-authorization-id"]; - } else { + } + else + { m_log.Error("[GLOEBITMONEYMODULE].AlertTransactionFailed subscription-authorization-id expected, but missing from extraData"); } + GloebitSubscription sub = GloebitSubscription.GetBySubscriptionID(txn.SubscriptionID.ToString(), m_apiW.m_url.ToString()); - m_apiW.AuthorizeSubscription(txn.PayerID, subscriptionAuthIDStr, sub, true); + m_apiW.AuthorizeSubscription(UUID.Parse(txn.PayerID), subscriptionAuthIDStr, sub, true); break; } } @@ -2129,6 +2251,7 @@ public void AlertUserAuthorized(GloebitUser user, UUID agentID, double balance, public void AlertSubscriptionCreated(GloebitSubscription subscription) { m_log.InfoFormat ("[GLOEBITMONEYMODULE].AlertSubscriptionCreated appSubID:{0} GloebitSubID:{1}", subscription.ObjectID, subscription.SubscriptionID); + // TODO: Do we need to message any client? // OpenSim needs to handle this specially because we are using subscriptions for auto-debit and therefore creating @@ -2138,24 +2261,34 @@ public void AlertSubscriptionCreated(GloebitSubscription subscription) // Look at authWaitingForSubMap - if auth waiting, start that flow. IClientAPI client = null; bool foundClient = false; - lock (m_authWaitingForSubMap) { - foundClient = m_authWaitingForSubMap.TryGetValue (subscription.ObjectID, out client); - if (foundClient) { - m_authWaitingForSubMap.Remove (subscription.ObjectID); + UUID objectId = UUID.Parse(subscription.ObjectID); + + lock (m_authWaitingForSubMap) + { + foundClient = m_authWaitingForSubMap.TryGetValue (objectId, out client); + if (foundClient) + { + m_authWaitingForSubMap.Remove (objectId); } } + // TODO: Not sending dialog. Just creating auth. Should we separate flow from transaction failure and deliver dialog? - if (foundClient) { + if (foundClient) + { m_apiW.AuthorizeSubscription(client.AgentId, String.Empty, subscription, false); } } public void AlertSubscriptionCreationFailed(GloebitSubscription subscription) { - m_log.InfoFormat ("[GLOEBITMONEYMODULE].AlertSubscriptionCreationFailed appSubID:{0}", subscription.ObjectID); + UUID objectId = UUID.Parse(subscription.ObjectID); + + m_log.InfoFormat ("[GLOEBITMONEYMODULE].AlertSubscriptionCreationFailed appSubID:{0}", objectId); + // If we added to this map. remove so we're not leaking memory in failure cases. - lock(m_authWaitingForSubMap) { - m_authWaitingForSubMap.Remove(subscription.ObjectID); + lock(m_authWaitingForSubMap) + { + m_authWaitingForSubMap.Remove(objectId); } } @@ -2702,11 +2835,13 @@ private void ObjectBuy(IClientAPI remoteClient, UUID agentID, m_log.InfoFormat("[GLOEBITMONEYMODULE] ObjectBuy Transaction queued {0}", txn.TransactionID.ToString()); } - private bool deliverObject(GloebitTransaction txn, out string returnMsg) { + private bool deliverObject(GloebitTransaction txn, out string returnMsg) + { // TODO: this could fail if user logs off right after submission. Is this what we want? // TODO: This basically always fails when you crash opensim and recover during a transaction. Is this what we want? - IClientAPI buyerClient = LocateClientObject(txn.PayerID); - if (buyerClient == null) { + IClientAPI buyerClient = LocateClientObject(UUID.Parse(txn.PayerID)); + if (buyerClient == null) + { m_log.ErrorFormat("[GLOEBITMONEYMODULE].deliverObject FAILED to locate buyer agent. Agent may have logged out prior to delivery."); returnMsg = "Can't locate buyer."; return false; @@ -2714,9 +2849,11 @@ private bool deliverObject(GloebitTransaction txn, out string returnMsg) { // Retrieve BuySellModule used for delivering this asset Scene s = LocateSceneClientIn(buyerClient.AgentId); + // TODO: we should be locating the scene the part is in instead of the agent in case the agent moved (to a non Gloebit region) -- maybe store scene ID in asset -- see processLandBuy? IBuySellModule module = s.RequestModuleInterface(); - if (module == null) { + if (module == null) + { m_log.ErrorFormat("[GLOEBITMONEYMODULE].deliverObject FAILED to access to IBuySellModule"); returnMsg = "Can't access IBuySellModule."; return false; @@ -2724,24 +2861,33 @@ private bool deliverObject(GloebitTransaction txn, out string returnMsg) { // Rebuild delivery params from Asset and attempt delivery of object uint localID; - if (!txn.TryGetLocalID(out localID)) { + if (!txn.TryGetLocalID(out localID)) + { SceneObjectPart part; - if (s.TryGetSceneObjectPart(txn.PartID, out part)) { + if (s.TryGetSceneObjectPart(UUID.Parse(txn.PartID), out part)) + { localID = part.LocalId; - } else { + } + else + { m_log.ErrorFormat("[GLOEBITMONEYMODULE].deliverObject FAILED to deliver asset - could not retrieve SceneObjectPart from ID"); returnMsg = "Failed to deliver asset. Could not retrieve SceneObjectPart from ID."; return false; } } - bool success = module.BuyObject(buyerClient, txn.CategoryID, localID, (byte)txn.SaleType, txn.Amount); - if (!success) { + + bool success = module.BuyObject(buyerClient, UUID.Parse(txn.CategoryID), localID, (byte)txn.SaleType, txn.Amount); + if (!success) + { m_log.ErrorFormat("[GLOEBITMONEYMODULE].deliverObject FAILED to deliver asset"); returnMsg = "IBuySellModule.BuyObject failed delivery attempt."; - } else { + } + else + { m_log.InfoFormat("[GLOEBITMONEYMODULE].deliverObject SUCCESS - delivered asset"); returnMsg = "object delivery succeeded"; } + return success; } @@ -2855,8 +3001,9 @@ private bool deliverLandPass(GloebitTransaction txn, out string returnMsg) { return false; } int parcelLocalID = (int)localID; - UUID agentID = txn.PayerID; - UUID regionID = txn.CategoryID; + UUID agentID = UUID.Parse(txn.PayerID); + UUID regionID = UUID.Parse(txn.CategoryID); + Scene s = GetSceneByUUID(regionID); if (s == null) { // Should probably never happen @@ -2880,11 +3027,13 @@ private bool deliverLandPass(GloebitTransaction txn, out string returnMsg) { return false; } // Make sure owner hasn't changed - if ((parcel.LandData.OwnerID != txn.PayeeID)) { + if (parcel.LandData.OwnerID != UUID.Parse(txn.PayeeID)) + { m_log.WarnFormat("[GLOEBITMONEYMODULE] AgentID:{0} attempted to buy a pass on parcel:{1} which changed ownership before transaction completed.", agentID, parcel.LandData.GlobalID); returnMsg = "Parcel ownership has changed. Please retry purchase."; return false; } + // Make sure price hasn't changed if(parcel.LandData.PassPrice != txn.Amount) { @@ -3112,27 +3261,36 @@ private void ProcessLandBuy(Object osender, EventManager.LandBuyArgs e) // Add region UUID and LandBuyArgs to dictionary accessible for callback and wait for callback // Needs to happen before we submit because C# can delay wakeup for this synchronous call and // the enact could be received before we know if the submission succeeded. - lock(m_landAssetMap) { - m_landAssetMap[txn.TransactionID] = new Object[2]{s.RegionInfo.originRegionID, e}; + lock(m_landAssetMap) + { + m_landAssetMap[UUID.Parse(txn.TransactionID)] = new Object[2]{s.RegionInfo.originRegionID, e}; } bool submission_result = m_apiW.SubmitTransaction(txn, description, descMap, true); // See GloebitAPIWrapper.TransactU2UCompleted and helper messaging functions for error messaging on failure - no action required. // See ProcessAssetEnactHold for proceeding with txn on success. - if (!submission_result) { + if (!submission_result) + { // payment failed. message user and halt attempt to transfer land //// TODO: message error - lock(m_landAssetMap) { - m_landAssetMap.Remove(txn.TransactionID); + lock(m_landAssetMap) + { + m_landAssetMap.Remove(UUID.Parse(txn.TransactionID)); } + return; } } - } else { /* economy is validated. Second time through or 0G txn */ - if (e.parcelPrice == 0) { + } + else + { /* economy is validated. Second time through or 0G txn */ + if (e.parcelPrice == 0) + { // Free land. No economic part. e.amountDebited = 0; - } else { + } + else + { // Second time through. Completing a transaction we launched the first time through. // if e.landValidated, land has or will transfer. // We can't verify here because the land process may happen after economy, so do nothing here. @@ -3141,16 +3299,20 @@ private void ProcessLandBuy(Object osender, EventManager.LandBuyArgs e) } } - private bool transferLand(GloebitTransaction txn, out string returnMsg) { + private bool transferLand(GloebitTransaction txn, out string returnMsg) + { //// retrieve LandBuyArgs from assetMap - bool foundArgs = m_landAssetMap.ContainsKey(txn.TransactionID); - if (!foundArgs) { + bool foundArgs = m_landAssetMap.ContainsKey(UUID.Parse(txn.TransactionID)); + if (!foundArgs) + { returnMsg = "Could not locate land asset for transaction."; return false; } - Object[] landBuyAsset = m_landAssetMap[txn.TransactionID]; + + Object[] landBuyAsset = m_landAssetMap[UUID.Parse(txn.TransactionID)]; UUID regionID = (UUID)landBuyAsset[0]; EventManager.LandBuyArgs e = (EventManager.LandBuyArgs)landBuyAsset[1]; + // Set land buy args that need setting // TODO: should we be creating a new LandBuyArgs and copying the data instead in case anything else subscribes to the LandBuy events and mucked with these? e.economyValidated = true; @@ -3158,12 +3320,14 @@ private bool transferLand(GloebitTransaction txn, out string returnMsg) { e.landValidated = false; //// retrieve client - IClientAPI sender = LocateClientObject(txn.PayerID); - if (sender == null) { + IClientAPI sender = LocateClientObject(UUID.Parse(txn.PayerID)); + if (sender == null) + { // TODO: Does it matter if we can't locate the client? Does this break if sender is null? returnMsg = "Could not locate buyer."; return false; } + //// retrieve scene Scene s = GetSceneByUUID(regionID); if (s == null) { @@ -3173,12 +3337,16 @@ private bool transferLand(GloebitTransaction txn, out string returnMsg) { //// Trigger validate s.EventManager.TriggerValidateLandBuy(sender, e); + // Check land validation - if (!e.landValidated) { + if (!e.landValidated) + { returnMsg = "Land validation failed."; return false; } - if (e.parcelOwnerID != txn.PayeeID) { + + if (e.parcelOwnerID != UUID.Parse(txn.PayeeID)) + { returnMsg = "Parcel owner changed."; return false; } @@ -3188,7 +3356,8 @@ private bool transferLand(GloebitTransaction txn, out string returnMsg) { // Verify that land transferred successfully - sad that we have to check this. ILandObject parcel = s.LandChannel.GetLandObject(e.parcelLocalID); UUID newOwnerID = parcel.LandData.OwnerID; - if (newOwnerID != txn.PayerID) { + if (newOwnerID != UUID.Parse(txn.PayerID)) + { // This should only happen if due to race condition. Unclear if possible or result. returnMsg = "Land transfer failed. Owner is not buyer."; return false; @@ -3210,7 +3379,8 @@ private bool transferLand(GloebitTransaction txn, out string returnMsg) { /// SceneObjectPart UUID of the item the client is granting permissions on /// UUID of the TaskInventoryItem associated with this SceneObjectPart which handles permissions /// Bitmap of the permissions which are being granted - private void handleScriptAnswer(IClientAPI client, UUID objectID, UUID itemID, int answer) { + private void handleScriptAnswer(IClientAPI client, UUID objectID, UUID itemID, int answer) + { // m_log.InfoFormat("[GLOEBITMONEYMODULE] handleScriptAnswer for client:{0} with objectID:{1}, itemID:{2}, answer:{3}", client.AgentId, objectID, itemID, answer); if ((answer & ScriptBaseClass.PERMISSION_DEBIT) == 0) @@ -3227,16 +3397,22 @@ private void handleScriptAnswer(IClientAPI client, UUID objectID, UUID itemID, i // Check subscription table. If not exists, send create call to Gloebit. m_log.DebugFormat("[GLOEBITMONEYMODULE] handleScriptAnswer - looking for local subscription"); GloebitSubscription sub = GloebitSubscription.Get(objectID, m_key, m_apiUrl); - if (sub == null || sub.SubscriptionID == UUID.Zero) { + + if (sub == null || (sub.SubscriptionID == UUID.Zero.ToString())) + { // Don't create unless the object has a name and description // Make sure Name and Description are not null to avoid pgsql issue with storing null values // Make sure neither are empty as they are required by Gloebit to create a subscription SceneObjectPart part = findPrim(objectID); - if (part == null) { + + if (part == null) + { m_log.ErrorFormat("[GLOEBITMONEYMODULE] handleScriptAnswer - Could not find object - ID:{0}", objectID); return; } - if (String.IsNullOrEmpty(part.Name) || String.IsNullOrEmpty(part.Description)) { + + if (String.IsNullOrEmpty(part.Name) || String.IsNullOrEmpty(part.Description)) + { m_log.WarnFormat("[GLOEBITMONEYMODULE] handleScriptAnswer - Can not create local subscription because part name or description is blank - Name:{0} Description:{1}", part.Name, part.Description); // Send message to the owner to let them know they must edit the object and add a name and description String imMsg = String.Format("Object with auto-debit script is missing a name or description. Name and description are required by Gloebit in order to create a subscription for this auto-debit object. Please enter a name and description in the object. Current values are Name:[{0}] and Description:[{1}].", part.Name, part.Description); @@ -3245,7 +3421,8 @@ private void handleScriptAnswer(IClientAPI client, UUID objectID, UUID itemID, i } // Add this user to map of waiting for sub to create auth. - lock(m_authWaitingForSubMap) { + lock(m_authWaitingForSubMap) + { m_authWaitingForSubMap[objectID] = client; } @@ -3547,7 +3724,7 @@ private void SendNewSessionMessaging(IClientAPI client, GloebitUser user) { /// private void sendTxnStatusToClient(GloebitTransaction txn, IClientAPI client, string baseStatus, bool showTxnDetails, bool showTxnID) { - sendTxnStatusToClient(txn, client, baseStatus, showTxnDetails, showTxnID, txn.PayerID); + sendTxnStatusToClient(txn, client, baseStatus, showTxnDetails, showTxnID, UUID.Parse(txn.PayerID)); } /// @@ -3868,19 +4045,22 @@ private void alertUsersTransactionBegun(GloebitTransaction txn, string descripti } // Alert payer - IClientAPI payerClient = LocateClientObject(txn.PayerID); + IClientAPI payerClient = LocateClientObject(UUID.Parse(txn.PayerID)); + // TODO: remove description once in txn and managed in sendTxnStatusToClient //string baseStatus = String.Format("Submitting transaction request...\n {0}", actionStr); string baseStatus = String.Format("Submitting transaction request...\n {0}\nDescription: {1}", actionStr, description); sendTxnStatusToClient(txn, payerClient, baseStatus, showDetailsWithTxnBegun, showIDWithTxnBegun); // If necessary, alert Payee - if (messagePayee && (txn.PayerID != txn.PayeeID)) { - IClientAPI payeeClient = LocateClientObject(txn.PayeeID); + if (messagePayee && (txn.PayerID != txn.PayeeID)) + { + IClientAPI payeeClient = LocateClientObject(UUID.Parse(txn.PayeeID)); + // TODO: remove description once in txn and managed in sendTxnStatusToClient // string payeeBaseStatus = String.Format("Submitting transaction request...\n {0}", payeeActionStr); string payeeBaseStatus = String.Format("Submitting transaction request...\n {0}\nDescription: {1}", payeeActionStr, description); - sendTxnStatusToClient(txn, payeeClient, payeeBaseStatus, showDetailsWithTxnBegun, showIDWithTxnBegun, txn.PayeeID); + sendTxnStatusToClient(txn, payeeClient, payeeBaseStatus, showDetailsWithTxnBegun, showIDWithTxnBegun, UUID.Parse(txn.PayeeID)); } } @@ -4040,7 +4220,7 @@ private void alertUsersTransactionStageCompleted(GloebitTransaction txn, Gloebit } // for now, we're only going to send these to the payer. - IClientAPI payerClient = LocateClientObject(txn.PayerID); + IClientAPI payerClient = LocateClientObject(UUID.Parse(txn.PayerID)); sendTxnStatusToClient(txn, payerClient, status, showDetailsWithTxnStage, showIDWithTxnStage); } @@ -4246,32 +4426,36 @@ private void alertUsersTransactionFailed(GloebitTransaction txn, GloebitAPI.Tran } // send failure alert to payer - IClientAPI payerClient = LocateClientObject(txn.PayerID); + IClientAPI payerClient = LocateClientObject(UUID.Parse(txn.PayerID)); sendTxnStatusToClient(txn, payerClient, statusAndInstruction, showDetailsWithTxnFailed, showIDWithTxnFailed); // Determine if alert needs to be sent to payee and send IClientAPI payeeClient = null; - if (txn.TransactionType == (int)TransactionType.OBJECT_PAYS_USER || messagePayee) { + if (txn.TransactionType == (int)TransactionType.OBJECT_PAYS_USER || messagePayee) + { // locate payee since we'll need to message - payeeClient = LocateClientObject(txn.PayeeID); + payeeClient = LocateClientObject(UUID.Parse(txn.PayeeID)); } + // If this is a transaction type where we notified the payer the txn started, we should alert to failure as payer may have triggered the txn - if (txn.TransactionType == (int)TransactionType.OBJECT_PAYS_USER) { + if (txn.TransactionType == (int)TransactionType.OBJECT_PAYS_USER) + { // build failure alert from temp strings statusAndInstruction = status; if (!String.IsNullOrEmpty(payeeInstruction)) { statusAndInstruction = String.Format("{0}\n{1}", status, payeeInstruction); } - sendTxnStatusToClient(txn, payeeClient, statusAndInstruction, showDetailsWithTxnFailed, showIDWithTxnFailed, txn.PayeeID); + sendTxnStatusToClient(txn, payeeClient, statusAndInstruction, showDetailsWithTxnFailed, showIDWithTxnFailed, UUID.Parse(txn.PayeeID)); } // If necessary, send separate message to Payee if (messagePayee) { - sendMessageToClient(payeeClient, payeeMessage, txn.PayeeID); + sendMessageToClient(payeeClient, payeeMessage, UUID.Parse(txn.PayeeID)); // TODO: this message should be delivered to email if client is not online and didn't trigger this message. // Since unidentified seller can now be fixed by auth, send the auth link if they are online - if (payeeClient != null && failure == GloebitAPI.TransactionFailure.PAYEE_CANNOT_BE_IDENTIFIED) { + if (payeeClient != null && failure == GloebitAPI.TransactionFailure.PAYEE_CANNOT_BE_IDENTIFIED) + { m_apiW.Authorize(payeeClient.AgentId, payeeClient.Name); } } @@ -4289,20 +4473,20 @@ private void alertUsersTransactionSucceeded(GloebitTransaction txn) bool showDetailsWithTxnSucceeded = false; bool showIDWithTxnSucceeded = false; - IClientAPI payerClient = LocateClientObject(txn.PayerID); - IClientAPI payeeClient = LocateClientObject(txn.PayeeID); // get this regardless of messaging since we'll try to update balance + IClientAPI payerClient = LocateClientObject(UUID.Parse(txn.PayerID)); + IClientAPI payeeClient = LocateClientObject(UUID.Parse(txn.PayeeID)); // get this regardless of messaging since we'll try to update balance // send success message to payer sendTxnStatusToClient(txn, payerClient, "Transaction SUCCEEDED.", showDetailsWithTxnSucceeded, showIDWithTxnSucceeded); // If this is a transaction type where we notified the payee the txn started, we should alert to successful completion if ((txn.TransactionType == (int)TransactionType.OBJECT_PAYS_USER) && (txn.PayerID != txn.PayeeID)) { - sendTxnStatusToClient(txn, payeeClient, "Transaction SUCCEEDED.", showDetailsWithTxnSucceeded, showIDWithTxnSucceeded, txn.PayeeID); + sendTxnStatusToClient(txn, payeeClient, "Transaction SUCCEEDED.", showDetailsWithTxnSucceeded, showIDWithTxnSucceeded, UUID.Parse(txn.PayeeID)); } // If this transaction was one user paying another, if the user is online, we should let them know they received a payment if (txn.TransactionType == (int)TransactionType.USER_PAYS_USER) { string message = String.Format("You've received Gloebits from {0}.", resolveAgentName(payerClient.AgentId)); - sendTxnStatusToClient(txn, payeeClient, message, true, showIDWithTxnSucceeded, txn.PayeeID); + sendTxnStatusToClient(txn, payeeClient, message, true, showIDWithTxnSucceeded, UUID.Parse(txn.PayeeID)); } // TODO: consider if we want to send an alert that payee earned money with transaction details for other transaction types @@ -4311,19 +4495,58 @@ private void alertUsersTransactionSucceeded(GloebitTransaction txn) // TODO: Once we store description in txn, change 3rd arg in SMB below to Utils.StringToBytes(description) // Update Payer & Payee balances if still logged in. - if (payerClient != null) { - if (txn.PayerEndingBalance >= 0) { /* if -1, got an invalid balance in response. possible this shouldn't ever happen */ - payerClient.SendMoneyBalance(txn.TransactionID, true, new byte[0], txn.PayerEndingBalance, txn.TransactionType, txn.PayerID, false, txn.PayeeID, false, txn.Amount, txn.PartDescription); - } else { + if (payerClient != null) + { + if (txn.PayerEndingBalance >= 0) + { /* if -1, got an invalid balance in response. possible this shouldn't ever happen */ + payerClient.SendMoneyBalance( + UUID.Parse(txn.TransactionID), + true, + new byte[0], + txn.PayerEndingBalance, + txn.TransactionType, + UUID.Parse(txn.PayerID), + false, + UUID.Parse(txn.PayeeID), + false, + txn.Amount, + txn.PartDescription); + } + else + { // TODO: consider what this delays while it makes non async call GetBalance from GetUserBalance call get balance - int payerBalance = (int)m_apiW.GetUserBalance(txn.PayerID, true, payerClient.Name); - payerClient.SendMoneyBalance(txn.TransactionID, true, new byte[0], payerBalance, txn.TransactionType, txn.PayerID, false, txn.PayeeID, false, txn.Amount, txn.PartDescription); + int payerBalance = (int)m_apiW.GetUserBalance(UUID.Parse(txn.PayerID), true, payerClient.Name); + payerClient.SendMoneyBalance( + UUID.Parse(txn.TransactionID), + true, + new byte[0], + payerBalance, + txn.TransactionType, + UUID.Parse(txn.PayerID), + false, + UUID.Parse(txn.PayeeID), + false, + txn.Amount, + txn.PartDescription); } } - if ((payeeClient != null) && (txn.PayerID != txn.PayeeID)) { + + if ((payeeClient != null) && (txn.PayerID != txn.PayeeID)) + { // TODO: consider what this delays while it makes non async call GetBalance from GetUserBalance call get balance - int payeeBalance = (int)m_apiW.GetUserBalance(txn.PayeeID, false, payeeClient.Name); - payeeClient.SendMoneyBalance(txn.TransactionID, true, new byte[0], payeeBalance, txn.TransactionType, txn.PayerID, false, txn.PayeeID, false, txn.Amount, txn.PartDescription); + int payeeBalance = (int)m_apiW.GetUserBalance(UUID.Parse(txn.PayeeID), false, payeeClient.Name); + payeeClient.SendMoneyBalance( + UUID.Parse(txn.TransactionID), + true, + new byte[0], + payeeBalance, + txn.TransactionType, + UUID.Parse(txn.PayerID), + false, + UUID.Parse(txn.PayeeID), + false, + txn.Amount, + txn.PartDescription); } } diff --git a/addon-modules/Gloebit/GloebitMoneyModule/GloebitSubscription.cs b/addon-modules/Gloebit/GloebitMoneyModule/GloebitSubscription.cs index 81330511054..544a4753f7f 100644 --- a/addon-modules/Gloebit/GloebitMoneyModule/GloebitSubscription.cs +++ b/addon-modules/Gloebit/GloebitMoneyModule/GloebitSubscription.cs @@ -39,11 +39,13 @@ public class GloebitSubscription { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); // These 3 make up the primary key -- allows sim to swap back and forth between apps or GlbEnvs without getting errors - public UUID ObjectID; // ID of object with an LLGiveMoney or LLTransferLinden's script - local subscription ID + // public UUID ObjectID; // ID of object with an LLGiveMoney or LLTransferLinden's script - local subscription ID + public string ObjectID; // ID of object with an LLGiveMoney or LLTransferLinden's script - local subscription ID public string AppKey; // AppKey active when created public string GlbApiUrl; // GlbEnv Url active when created - public UUID SubscriptionID; // ID returned by create-subscription Gloebit endpoint + //public UUID SubscriptionID; // ID returned by create-subscription Gloebit endpoint + public string SubscriptionID; // ID returned by create-subscription Gloebit endpoint public bool Enabled; // enabled returned by Gloebit Endpoint - if not enabled, can't use. public DateTime cTime; // time of creation @@ -59,7 +61,7 @@ public class GloebitSubscription { public GloebitSubscription() { } - private GloebitSubscription(UUID objectID, string appKey, string apiURL, string objectName, string objectDescription) { + private GloebitSubscription(string objectID, string appKey, string apiURL, string objectName, string objectDescription) { this.ObjectID = objectID; this.AppKey = appKey; this.GlbApiUrl = apiURL; @@ -68,7 +70,7 @@ private GloebitSubscription(UUID objectID, string appKey, string apiURL, string this.Description = objectDescription; // Set defaults until we fill them in - SubscriptionID = UUID.Zero; + SubscriptionID = UUID.Zero.ToString(); this.cTime = DateTime.UtcNow; this.Enabled = false; @@ -76,13 +78,15 @@ private GloebitSubscription(UUID objectID, string appKey, string apiURL, string } - public static GloebitSubscription Init(UUID objectID, string appKey, string apiUrl, string objectName, string objectDescription) { - string objectIDstr = objectID.ToString(); - + public static GloebitSubscription Init(string objectID, string appKey, string apiUrl, string objectName, string objectDescription) + { GloebitSubscription s = new GloebitSubscription(objectID, appKey, apiUrl, objectName, objectDescription); - lock(s_subscriptionMap) { - s_subscriptionMap[objectIDstr] = s; + + lock(s_subscriptionMap) + { + s_subscriptionMap[objectID] = s; } + GloebitSubscriptionData.Instance.Store(s); return s; } diff --git a/addon-modules/Gloebit/GloebitMoneyModule/GloebitTransaction.cs b/addon-modules/Gloebit/GloebitMoneyModule/GloebitTransaction.cs index 1c6cc390ca6..907b0e12596 100644 --- a/addon-modules/Gloebit/GloebitMoneyModule/GloebitTransaction.cs +++ b/addon-modules/Gloebit/GloebitMoneyModule/GloebitTransaction.cs @@ -36,12 +36,15 @@ public class GloebitTransaction { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); // Primary Key value - public UUID TransactionID; + //public UUID TransactionID; + public string TransactionID; // Common, vital transaction details - public UUID PayerID; + //public UUID PayerID; + public string PayerID; public string PayerName; // TODO: do we need to ensure this is not larger than the db field on hypergrid? - VARCHAR(255) - public UUID PayeeID; + //public UUID PayeeID; + public string PayeeID; public string PayeeName; // TODO: do we need to ensure this is not larger than the db field on hypergrid? - VARCHAR(255) public int Amount; @@ -51,15 +54,18 @@ public class GloebitTransaction { // Subscription info public bool IsSubscriptionDebit; - public UUID SubscriptionID; + //public UUID SubscriptionID; + public string SubscriptionID; // Object info required when enacting/consume/canceling, delivering, and handling subscriptions - public UUID PartID; // UUID of object + //public UUID PartID; // UUID of object + public string PartID; // UUID of object public string PartName; // object name public string PartDescription; // Details required by IBuySellModule when delivering an object - public UUID CategoryID; // Appears to be a folder id used when saleType is copy + //public UUID CategoryID; // Appears to be a folder id used when saleType is copy + public string CategoryID; // Appears to be a folder id used when saleType is copy private uint? m_localID; // Region specific ID of object. Unclear why this is passed instead of UUID public int SaleType; // object, copy, or contents @@ -97,8 +103,24 @@ public GloebitTransaction() { m_localID = null; } - private GloebitTransaction(UUID transactionID, UUID payerID, string payerName, UUID payeeID, string payeeName, int amount, int transactionType, string transactionTypeString, bool isSubscriptionDebit, UUID subscriptionID, UUID partID, string partName, string partDescription, UUID categoryID, uint localID, int saleType) { - + private GloebitTransaction( + string transactionID, + string payerID, + string payerName, + string payeeID, + string payeeName, + int amount, + int transactionType, + string transactionTypeString, + bool isSubscriptionDebit, + string subscriptionID, + string partID, + string partName, + string partDescription, + string categoryID, + uint localID, + int saleType) + { // Primary Key value this.TransactionID = transactionID; @@ -145,6 +167,7 @@ private GloebitTransaction(UUID transactionID, UUID payerID, string payerName, U this.cTime = DateTime.UtcNow; this.enactedTime = null; // set to null instead of DateTime.MinValue to avoid crash on reading 0 timestamp this.finishedTime = null; // set to null instead of DateTime.MinValue to avoid crash on reading 0 timestamp + // TODO: We have made these nullable and initialize to null. We could alternatively choose a time that is not zero // and avoid any potential conflicts from allowing null. // On MySql, I had to set the columns to allow NULL, otherwise, inserting null defaulted to the current local time. @@ -156,7 +179,23 @@ private GloebitTransaction(UUID transactionID, UUID payerID, string payerName, U // First verifies that a transaction with this ID does not already exist // --- If existing txn is found, returns null // Creates new Transaction, stores it in the cache and db - public static GloebitTransaction Create(UUID transactionID, UUID payerID, string payerName, UUID payeeID, string payeeName, int amount, int transactionType, string transactionTypeString, bool isSubscriptionDebit, UUID subscriptionID, UUID partID, string partName, string partDescription, UUID categoryID, uint localID, int saleType) + public static GloebitTransaction Create( + string transactionID, + string payerID, + string payerName, + string payeeID, + string payeeName, + int amount, + int transactionType, + string transactionTypeString, + bool isSubscriptionDebit, + string subscriptionID, + string partID, + string partName, + string partDescription, + string categoryID, + uint localID, + int saleType) { // Create the Transaction GloebitTransaction txn = new GloebitTransaction(transactionID, payerID, payerName, payeeID, payeeName, amount, transactionType, transactionTypeString, isSubscriptionDebit, subscriptionID, partID, partName, partDescription, categoryID, localID, saleType);