Search code examples
c#facebookentity-frameworkcallbackfacebook-canvas

Callback functions finishing in the wrong order


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:

  1. 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.

  2. 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);
                    }
                }

Solution

  • 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).