public async Task <HttpResponseMessage> ReceiveBrowserFormPost(string punchoutName)
        {
            Util.Log($"starting browser form post {punchoutName}");
            var config = Util.GetPunchoutConfig(punchoutName);

            var formData = await this.Request.Content.ReadAsFormDataAsync();

            var xd = XDocument.Load(new MemoryStream(Convert.FromBase64String(formData["cxml-base64"])));

            Util.Log(xd.ToString());

            var buyerCookie = xd.Descendants("BuyerCookie").FirstOrDefault().Value;

            Util.Log($"buyer cookie: {buyerCookie}");

            //will throw exception on invalid cookie
            var validatedToken = Util.ValidateBuyerCookie(buyerCookie, config.SecretHashKey);
            var api            = await OrderCloudAPI.InitializeByClientCredentials(config.AdminAppId, config.AdminAppSecret);

            await api.ImpersonateBuyerUser(validatedToken.BuyerID, validatedToken.UserID, validatedToken.clientID, "OverrideUnitPrice", "ProductReader", "ProductAdmin");

            var order = await api.GetOrder(validatedToken.BuyerID, validatedToken.CurrentOrderID);

            if (order.ID == null)
            {
                order = await api.Createorder(validatedToken.BuyerID, validatedToken.CurrentOrderID);
            }

            var en = xd.Descendants("ItemIn").GetEnumerator();

            while (en.MoveNext())
            {
                await Additem(en.Current, api, validatedToken, config.PunchoutProductId);
            }

            if (config.BrowserFormPostRedirect != string.Empty)
            {
                var response = this.Request.CreateResponse(HttpStatusCode.Redirect);
                response.Headers.Location = new Uri(config.BrowserFormPostRedirect);
                return(response);
            }
            else
            {
                var lineitems = await api.ListLineItems(validatedToken.BuyerID, validatedToken.CurrentOrderID);

                return(this.Request.CreateResponse(HttpStatusCode.OK, lineitems));
            }
        }
        private async Task Additem(XElement item, OrderCloudAPI api, BuyerToken validatedToken, string productID)
        {
            var lineItem = new OrderCloudLineItem
            {
                ProductID = productID,
                Quantity  = Convert.ToInt32(item.Attributes().Where(x => x.Name == "quantity").FirstOrDefault()?.Value),
                UnitPrice = Convert.ToDecimal(item.Descendants("Money").FirstOrDefault()?.Value)
            };

            lineItem.xp.PunchoutName            = validatedToken.PunchoutName;
            lineItem.xp.SupplierPartAuxiliaryID = item.Descendants("SupplierPartAuxiliaryID").FirstOrDefault()?.Value;
            lineItem.xp.SupplierPartID          = item.Descendants("SupplierPartID").FirstOrDefault()?.Value;

            //since the ShortName element is also a child of description, it's werid to pull the sibling text which is also a child of description. There must be a better way.<Description><ShortName>some name</ShortName>some other additional text</Description>
            lineItem.xp.Description = item.Descendants("Description").DescendantNodesAndSelf()
                                      .FirstOrDefault(x => x.NodeType == XmlNodeType.Text && x.Parent.Name == "Description")?
                                      .ToString();
            lineItem.xp.ShortName = item.Descendants("ShortName").FirstOrDefault()?.Value;
            await api.CreateLineItem(validatedToken.BuyerID, validatedToken.CurrentOrderID, lineItem);
        }
        private async Task MapAddressElement(XmlDocument xml, OrderCloudAPI api, string shiptoID)
        {
            var xpath = "cXML/Request/PunchOutSetupRequest/ShipTo";

            if (shiptoID == null)
            {
                xml.SelectSingleNode("cXML/Request/PunchOutSetupRequest").RemoveChild(xml.SelectSingleNode(xpath));
                return;
            }

            var address = await api.GetUserAddress(shiptoID);

            xml.SelectSingleNode($"{xpath}/Address/@addressID").InnerText               = address.ID;
            xml.SelectSingleNode($"{xpath}/Address/PostalAddress/@name").InnerText      = address.AddressName;
            xml.SelectSingleNode($"{xpath}/Address/PostalAddress/DeliverTo").InnerText  = $"{address.FirstName} {address.LastName}";
            xml.SelectSingleNode($"{xpath}/Address/PostalAddress/Street").InnerText     = $"{address.Street1} {address.Street2}";
            xml.SelectSingleNode($"{xpath}/Address/PostalAddress/State").InnerText      = address.State;
            xml.SelectSingleNode($"{xpath}/Address/PostalAddress/PostalCode").InnerText = address.Zip;
            xml.SelectSingleNode($"{xpath}/Address/PostalAddress/Country/@isoCountryCode").InnerText = address.Country;
            xml.SelectSingleNode($"{xpath}/Address/PostalAddress/Country").InnerText = address.Country;
        }
        public async Task <HttpResponseMessage> StartPunchoutSession(SetupRequest setup)
        {
            Func <string, string, HttpResponseMessage> fail = (message, punchoutCode) => this.Request.CreateResponse(HttpStatusCode.BadRequest, new { StatusCode = punchoutCode, Message = message });

            if (setup.access_token == null || setup.punchoutName == null || setup.currentOrderID == null || setup.buyerID == null)
            {
                return(fail("Please pass AccessToken, PunchoutName, CurrentOrderID, and buyerID", null));
            }

            var api    = new OrderCloudAPI(setup.access_token);
            var ocUser = await api.GetCurrentUser();

            var         config = Util.GetPunchoutConfig(setup.punchoutName);
            XmlDocument doc    = new XmlDocument();

            //this provides a bit of security since the access token is validated against the OrderCloud api. This check will verify that an authorized user is using the punchout
            if (config.AllowedBuyerClientIds.Where(x => x.ToLower() == api.AccessToken.ClientID.ToLower()).Count() == 0)
            {
                return(fail("client id is not allowed to use this punchout config.", ""));
            }

            //load the setuprequest template specified in the config. Normally only one template will be needed, but it happens that vendors don't closely follow the spec and it might be easier to have a different template for that config
            doc.LoadXml(Util.ReadEmbeddedResource(config.SetupRequestTemplateResource));

            //if a shipto id is specified, pull the data from the OC api and add it to the setupRequest
            await MapAddressElement(doc, api, setup.shiptoID);

            //if a selected item is specified, add it directly to the setupRequest
            MapSelectedItem(doc, setup.selectedItemID, setup.selectedItemAuxID);

            //set the user based values in the in the Setuprequest
            config.SetupRequestMappings.First(x => x.Name == "BuyerCookie").Value = Util.GenerateBuyerCookie(new BuyerToken {
                UserID         = ocUser.ID,
                Username       = ocUser.Username,
                clientID       = api.AccessToken.ClientID,
                PunchoutName   = setup.punchoutName,
                CurrentOrderID = setup.currentOrderID,
                dateSigned     = Util.TimeInHours(),
                BuyerID        = setup.buyerID
            },
                                                                                                             config.SecretHashKey);
            config.SetupRequestMappings.First(x => x.Name == "UniqueUserName").Value = ocUser.Username;
            config.SetupRequestMappings.First(x => x.Name == "ContactName").Value    = $"{ocUser.FirstName} {ocUser.LastName}";
            config.SetupRequestMappings.First(x => x.Name == "ContactEmail").Value   = ocUser.Email;

            //load the Setuprequest values specific to this punchout config into the document
            var missingNode = Util.ReplaceTemplateValues(doc, config.HeaderMappings);

            missingNode += Util.ReplaceTemplateValues(doc, config.SetupRequestMappings);

            if (missingNode != "")
            {
                return(fail(missingNode, null));
            }

            //post the setuprequest to the vendor
            var stringResponse = await config.SetupRequestUrl.PostStringAsync(doc.OuterXml).ReceiveString();

            var response = new XmlDocument();

            response.LoadXml(stringResponse);

            var code = response.SelectSingleNode("cXML/Response/Status/@code")?.InnerText;

            if (code != "200")
            {
                return(fail(response.SelectSingleNode("cXML/Response/Status/@text")?.InnerText, code));
            }
            else
            {
                // the client app should look for a start url and redirect the browser there.
                var httpResponse = this.Request.CreateResponse(HttpStatusCode.Redirect);
                httpResponse.Headers.Location = new Uri(response.SelectSingleNode("cXML/Response/PunchOutSetupResponse/StartPage/URL").InnerText);
                return(httpResponse);
            }
        }
