I have a windows service which checks for date and send notification remainder to user for a payment for which user has subcribed for a service.
Its a monthly payment service system for which user has to make a payment at the end of the month and for that system sends 2 remainder notification to user :
1) Before N days from deadline if payment not made.
2) Send remainder on deadline if payment not received.
So below are the conditions based on which notifications are send to User as well as Administrator :
Code :
public enum PaymentStatusEnum
{
Ask_For_Payment = 0,
Payment_Remainder_Sent = 1,
Full_Payment_Done = 2,
Payment_Not_Done = 3,
}
public class ServicePaymentModel
{
public int PaymentId { get; set; }
public string Email { get; set; }
public int PaymentStatus { get; set; }
public string AdminId { get; set; }
public int NoOfDaysPassed { get; set; }
public decimal DueAmount { get; set; }
public decimal PaymentMade { get; set; }
}
public void SendNotification()
{
int daysBeforeDeadline = 10;
int deadlineDays = 20;
var paymentModel = new PaymentModel
{
Today = 10/5/2019,
DaysBeforeDeadline = daysBeforeDeadline,
DeadlineDays = deadlineDays
};
//Get all the payments whose daysBeforeDeadline or deadlineDays condition is met.
//For eg: If some users subscription started from 1/5/2019 and Todays date is 10/5/2019 then this users will be will be fetched because of daysBeforeDeadline.
//For eg: If some users subscription started from 20/4/2019 and Todays date is 10/5/2019 then this users will be will be fetched because deadlineDays condition
List<ServicePaymentModel> payments = MyRepo.GetPayments(paymentModel);
if (payments != null && payments.Count == 0)
return;
UserPayment userPayment = null;
foreach (var payment in payments)
{
try
{
if (payment.DueAmount > 0) //Payment not done
{
if (paymentModel.DeadlineDays == payment.NoOfDaysPassed
&& payment.PaymentStatus == (int)PaymentStatusEnum.Payment_Remainder_Sent) // payment not made on deadline
{
userPayment = new UserPayment
{
PaymentId = payment.Id,
PaymentStatus = (int)PaymentStatusEnum.Payment_Not_Done
}
SendNotificationToUser(payment);//method handles email sending and different email template for user
SendNotificationToAdmin(payment)//method handles email sending and different email template for Admin telling him about which user payment has not been received
}
else if (paymentModel.DaysBeforeDeadline == payment.NoOfDaysPassed
&& payment.PaymentStatus == (int)PaymentStatusEnum.Ask_For_Payment)//payment not done after N days
{
userPayment = new UserPayment
{
PaymentId = payment.Id,
PaymentStatus = (int)PaymentStatusEnum.Payment_Remainder_Sent
}
SendNotificationToUser(payment);//method handles email sending and different email template for user
SendNotificationToAdmin(payment)//method handles email sending and different email template for Admin telling him about which user payment has not been received
}
}
else if (payment.DueAmount == 0) // payment done
{
userPayment = new UserPayment
{
PaymentId = payment.Id,
PaymentStatus = (int)PaymentStatusEnum.Full_Payment_Done
}
if ((paymentModel.DeadlineDays == payment.NoOfDaysPassed
&& payment.PaymentStatus == (int)PaymentStatusEnum.Ask_For_Payment)// payment made on deadline
{
SendNotificationToUser(payment);//method handles email sending and different email template for user along with message and body
SendNotificationToAdmin(payment)//method handles email sending and different email template for admin along with message and body
}
else if (paymentModel.DaysBeforeDeadline == payment.NoOfDaysPassed
&& payment.PaymentStatus == (int)PaymentStatusEnum.Ask_For_Payment)//payment done before XX days
{
SendNotificationToAdmin(payment)//method handles email sending and different email template for admin along with message and body
}
}
PaymentRepo.UpdateUserPaymentStatus(userPayment);
}
catch (Exception ex)
{
//do nothing and continue processing other payment
}
}
}
I have seen this Plural sight video where Author - Zoran Horvat is saying that we can turn almost any If checks to object oriented solution and as you can see my code contains alot of if checks and tomorrow if more Conditions are added than this If will grow tremendously creating maintainance night mare.
All my Conditions and PaymentStatus are being handled based on If checks but here I am not getting how to turn this if conditions in to object oriented solution and whether it would be really possible or not in this case.
So is it possible to make this code object oriented by removing if checks or any better way to handle this checks?
public void SendNotificationRefactor2()
{
int daysBeforeDeadline = 10;
int deadlineDays = 20;
var paymentModel = new PaymentModel
{
Today = 10 / 5 / 2019,
DaysBeforeDeadline = daysBeforeDeadline,
DeadlineDays = deadlineDays
};
//Get all the payments whose daysBeforeDeadline or deadlineDays condition is met.
//For eg: If some users subscription started from 1/5/2019 and Todays date is 10/5/2019 then this users will be will be fetched because of daysBeforeDeadline.
//For eg: If some users subscription started from 20/4/2019 and Todays date is 10/5/2019 then this users will be will be fetched because deadlineDays condition
List<ServicePaymentModel> payments = MyRepo.GetPayments(paymentModel);
if (payments != null && payments.Count == 0)
return;
//UserPayment userPayment = null;
foreach (var payment in payments)
{
try
{
// Breaking this out into a method is optional, really, because there's little chance it'll
HandlePayment(paymentModel, payment);
}
catch (Exception ex)
{
// SWALLOWING EXCEPTIONS IS AN INDESCRIBABLY BAD IDEA. DON'T DO THIS.
}
}
}
protected void HandlePayment(PaymentModel paymentModel, ServicePaymentModel payment)
{
var userPayment = new UserPayment
{
PaymentId = payment.Id
};
if (payment.DueAmount > 0) //Payment not done
{
if (paymentModel.DeadlineDays == payment.NoOfDaysPassed)
{
if (payment.PaymentStatus == (int)PaymentStatusEnum.Payment_Remainder_Sent)
{
userPayment.PaymentStatus = (int)PaymentStatusEnum.Payment_Not_Done;
}
else if (payment.PaymentStatus == (int)PaymentStatusEnum.Ask_For_Payment)//payment not done after N days
{
userPayment.PaymentStatus = (int)PaymentStatusEnum.Payment_Remainder_Sent;
}
SendNotificationToUser(payment);//method handles email sending and different email template for user
SendNotificationToAdmin(payment);//method handles email sending and different email template for Admin telling him about which user payment has not been received
}
}
else if (payment.DueAmount == 0) // payment done
{
userPayment.PaymentStatus = (int)PaymentStatusEnum.Full_Payment_Done;
if (paymentModel.DeadlineDays == payment.NoOfDaysPassed)
{
if (payment.PaymentStatus == (int)PaymentStatusEnum.Ask_For_Payment)
{
SendNotificationToUser(payment);//method handles email sending and different email template for user along with message and body
SendNotificationToAdmin(payment);//method handles email sending and different email template for admin along with message and body
}
else if (payment.PaymentStatus == (int)PaymentStatusEnum.Ask_For_Payment)
{
SendNotificationToAdmin(payment);//method handles email sending and different email template for admin along with message and body
}
}
}
PaymentRepo.UpdateUserPaymentStatus(userPayment);
}
First off: focus on the goal. The belief that making something more OO makes it better is a belief structure I call "object happiness disorder". Remember, the purpose of OO code is to lower the costs of large programs worked on by large teams, by making it very clear what service is provided by a piece of code, what its public interface is, and how it interacts with other components. It's not a general-purpose technique for making small code better.
Your goal should not be to make the program "more OO"; it should be to make it lower cost, so ask yourself "what are the costs associated with this program?" Where are you spending money, remembering that your salary is probably most of that money?
For example:
We're spending too much time updating the code when business processes change.
If that's the problem then I would make the program more OO, but not by "replace condition with polymorphism". Just because it is polymorphic does not make it OO. What makes it OO is we have identified fundamental concepts in the business domain and encapsulated those concepts into objects that only have to change when the business process changes.
The key thing to look at is your extremely helpful diagram that shows:
What exogenous condition triggers a state change?
What are the state changes?
What action is associated with a state change?
So, codify that. Make a base class EventTrigger. You've already got a type representing states. Make a class called EventAction. Make a class Condition. And now what is our process?
for each trigger in eventTriggers
if any trigger condition is met
execute all trigger actions
Now we're down to a single if statement, like you wanted. Now you can write one class for each condition, and one class for each action, and triggers tie them together.
If you want to change the action associated with a particular trigger, you change it in one place, not in a mass of spaghetti code.
Also, this technique is amenable to many other improvements. You can easily add logging; logging is just another action. You can make compositions of actions; make an action that takes two actions and runs them both. And so on.
You could even make a configuration document that looks like:
TRIGGER PaymentReceivedTrigger HAS CONDITION AskForPayment WITH ACTIONS SetFullPayment, EmailAdmin
…
And now you can set up your whole system based on a config file instead of writing C# code.
But what if that is not the problem? What if the problem is:
We're spending too much time tracking down bugs
Or
Our performance is bad and we do not know why
Or
We're completely tied to one database vendor but they are too expensive; how can we lower the cost of switching back ends?
Or any of a million other things? In those cases, you don't want to waste any time building an OO business process engine; you want to concentrate on the problem that is actually costing you money, and figure out what lowers that cost.
Related
I have problem in when user post the data. Some times the post run so fast and this make problem in my website.
The user want to register a form about 100$ and have 120$ balance.
When the post (save) button pressed sometimes two post come to server very fast like:
2018-01-31 19:34:43.660 Register Form 5760$
2018-01-31 19:34:43.663 Register Form 5760$
Therefore my client balance become negative.
I use If in my code to check balance but the code run many fast and I think both if happen together and I missed them.
Therefore I made Lock Controll class to avoid concurrency per user but not work well.
I made global Action Filter to control the users this is my code:
public void OnActionExecuting(ActionExecutingContext context)
{
try
{
var controller = (Controller)context.Controller;
if (controller.User.Identity.IsAuthenticated)
{
bool jobDone = false;
int delay = 0;
int counter = 0;
do
{
delay = LockControllers.IsRequested(controller.User.Identity.Name);
if (delay == 0)
{
LockControllers.AddUser(controller.User.Identity.Name);
jobDone = true;
}
else
{
counter++;
System.Threading.Thread.Sleep(delay);
}
if (counter >= 10000)
{
context.HttpContext.Response.StatusCode = 400;
jobDone = true;
context.Result = new ContentResult()
{
Content = "Attack Detected"
};
}
} while (!jobDone);
}
}
catch (System.Exception)
{
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
try
{
var controller = (Controller)context.Controller;
if (controller.User.Identity.IsAuthenticated)
{
LockControllers.RemoveUser(controller.User.Identity.Name);
}
}
catch (System.Exception)
{
}
}
I made list static list of user and sleep their thread until previous task happen.
Is there any better way to manage this problem?
So the original question has been edited so this answer is invalid.
so the issue isn't that the code runs too fast. Fast is always good :) The issue is that the account is going into negative funds. If the client decides to post a form twice that is the clients fault. It maybe that you only want the client to pay only once which is an other problem.
So for the first problem, I would recommend a using transactions (https://en.wikipedia.org/wiki/Database_transaction) to lock your table. Which means that the add update/add a change (or set of changes) and you force other calls to that table to wait until those operations have been done. You can always begin your transaction and check that the account has the correct amount of funds.
If the case is that they are only ever meant to pay once then.. then have a separate table that records if the user has payed (again within a transaction), before processing the update/add.
http://www.entityframeworktutorial.net/entityframework6/transaction-in-entity-framework.aspx
(Edit: fixing link)
You have a few options here
You implement ETag functionality in your app which you can use for optimistic concurrency. This works well, when you are working with records, i.e. you have a database with a data record, return that to the user and then the user changes it.
You could add an required field with a guid to your view model which you pass to your app and add it to in memory cache and check it on each request.
public class RegisterViewModel
{
[Required]
public Guid Id { get; set; }
/* other properties here */
...
}
and then use IMemoryCache or IDistributedMemoryCache (see ASP.NET Core Docs) to put this Id into the memory cache and validate it on request
public Task<IActioNResult> Register(RegisterViewModel register)
{
if(!ModelState.IsValid)
return BadRequest(ModelState);
var userId = ...; /* get userId */
if(_cache.TryGetValue($"Registration-{userId}", register.Id))
{
return BadRequest(new { ErrorMessage = "Command already recieved by this user" });
}
// Set cache options.
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Keep in cache for 5 minutes, reset time if accessed.
.SetSlidingExpiration(TimeSpan.FromMinutes(5));
// when we're here, the command wasn't executed before, so we save the key in the cache
_cache.Set($"Registration-{userId}", register.Id, cacheEntryOptions );
// call your service here to process it
registrationService.Register(...);
}
When the second request arrives, the value will already be in the (distributed) memory cache and the operation will fail.
If the caller do not sets the Id, validation will fail.
Of course all that Jonathan Hickey listed in his answer below applies to, you should always validate that there is enough balance and use EF-Cores optimistic or pessimistic concurrency
Summary
Need to be able to tell if a work request has one or more statuses currently applied to it, and be able to remove statuses without affecting other statuses applied. Currently, the work request can only have one status at a time, and the code for determining what the 'most important' status is keeps growing.
SQL server back end, C# with EF (mostly) for data access
Background
I'm working on an application where we have a work request where the status changes as people do specific activities until the request is finished. There are close to 30 statuses that the request can have, and there are many instances where we need to know if one or more statuses have been applied to the work request (to determine what happens next).
Currently the request has a single status that reflects the most current status, and when we change the status it has to go through code that looks at other associated data to determine what the 'most important' status is and change the request to that one.
This business problem seems to be perfect for using bitwise calculations, but I don't want to resort to an obsolete practice. The other possibility is to just have a collection of statuses and add/remove from the list.
Thanks
X DO NOT use an enum for open sets (such as the operating system
version, names of your friends, etc.).
[Microsoft Framework Design Guidelines].
Your use case sounds like an open set that will be added to over time. So based on that alone I'd say enum's are not right for this use case.
X AVOID creating flag enums where certain combinations of values are invalid.
Additionally it doesn't sound like all the values from your enum can be combined and still be valid.
Lastly here's a comment from Steven Clarke from the published copy of the Microsoft Framework Design Guidelines about the complexity in your proposed use of enums:
I'm sure that less experienced developers will be able to understand
bitwise operation on flags. The real question, though, is whether they
would expect to have to do this. Most of the APIs that I have run
through the labs don't require them to perform such operations so I
have a feeling that they would have the same experience that we
observed during a recent study - it's just not something that they are
used to doing so they might not even think about it. Where it could get
worse, I think, is that if less advanced developers don't realize they
are working with a set of flags that can be combined with one another,
they might just look at the list available and think that is all the
functionality they can access. As we've seen in other studies, if an
API makes it look to them as though a specific scenario or requirement
isn't immediately possible, it's likely that they will change the
requirement and do what does appear to be possible, rather than being
motivated to spend time investigating what they need to do to achieve
the original goal.
What follows are just some thoughts about enums should you go this route:
DO name flag enums with plural nouns or noun phrases and simple enums with singular nouns or noun phrases.
DO use powers of two for the flag enum values so they can be freely combined using the bitwise OR operation.
I don't think there is anything necessarily wrong with using a flagged enum for your status. To clarify, I think there are two things you are talking about. The set of actions that have been done for a request, and then some sort of derived value that you want to communicate to the user as being the most important value. Think this could be handled by doing a flagged enum, and then a property that is derived from your status enum (you might already be doing this). I would also recommend keeping a log of when each status was applied to a request in a separate entity.
As far as enforcing your statuses goes, one thing you could try is to represent your process as a directed graph of each of the steps. Then that data structure can be used to determine if all the conditions are met to move to the next step in your process.
[Flags]
public enum Status
{
Unknown = 0,
Completed = 1,
Blocked = 2,
Phase1 = 4,
Phase2 = 8,
Phase3 = 16,
Closed = 32
}
public class Request
{
public string Name { get; set; }
public string StatusText { get { return GetStatusText(); } }
public Status Status { get; set; }
public Request()
{
this.Status = Status.Unknown;
}
private string GetStatusText()
{
string statusText = "Created";
if (AnyStatus(Status.Closed | Status.Completed))
{
statusText = IsStatus(Status.Closed) ? "Closed" : "Completed";
}
else
{
if (IsStatus(Status.Blocked))
{
statusText = "Blocked";
}
else
{
if(IsStatus(Status.Phase3)) {
statusText = "Phase 3";
}
else if(IsStatus(Status.Phase2)) {
statusText = "Phase 2";
}
else if (IsStatus(Status.Phase1))
{
statusText = "Phase 1";
}
}
}
return statusText;
}
private bool IsStatus(Status checkStatus)
{
return ((this.Status & checkStatus) == checkStatus);
}
private bool AnyStatus(Status checkStatus)
{
return ((this.Status & checkStatus) > 0);
}
}
Possible class for logging status changes
public class StatusLog
{
public int RequestId { get; set; }
public int UserId { get; set; }
public DateTime Date { get; set; }
public Status Status { get; set; }
}
I am creating an app that implements the Soomla Unity IAP plugin. In my effort to get the IAP to work, I have gotten to a point where I can make a purchase when in the editor. (Not a real purchase, it just updates the virtual currency that the user can buy/spend in game).
When I launch this on an Android device, I get this error: Authentication is required. You need to sign into your Google Account.
Now I have read multiple different articles where people have had this issue and nothing seems to be helping me.
Here is list of what I have tried so far:
1) Make sure app is published either to alpha or beta for testing.(in alpha now)
2) Make sure prices match for game and in developer console.
3) Use a device that is logged in as a user that is not using the developer email.
4) Make sure the email on the test device is listed as a tester in the developer console.
5) Make sure the necessary permissions are listed in the Android Manifest.
6) Confirm merchant account is set up correctly and verified.
7) Make sure to build as release and not development (not sure if this even matters, but I tried both ways).
8) Completely removed all of the Soomla plugin from the project and then added it back in while following the tutorial very closely just to makes sure nothing small was missed. Still no dice.
I have the core and store components added to the scene that they are necessary in. Please let me know if you have any other ideas of what I should do to fix this problem. In the mean time, here is the StoreAssets.cs code I use for setting up Soomla:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Soomla.Store;
public class StoreAssets : IStoreAssets
{
public static bool purchased = false;
public int GetVersion()
{
return 0;
}
public void onItemPurchased(PurchasableVirtualItem pvi, string payload)
{
purchased = true;
}
public VirtualCurrency[] GetCurrencies()
{
return new VirtualCurrency[]{TOKEN_CURRENCY};
}
public VirtualGood[] GetGoods()
{
return new VirtualGood[] {BACKUP_FORCEFIELD, IMMUNITY, EMP, MULTIPLIER};
}
public VirtualCurrencyPack[] GetCurrencyPacks()
{
return new VirtualCurrencyPack[] {FIVE_TOKEN_PACK, TEN_TOKEN_PACK, FIFTY_TOKEN_PACK};
}
public VirtualCategory[] GetCategories()
{
return new VirtualCategory[]{};
}
/** Virtual Currencies **/
public static VirtualCurrency TOKEN_CURRENCY = new VirtualCurrency
(
"Token", // Name
"Token currency", // Description
"token_currency_ID" // Item ID
);
/** Virtual Currency Packs **/
public static VirtualCurrencyPack FIVE_TOKEN_PACK = new VirtualCurrencyPack
(
"5 Tokens", // Name
"5 token currency units", // Description
"5_tokens_id", // Item ID
5, // Number of currencies in the pack
"token_currency_ID", // ID of the currency associated with this pack
new PurchaseWithMarket
( // Purchase type (with real money $)
"tokens_5_PROD_ID", // Product ID
0.99 // Price (in real money $)
)
);
public static VirtualCurrencyPack TEN_TOKEN_PACK = new VirtualCurrencyPack
(
"10 Tokens", // Name
"10 token currency units", // Description
"10_tokens_id", // Item ID
10, // Number of currencies in the pack
"token_currency_ID", // ID of the currency associated with this pack
new PurchaseWithMarket
( // Purchase type (with real money $)
"tokens_10_PROD_ID", // Product ID
1.99 // Price (in real money $)
)
);
public static VirtualCurrencyPack FIFTY_TOKEN_PACK = new VirtualCurrencyPack
(
"50 Tokens", // Name
"50 token currency units", // Description
"50_tokens_id", // Item ID
50, // Number of currencies in the pack
"token_currency_ID", // ID of the currency associated with this pack
new PurchaseWithMarket
( // Purchase type (with real money $)
"tokens_50_PROD_ID", // Product ID
4.99 // Price (in real money $)
)
);
/** Virtual Goods **/
public static VirtualGood BACKUP_FORCEFIELD = new SingleUseVG
(
"BackupForcefield", // Name
"Secondary forcefield for extra protection.", // Description
"bff_ID", // Item ID
new PurchaseWithVirtualItem
( // Purchase type (with virtual currency)
"token_currency_ID", // ID of the item used to pay with
1 // Price (amount of coins)
)
);
public static VirtualGood EMP = new SingleUseVG
(
"Emp", // Name
"Clear the surrounding space of all lasers.", // Description
"emp_ID", // Item ID
new PurchaseWithVirtualItem
( // Purchase type (with virtual currency)
"token_currency_ID", // ID of the item used to pay with
5 // Price (amount of coins)
)
);
public static VirtualGood IMMUNITY = new SingleUseVG
(
"Immunity", // Name
"Immune to damage.", // Description
"immunity_ID", // Item ID
new PurchaseWithVirtualItem
( // Purchase type (with virtual currency)
"token_currency_ID", // ID of the item used to pay with
10 // Price (amount of coins)
)
);
public static VirtualGood MULTIPLIER = new SingleUseVG
(
"Multiplier", // Name
"Double your score per deflected laser.", // Description
"multiplier_ID", // Item ID
new PurchaseWithVirtualItem
( // Purchase type (with virtual currency)
"token_currency_ID", // ID of the item used to pay with
15 // Price (amount of coins)
)
);
}
In order to purchase Items, I call:
StoreInventory.BuyItem("the id of my item");
To use an item that has been purchased, I call:
StoreInventory.TakeItem("the id of my item");
StoreInventory is a class that is included in Soomla when it is imported into Unity.
Here is the code where buying and item consumption is done:
public class Purchase : MonoBehaviour
{
public Text tokens;
public static bool bffFilled = false,
immFilled = false, empFilled = false,
multFilled = false, init = false;
void Start()
{
if (!init)
{
init = true;
SoomlaStore.Initialize(new StoreAssets());
}
Token.updateTokens (tokens);
}
void Update()
{
if (StoreEvents.balanceChanged)
{
StoreEvents.balanceChanged = false;
Token.updateTokens(tokens);
}
}
public void BuyItem (int item)
{
if (item == 1)
{
StoreInventory.BuyItem (StoreAssets.FIVE_TOKEN_PACK.ItemId);
}
else if (item == 2)
{
StoreInventory.BuyItem (StoreAssets.TEN_TOKEN_PACK.ItemId);
}
else if (item == 3)
{
StoreInventory.BuyItem (StoreAssets.FIFTY_TOKEN_PACK.ItemId);
}
Token.updateTokens(tokens);
}
public void getUpgrade(int upgrade)
{
if (upgrade == 1)
{
bool bffNotBought = PlayerPrefs.GetInt("Bff Available", 0) == 0;
if (StoreAssets.TOKEN_CURRENCY.GetBalance() >= 1 && bffNotBought)
{
PlayerPrefs.SetInt("Bff Available", 1);
PlayerPrefs.Save();
bffFilled = true;
StoreInventory.TakeItem(StoreAssets.TOKEN_CURRENCY.ItemId, 1);
}
}
else if (upgrade == 2)
{
bool empNotBought = PlayerPrefs.GetInt("Emp Available", 0) == 0;
if (StoreAssets.TOKEN_CURRENCY.GetBalance() >= 5 && empNotBought)
{
PlayerPrefs.SetInt("Emp Available", 1);
PlayerPrefs.Save();
empFilled = true;
StoreInventory.TakeItem(StoreAssets.TOKEN_CURRENCY.ItemId, 5);
}
}
else if (upgrade == 3)
{
bool immNotBought = PlayerPrefs.GetInt("Imm Available", 0) == 0;
if (StoreAssets.TOKEN_CURRENCY.GetBalance() >= 10 && immNotBought)
{
PlayerPrefs.SetInt("Imm Available", 1);
PlayerPrefs.Save();
immFilled = true;
StoreInventory.TakeItem(StoreAssets.TOKEN_CURRENCY.ItemId, 10);
}
}
else if (upgrade == 4)
{
bool multNotBought = PlayerPrefs.GetInt("Mult Available", 0) == 0;
if (StoreAssets.TOKEN_CURRENCY.GetBalance() >= 15 && multNotBought)
{
PlayerPrefs.SetInt("Mult Available", 1);
PlayerPrefs.Save();
multFilled = true;
StoreInventory.TakeItem(StoreAssets.TOKEN_CURRENCY.ItemId, 15);
}
}
Token.updateTokens (tokens);
}
}
8/26/15
I have now created both a google group and a google community for testing this app. I added the email for my other android device to the both of these and I used the link provided to download the app. Doing all of this still resulted in the same error as before.
8/27/15
I just noticed that my credit card on my merchant account had expired. One of the articles I read mentioned having issues like this if there were issues with the merchant account. I have updated the information and now I have to wait the deposit they will put in my account to make sure it is working. Once that is done I will update whether or not this fixed my current problem.
8/31/15
After finally verifying my merchant account on Google Play, I still seem to have the same problem. Fixing my merchant account did not change anything that I couldn't tell.
I just updated my post to include my whole StoreAssets.cs script and what I use to make purchases and consume items when a player uses them. I added this since I have no idea what else the issue could be.
9/7/15
Still no luck so far. Issue persists in android. The editor itself makes test purchases but purchases cannot be make from an android device without getting the same error as listed above.
9/9/15
Just as a quick update, I have tried building the project without the development build selected in the build settings and I send the link to all of the people in my Google community in order to give it a shot. Everyone still has the same error as my testing device does.
9/11/15
After trying a few things that Tony pointed out, I have noticed that the onBillingSupported() function is not called when I use this: StoreEvents.OnBillingSupported += onBillingSupported; I'm not sure why just yet.
9/12/15
After going through the tutorial on the soomla site, I have done everything it said except starting the Iab in the background and the fraud protection since they aren't required. the onBillingSupported method is still not called and I am still getting the same error as before on android devices.
9/12/15
I just removed everything for the Soomla plugin and imported the newest version and followed the instructions again and I still get the same error.
9/16/15
Really no idea what I am missing here. After removing all of the Soomla plugin and then adding it again and still getting the same error after multiple tries. I have followed everything as the tutorial says and I have all of the code above.
I have never had the problem you describe. I created a private Google+ community though, and added the community as testers. Shifted my App to Beta. And invited people to my private community where there was a link to download the app and test it. It was easy.
The second point, is your code above. You are fetching 4 goods and 3 currency packs but the code does not reflect that. Maybe you you only pasted half the class.
Finally, see if this thread will help: http://answers.soom.la/t/solved-some-clarification-about-iab-testing/2067/8
By the way, SOOMLA have a dedicated site answers.soom.la for SOOMLA related issues.
Did you try buying goods from another device?
I have had this issue before, but do not recall how I fixed it.
From what I remember when it failed for me, it was working on the different co-worker's device.
Try testing it on a different device.
also, do you have multiple google accounts on your device?
I remember that one of the things that I tried was removing all google account from my device and just left my main account registered. Unfortunately, I don't remember if that worked, but I hope it will help you.
if you find out how to fix it please post an update here. good luck
In my case, I didn't realize that what I was using for my item ID and my product ID were mixed up. The item ID is just to identify the item within soomla, the product ID is the actual I'd you,set in google play.
From the brief discussion in the comments it sounds like you may not have everything implemented.
Check out this link:
http://know.soom.la/unity/store/store_gettingstarted/
In the Getting Started section it states..
Create your own implementation of IStoreAssets in order to describe your game's specific assets.
Initialize SoomlaStore with the class you just created:
SoomlaStore.Initialize(new YourStoreAssetsImplementation());
Initialize SoomlaStore in the Start function of MonoBehaviour and NOT
in the Awake function. SOOMLA has its own MonoBehaviour and it needs
to be "Awakened" before you initialize.
Initialize SoomlaStore ONLY ONCE when your application loads.
The initialization is confirmed on this page as well.
http://know.soom.la/unity/store/store_istoreassets/
This says it is not mandatory but looks helpful
If you have your own storefront implemented inside your game, it's
recommended that you open the IAB Service in the background when the
store opens and close it when the store is closed.
// Start Iab Service SoomlaStore.StartIabServiceInBg();
// Stop Iab Service SoomlaStore.StopIabServiceInBg();
This is not mandatory, your game will work without this, but we do recommend it
because it enhances performance. The idea here is to preemptively
start the in-app billing setup process with Google's (or Amazon's)
servers.
You could try adding logging to the initialize event to make sure it is initializing. Here is a list of events you can try logging from..
http://know.soom.la/unity/store/store_events/
NOTE: One thing you need to notice is that if you want to listen to
OnSoomlaStoreInitialized event you have to set up the listener before
you initialize SoomlaStore. So you'll need to do:
StoreEvents.OnSoomlaStoreInitialized += onSoomlaStoreInitialized;
before
Soomla.SoomlaStore.Initialize(new Soomla.Example.MuffinRushAssets());
You can also setup the OnBillingSetup event with some logging to make sure it is initializing your billing correctly.
StoreEvents.OnBillingSupported += onBillingSupported;
public void onBillingSupported() {
// ... your game specific implementation here ... }
I am currently using the Change Notifications in Active Directory Domain Services in .NET as described in this blog. This will return all events that happen on an selected object (or in the subtree of that object). I now want to filter the list of events for creation and deletion (and maybe undeletion) events.
I would like to tell the ChangeNotifier class to only observe create-/delete-/undelete-events. The other solution is to receive all events and filter them on my side. I know that in case of the deletion of an object, the atribute list that is returned will contain the attribute isDeleted with the value True. But is there a way to see if the event represents the creation of an object? In my tests the value for usnchanged is always usncreated+1 in case of userobjects and both are equal for OUs, but can this be assured in high-frequency ADs? It is also possible to compare the changed and modified timestamp. And how can I tell if an object has been undeleted?
Just for the record, here is the main part of the code from the blog:
public class ChangeNotifier : IDisposable
{
static void Main(string[] args)
{
using (LdapConnection connect = CreateConnection("localhost"))
{
using (ChangeNotifier notifier = new ChangeNotifier(connect))
{
//register some objects for notifications (limit 5)
notifier.Register("dc=dunnry,dc=net", SearchScope.OneLevel);
notifier.Register("cn=testuser1,ou=users,dc=dunnry,dc=net", SearchScope.Base);
notifier.ObjectChanged += new EventHandler<ObjectChangedEventArgs>(notifier_ObjectChanged);
Console.WriteLine("Waiting for changes...");
Console.WriteLine();
Console.ReadLine();
}
}
}
static void notifier_ObjectChanged(object sender, ObjectChangedEventArgs e)
{
Console.WriteLine(e.Result.DistinguishedName);
foreach (string attrib in e.Result.Attributes.AttributeNames)
{
foreach (var item in e.Result.Attributes[attrib].GetValues(typeof(string)))
{
Console.WriteLine("\t{0}: {1}", attrib, item);
}
}
Console.WriteLine();
Console.WriteLine("====================");
Console.WriteLine();
}
LdapConnection _connection;
HashSet<IAsyncResult> _results = new HashSet<IAsyncResult>();
public ChangeNotifier(LdapConnection connection)
{
_connection = connection;
_connection.AutoBind = true;
}
public void Register(string dn, SearchScope scope)
{
SearchRequest request = new SearchRequest(
dn, //root the search here
"(objectClass=*)", //very inclusive
scope, //any scope works
null //we are interested in all attributes
);
//register our search
request.Controls.Add(new DirectoryNotificationControl());
//we will send this async and register our callback
//note how we would like to have partial results
IAsyncResult result = _connection.BeginSendRequest(
request,
TimeSpan.FromDays(1), //set timeout to a day...
PartialResultProcessing.ReturnPartialResultsAndNotifyCallback,
Notify,
request
);
//store the hash for disposal later
_results.Add(result);
}
private void Notify(IAsyncResult result)
{
//since our search is long running, we don't want to use EndSendRequest
PartialResultsCollection prc = _connection.GetPartialResults(result);
foreach (SearchResultEntry entry in prc)
{
OnObjectChanged(new ObjectChangedEventArgs(entry));
}
}
private void OnObjectChanged(ObjectChangedEventArgs args)
{
if (ObjectChanged != null)
{
ObjectChanged(this, args);
}
}
public event EventHandler<ObjectChangedEventArgs> ObjectChanged;
#region IDisposable Members
public void Dispose()
{
foreach (var result in _results)
{
//end each async search
_connection.Abort(result);
}
}
#endregion
}
public class ObjectChangedEventArgs : EventArgs
{
public ObjectChangedEventArgs(SearchResultEntry entry)
{
Result = entry;
}
public SearchResultEntry Result { get; set; }
}
I participated in a design review about five years back on a project that started out using AD change notification. Very similar questions to yours were asked. I can share what I remember, and don't think things have change much since then. We ended up switching to DirSync.
It didn't seem possible to get just creates & deletes from AD change notifications. We found change notification resulted enough events monitoring a large directory that notification processing could bottleneck and fall behind. This API is not designed for scale, but as I recall the performance/latency were not the primary reason we switched.
Yes, the usn relationship for new objects generally holds, although I think there are multi-dc scenarios where you can get usncreated == usnchanged for a new user, but we didn't test that extensively, because...
The important thing for us was that change notification only gives you reliable object creation detection under the unrealistic assumption that your machine is up 100% of the time! In production systems there are always some case where you need to reboot and catch up or re-synchronize, and we switched to DirSync because it has a robust way to handle those scenarios.
In our case it could block email to a new user for an indeterminate time if an object create were missed. That obviously wouldn't be good, we needed to be sure. For AD change notifications, getting that resync right that would have some more work and hard to test. But for DirSync, its more natural, and there's a fast-path resume mechanism that usually avoids resync. For safety I think we triggered a full re-synchronize every day.
DirSync is not as real-time as change notification, but its possible to get ~30-second average latency by issuing the DirSync query once a minute.
I am trying to get a list of all users in our instance of Desire2Learn using a looping structure through the bookmarks however for some reason it continuously loops and doesn't return. When I debug it it is showing massive amounts of users (far more than we have in the system as shown by the User Management Tool. A portion of my code is here:
public async Task<List<UserData>> GetAllUsers(int pages = 0)
{
//List<UserData> users = new List<UserData>();
HashSet<UserData> users = new HashSet<UserData>();
int pageCount = 0;
bool getMorePages = true;
var response = await Get<PagedResultSet<UserData>>("/d2l/api/lp/1.4/users/");
var qParams = new Dictionary<string, string>();
do
{
qParams["bookmark"] = response.PagingInfo.Bookmark;
//users = users.Concat(response.Items).ToList<UserData>();
users.UnionWith(response.Items);
response = await Get<PagedResultSet<UserData>>("/d2l/api/lp/1.4/users/", qParams);
if (pages != 0)
{
pageCount++;
if (pageCount >= pages)
{
getMorePages = false;
}
}
}
while (response.PagingInfo.HasMoreItems && getMorePages);
return users.ToList();
}
I originally was using the List container that is commented out but just switched to the HashSet to see if I could notice if duplicates where being added.
It's fairly simple, but for whatever reason it's not working. The Get<PagedResultSet<UserData>>() method simply wraps the HTTP request logic. We set the bookmark each time and send it on.
The User Management Tool indicates there are 39,695 users in the system. After running for just a couple of minutes and breaking on the UnionWith in the loop I'm showing that my set has 211,800 users.
What am I missing?
It appears that you’ve encountered a defect in this API. The next course of action is for you to have your institution’s Approved Support Contact open an Incident through the Desire2Learn Helpdesk. Please make mention in the Incident report that Sarah-Beth Bianchi is aware of the issue, and I will work with our Support team to direct this issue appropriately.