This is a very confusing issue so I will do my best to elaborate. I have a facebook canvas app that takes payments and when the user hits the payment button, the following happens:
My javascript callback function gets called and is passed a payment id and so I save this payment id and other info to my order database.
Facebook calls a callback url that I have setup to let me know when the payment goes through. It gives me the payment id only so I use this to search the database for the row with the payment id they have sent. I then use the info in the row to populate an email that I send to the customer for their order confirmation.
My big issue is that for some reason step 2 is getting finished before step 1 so when I try to look up the payment id in the database, it doesn't exist yet so I can't send the email to the customer. What can I do to fix this? I have the pseudo code below for both steps.
Step 1:
using (OrderDBContext order = new OrderDBContext())
{
string message = Encryption.SimpleDecryptWithPassword(orderDetails.request_id, GlobalFacebookConfiguration.Configuration.AppId, 0);
string[] finalMessage = message.Split('@');
int orderID = Convert.ToInt16(finalMessage.ElementAtOrDefault(2));
Models.Order row = order.Orders.Where(i => i.ID == orderID).FirstOrDefault();
switch (orderDetails.status)
{
case "completed":
row.PaymentID = orderDetails.payment_id;
row.Currency = orderDetails.currency;
row.HashKey = orderDetails.request_id;
row.Paid = true;
order.SaveChanges();
return Json(new { message = "Your payment was processed! You will receive a confirmation email soon." }, JsonRequestBehavior.AllowGet);
case "initiated":
row.PaymentID = orderDetails.payment_id;
row.Currency = orderDetails.currency;
row.HashKey = orderDetails.request_id;
row.Paid = false;
order.SaveChanges();
return Json(new { message = "Your payment is being processed! You will receive a confirmation email as soon as the payment is confirmed." }, JsonRequestBehavior.AllowGet);
}
}
Step 2:
dynamic result = new StreamReader(request.InputStream).ReadToEnd();
var items = JsonConvert.DeserializeObject<RootObject>(result);
string paymentID;
if (items.entry != null && items.entry.Count > 0)
{
paymentID = items.entry[0].id;
}
else
{
// logic when items.entry is null or doesn't have any elements
paymentID = null;
}
if (PaymentHelper.confirmPayment(paymentID, GlobalFacebookConfiguration.Configuration.AppId, GlobalFacebookConfiguration.Configuration.AppSecret))
{
// if payment is confirmed then send email to us with the order details
// then send confirmation email to user letting them know that we are working on it
using (OrderDBContext order = new OrderDBContext())
{
Order row = order.Orders.Where(i => i.PaymentID == paymentID).FirstOrDefault();
SendEmail.sendOrderDetailsToWriter(row);
SendEmail.sendOrderDetailsToCustomer(row);
}
}
What initiates the Facebook operation? Does something in step #1 cause step #2 to start? Or are both steps started asynchronously and together?
Assuming the latter (since that's the more difficult scenario), you should do something like this:
readonly object o = new object();
bool databaseUpdated;
// user clicked the payment button
void onClick()
{
databaseUpdated = false;
StartStep1();
StartStep2();
}
// completion routines for steps #1 and #2
void stepOneDone()
{
lock (o)
{
// do the database update here...i.e. the code you posted for step #1
databaseUpdated = true;
Monitor.Pulse(o);
}
}
void stepTwoDone()
{
lock (o)
{
while (!databaseUpdated)
{
Monitor.Wait(o);
}
// Process Facebook response here...i.e. the code you posted for step #2
}
}
The above uses a shared lock for the two operations to synchronize with each other. The databaseUpdated
flag indicates of course whether the database update has completed. If the step #2 completion is initiated before the database update has even managed to start (i.e. step #2 acquires the lock before step #1 can), it will check the flag, note that it's not set yet, and will wait. The call to Monitor.Wait()
releases the lock so that step #1 can take it. Then step #1 does what it needs to do, sets the flag, and signals to the step #2 thread that it can continue.
Of course if step #1 acquires the lock first, step #2 won't even be able to get it. By the time the lock is available again and step #2 can process past the lock
statement, the flag will be set and it can go on its merry way. :)
It is possible there's a fun way to solve the problem using the new async
/await
idiom, but without more context I can't say. The above should work for sure.
Finally, a minor nit: why do you declare the step #2 result
variable as dynamic
? The ReadToEnd()
method will never return anything except a string
. The use of dynamic
here is pointless at best, and potentially extra overhead at worst, due to the dynamic binding required (depending on whether the C# compiler notices it's pointless…I don't recall off the top of my head what the compilation rules are there).