Пример #5
0
        public async Task ProcessOrderSubmit(string bearerToken, string orderID, string buyerID)
        {
            var addresses = new Dictionary <string, OrderCloudAddress>();
            var api       = new OrderCloudAPI(bearerToken);
            var order     = await api.GetOrder(buyerID, orderID);

            Util.Log(order.ToString());
            var lineItemList = await api.ListLineItems(buyerID, orderID);

            Func <string, Task <OrderCloudAddress> > GetCachedAddress = async(id) =>
            {
                if (id == null || id == "")
                {
                    return(null);
                }

                if (addresses.ContainsKey(id))
                {
                    return(addresses[id]);
                }
                else
                {
                    var address = await api.GetUserAddress(id);

                    addresses.Add(id, address);
                    return(address);
                }
            };

            //it's possible there will be line items from different punchout cofigs on this order
            var groups = lineItemList.Items.Where(x => x.xp != null && x.xp.PunchoutName != null).GroupBy(x => x.xp.PunchoutName, (key, group) => new GroupedItemList {
                PunchoutName = key, Items = group
            }).GetEnumerator();
            var billingAddress = await GetCachedAddress(order.BillingAddressID);

            while (groups.MoveNext())
            {
                var lineItemGroup = groups.Current;
                var config        = Util.GetPunchoutConfig(lineItemGroup.PunchoutName);
                var xml           = new XmlDocument();
                var orderTotal    = (decimal)0.00;

                xml.LoadXml(Util.ReadEmbeddedResource(config.OrderRequestTemplateResource));
                var itemTemplate = xml.SelectSingleNode("cXML/Request/OrderRequest/ItemOut").Clone();
                xml.SelectSingleNode("cXML/Request/OrderRequest").RemoveChild(xml.SelectSingleNode("cXML/Request/OrderRequest/ItemOut"));
                if (billingAddress != null)
                {
                    SetAddress(xml.SelectSingleNode("cXML/Request/OrderRequest/OrderRequestHeader/BillTo/Address/PostalAddress"), billingAddress);
                }

                for (var i = 0; i < lineItemGroup.Items.Count(); i++)
                {
                    var item    = lineItemGroup.Items.ElementAt(i);
                    var address = await GetCachedAddress(item.ShippingAddressID);

                    if (address != null)
                    {
                        SetAddress(itemTemplate.SelectSingleNode("ShipTo/Address/PostalAddress"), address);
                    }

                    itemTemplate.SelectSingleNode("@quantity").InnerText                      = item.Quantity.ToString();
                    itemTemplate.SelectSingleNode("@lineNumber").InnerText                    = i + 1.ToString();
                    itemTemplate.SelectSingleNode("ItemID/SupplierPartID").InnerText          = item.xp.SupplierPartID;
                    itemTemplate.SelectSingleNode("ItemID/SupplierPartAuxiliaryID").InnerText = item.xp.SupplierPartAuxiliaryID;
                    itemTemplate.SelectSingleNode("ItemDetail/Description").InnerText         = item.xp.Description;
                    itemTemplate.SelectSingleNode("ItemDetail/UnitPrice/Money").InnerText     = item.UnitPrice.ToString("0.00");
                    xml.SelectSingleNode("cXML/Request/OrderRequest").AppendChild(itemTemplate.Clone());
                    orderTotal += (item.UnitPrice * item.Quantity);
                }
                config.OrderRequestMappings.First(x => x.Name == "OrderID").Value    = orderID;
                config.OrderRequestMappings.First(x => x.Name == "OrderTotal").Value = Math.Round(orderTotal, 2).ToString("0.00");
                Util.ReplaceTemplateValues(xml, config.OrderRequestMappings);
                Util.ReplaceTemplateValues(xml, config.HeaderMappings);

                Util.Log(xml.OuterXml);
                var response = await config.OrderRequestUrl.PostStringAsync(xml.OuterXml).ReceiveString();

                Util.Log(response);
            }
        }