皇家邮政运输 API C#

本文关键字:API 运输 皇家 | 更新日期: 2023-09-27 18:32:17

我正在尝试将Royal Mail SOAP API集成到我的.NET代码中。我遵循了此处的建议 在 c# 控制台应用程序中使用 WCF 皇家邮件 API 和此处的 C# WCF 命名空间移动到标头并使用 NS 前缀。

我已经创建了一个自定义 IClientMessageFormatter 以便能够将命名空间附加到 soap 信封的开头,但我似乎仍然无法让它工作。我不断收到以下错误。无法为具有权限"api.royalmail.com"的 SSL/TLS 安全通道建立信任关系,内部异常是:根据验证过程,远程证书无效。

我正在使用Visual Studio 13和.Net 3.5版本,我已经尝试了许多其他版本,但没有进一步的进展。当我调试时,我可以看到普通消息已传递到RoyalMailMessage中,但是当它运行OnWriteStartEnvelope时,我看不到对_message对象的任何更改。我创建了一个跟踪来查看发送了什么 soap 请求。

我已将我的XML请求发送给皇家邮政支持人员,他们验证失败的原因是由于未在信封中声明命名空间和缺少前缀。

皇家邮政.cs

internal class RoyalMail
{
    private readonly X509Certificate2 _certificate;
    private readonly Config _config;
    public RoyalMail()
    {
        _config = new Config();
        _config.LoadConfig();
        // Load The SSL Certificate (Check The File Exists)
        var certificatePath = (Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + @"'" + _config.GetCertificateName());
        if (!File.Exists(certificatePath))
        {
            throw new Exception(@"The Royal Mail Certificate Is Missing From The Plugins Directory. Please Place The File " + _config.GetCertificateName() + " In The Same Directory As The Plugin DLL File & Relaunch FileMaker.'n'n" + certificatePath);
        }
        _certificate = new X509Certificate2(certificatePath, _config.GetCertificatePassword());
        // Check It's In The Certificate 
        var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
        store.Open(OpenFlags.ReadWrite);
        if (!store.Certificates.Contains(_certificate))
        {
            store.Add(_certificate);
            MessageBox.Show("Certificate Was Installed Into Computer Trust Store");
        }
        store.Close(); 
    }

    /*
     * 
     * SOAP Service & Methods
     * 
     */
    private shippingAPIPortTypeClient GetProxy()
    {
        var myBinding = new BasicHttpBinding(BasicHttpSecurityMode.Transport)
        {
            MaxReceivedMessageSize = 2147483647
        };
        myBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
        var uri = new Uri(_config.GetEndpointUrl());
        var endpointIdentity = EndpointIdentity.CreateDnsIdentity("api.royalmail.com");
        var shippingClient = new shippingAPIPortTypeClient(myBinding, new EndpointAddress(uri, endpointIdentity, new AddressHeaderCollection()));
        if (shippingClient.ClientCredentials != null)
            shippingClient.ClientCredentials.ClientCertificate.Certificate = _certificate;
        foreach (var od in shippingClient.Endpoint.Contract.Operations)
        {
           od.Behaviors.Add(new RoyalMailIEndpointBehavior());
        }
        return shippingClient;
    }
    private SecurityHeaderType GetSecurityHeaderType()
    {
        var securityHeader = new SecurityHeaderType();
        var creationDate = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");
        var nonce = (new Random().Next(0, int.MaxValue)).ToString();
        var hashedPassword = GetSha1(_config.GetPassword());
        var concatednatedDigestInput = string.Concat(nonce, creationDate, Encoding.Default.GetString(hashedPassword));
        var digest = GetSha1(concatednatedDigestInput);
        var passwordDigest = Convert.ToBase64String(digest);
        var encodedNonce = Convert.ToBase64String(Encoding.Default.GetBytes(nonce));
        var doc = new XmlDocument();
        using (var writer = doc.CreateNavigator().AppendChild())
        {
            writer.WriteStartDocument();
            writer.WriteStartElement("wsse", "Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
            writer.WriteStartElement("wsse", "UsernameToken", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
            writer.WriteElementString("wsse", "Username", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", _config.GetUsername());
            writer.WriteElementString("wsse", "Password", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", passwordDigest);
            writer.WriteElementString("wsse", "Nonce", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", encodedNonce);
            writer.WriteElementString("wsse", "Created", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", creationDate);
            writer.WriteEndElement();
            writer.WriteEndElement();
            writer.WriteEndDocument();
            writer.Flush();
        }
        if (doc.DocumentElement != null)
        {
            doc.DocumentElement.RemoveAllAttributes();
            var headers = doc.DocumentElement.ChildNodes.Cast<XmlElement>().ToArray();
            securityHeader.Any = headers;
        }
        return securityHeader;
    }
    private integrationHeader GetIntegrationHeader()
    {
        var header = new integrationHeader();
        var created = DateTime.Now;
        var createdAt = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");
        header.dateTime = created;
        header.version = int.Parse(_config.GetVersion());
        header.dateTimeSpecified = true;
        header.versionSpecified = true;
        var idStructure = new identificationStructure {applicationId = _config.GetApplicationId()};
        var nonce = new Random().Next(0, int.MaxValue).ToString();
        idStructure.transactionId = CalculateMd5Hash(nonce + createdAt);
        header.identification = idStructure;
        return header;
    }
    private static byte[] GetSha1(string input)
    {
        return SHA1Managed.Create().ComputeHash(Encoding.Default.GetBytes(input));
    }
    public string CalculateMd5Hash(string input)
    {
        // step 1, calculate MD5 hash from input
        var md5 = MD5.Create();
        var inputBytes = Encoding.ASCII.GetBytes(input);
        var hash = md5.ComputeHash(inputBytes);
        // step 2, convert byte array to hex string
        var sb = new StringBuilder();
        foreach (var t in hash)
        {
            sb.Append(t.ToString("X2"));
        }
        return sb.ToString();
    }
    /*
     * Check Response Footer For Errors & Warnings From Service
     * If Error Return True So We Can Inform File maker Of Error
     * Ignore Warnings For Now
     * 
     */
    private static void CheckErrorsAndWarnings(integrationFooter integrationFooter)
    {
        if (integrationFooter != null)
        {
            if (integrationFooter.errors != null && integrationFooter.errors.Length > 0)
            {
                var errors = integrationFooter.errors;
                foreach (var error in errors)
                {
                    MessageBox.Show("Royal Mail Request Error: " + error.errorDescription + ". " + error.errorResolution, "Royal Mail Request Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
                }
                if (errors.Length > 0)
                {
                    return;
                }
            }
            if (integrationFooter.warnings != null && integrationFooter.warnings.Length > 0)
            {
                var warnings = integrationFooter.warnings;
                foreach (var warning in warnings)
                {
                    MessageBox.Show("Royal Mail Request Warning: " + warning.warningDescription + ". " + warning.warningResolution, "Royal Mail Request Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1);
                }
            }
        }
    }
    /*
     * Show Message Box With SOAP Error If We Receive A Fault Code Back From Service
     *
     */
    private static void ShowSoapException(FaultException e)
    {
        var message = e.CreateMessageFault();
        var errorDetail = message.GetDetail<XmlElement>();
        var errorDetails = errorDetail.ChildNodes;
        var fullErrorDetails = "";
        for (var i = 0; i < errorDetails.Count; i++)
        {
            var xmlNode = errorDetails.Item(i);
            if (xmlNode != null)
                fullErrorDetails += xmlNode.Name + ": " + xmlNode.InnerText + "'n";
        }
        MessageBox.Show("An Error Occured With Royal Mail Service: " + message.Reason + "'n'n" + fullErrorDetails, "Royal Mail SOAP Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
    }
    public createShipmentResponse SendCreateShipmentRequest(CreateShipmentForm shippingForm)
    {
        var client = GetProxy();
        try
        {
            var request = new createShipmentRequest {integrationHeader = GetIntegrationHeader()};
            var shipment = new requestedShipment();
            // Shipment Type Code (Delivery or Return)
            var shipmentType = new referenceDataType {code = shippingForm.ShippingType};
            shipment.shipmentType = shipmentType;
            // Service Type Code (1:24H 1st Class, 2: 48H 2nd Class, D: Special Delivery Guaranteed, H: HM Forces (BFPO), I: International, R: Tracked Returns, T: Tracked Domestic)
            var serviceType = new referenceDataType {code = shippingForm.ServiceType};
            shipment.serviceType = serviceType;
            // Service Offering (See Royal Mail Service Offering Type Codes. Too Many To List)
            var serviceOfferingTypeContainer = new serviceOfferingType();
            var serviceOffering = new referenceDataType {code = shippingForm.ServiceOffering};
            serviceOfferingTypeContainer.serviceOfferingCode = serviceOffering;
            shipment.serviceOffering = serviceOfferingTypeContainer;
            // Service Format Code
            var serviceFormatTypeContainer = new serviceFormatType();
            var serviceFormat = new referenceDataType {code = shippingForm.ServiceFormat};
            serviceFormatTypeContainer.serviceFormatCode = serviceFormat;
            shipment.serviceFormat = serviceFormatTypeContainer;
            // Shipping Date
            shipment.shippingDate = shippingForm.ShippingDate;
            shipment.shippingDateSpecified = true;
            shipment.signature = true;
            shipment.signatureSpecified = true;
            // Sender Reference Number (e.g. Invoice Number or RA Number)
            shipment.senderReference = shippingForm.InvoiceNumber;
            /*
             * Service Enhancements
            */
            var serviceEnhancements = new List<serviceEnhancementType>();
            shipment.serviceEnhancements = serviceEnhancements.ToArray();

            /*
             * Recipient Contact Details
            */
            var recipientContact = new contact();
            recipientContact.complementaryName = shippingForm.Company;
            recipientContact.name = shippingForm.Name;
            if(!shippingForm.EmailAddress.Equals("")) {
                var email = new digitalAddress {electronicAddress = shippingForm.EmailAddress};
                recipientContact.electronicAddress = email;
            }
            if(!shippingForm.MobileNumber.Equals("")) {
                var tel = new telephoneNumber();
                var phoneRegex = new Regex(@"[^'d]");
                tel.telephoneNumber1 = phoneRegex.Replace(shippingForm.MobileNumber, "");
                tel.countryCode = "00" + shippingForm.CountryDiallingCode;
                recipientContact.telephoneNumber = tel;
            }
            shipment.recipientContact = recipientContact;
            /*
             * Recipient Address
             * 
            */
            var recipientAddress = new address
            {
                addressLine1 = shippingForm.AddressLine1,
                addressLine2 = shippingForm.AddressLine2,
                addressLine3 = shippingForm.AddressLine3,
                addressLine4 = shippingForm.County,
                postTown = shippingForm.Town
            };
            var country = new countryType();
            var countryCode = new referenceDataType { code = shippingForm.CountryCode };
            country.countryCode = countryCode;
            recipientAddress.country = country;
            recipientAddress.postcode = shippingForm.PostCode;
            recipientAddress.stateOrProvince = new stateOrProvinceType {stateOrProvinceCode = new referenceDataType()};
            shipment.recipientAddress = recipientAddress;
            // Shipment Items
            var items = new List<item> ();
            foreach(var i in shippingForm.Items) {
                var item = new item
                {
                    numberOfItems = i.Products.Count.ToString(),
                    weight = new dimension
                    {
                        value = i.Weight*1000,
                        unitOfMeasure = new unitOfMeasureType {unitOfMeasureCode = new referenceDataType {code = "g"}}
                    }
                };
                items.Add(item);
            }
            if (shippingForm.ServiceType.Contains("international"))
            {
                var internationalInfo = new internationalInfo
                {
                    shipperExporterVatNo = _config.GetVatNumber(),
                    documentsOnly = false,
                    shipmentDescription = "Invoice Number: " + shippingForm.InvoiceNumber,
                    invoiceDate = DateTime.Now,
                    termsOfDelivery = "EXW",
                    invoiceDateSpecified = true,
                    purchaseOrderRef = shippingForm.InvoiceNumber
                };
                var parcels = new List<parcel>();
                foreach (var i in shippingForm.Items)
                {
                    var parcel = new parcel
                    {
                        weight = new dimension
                        {
                            value = i.Weight*1000,
                            unitOfMeasure = new unitOfMeasureType
                            {
                                unitOfMeasureCode = new referenceDataType {code = "g"}
                            }
                        },
                        invoiceNumber = shippingForm.InvoiceNumber,
                        purposeOfShipment = new referenceDataType {code = "31"}
                    };

                    var contents = new List<contentDetail>();
                    foreach (var product in i.Products)
                    {
                        var contentDetail = new contentDetail
                        {
                            articleReference = product.Sku,
                            countryOfManufacture = new countryType
                            {
                                countryCode = new referenceDataType
                                {
                                    code = product.CountryOfManufacture
                                }
                            },
                            currencyCode = new referenceDataType {code = product.CurrencyCode},
                            description = product.Name,
                            unitQuantity = product.Qty.ToString(),
                            unitValue = product.Price,
                            unitWeight = new dimension
                            {
                                value = Convert.ToSingle(product.Weight*1000),
                                unitOfMeasure = new unitOfMeasureType
                                {
                                    unitOfMeasureCode = new referenceDataType {code = "g"}
                                }
                            }
                        };
                        contents.Add(contentDetail);
                    }
                    //Parcel.contentDetails = Contents.ToArray();
                    parcels.Add(parcel);
                }
                internationalInfo.parcels = parcels.ToArray();
                shipment.internationalInfo = internationalInfo;
            }
            else
            {
                shipment.items = items.ToArray();
            }
            request.requestedShipment = shipment;
            var response = client.createShipment(GetSecurityHeaderType(), request);
            // Show Errors And Warnings
            CheckErrorsAndWarnings(response.integrationFooter);
            return response;
        }
        catch (TimeoutException e)
        {
            client.Abort();
            MessageBox.Show("Request Timed Out: " + e.Message, "Request Timeout", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
        }
        catch (FaultException e)
        {
            client.Abort();
            ShowSoapException(e);
        }
        catch (CommunicationException e)
        {
            client.Abort();
            MessageBox.Show("A communication error has occurred: " + e.Message + " - " + e.StackTrace, "Communication Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
        }
        catch (Exception e)
        {
            client.Abort();
            MessageBox.Show(e.Message, "Royal Mail Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
        }
        return null;
    }
}

皇家邮件消息.cs

class RoyalMailMessage : Message
{
    public Message _message;
    public RoyalMailMessage(Message message)
    {
        _message = message;
    }
    public override MessageHeaders Headers
    {
        get
        {
            return _message.Headers;
        }
    }
    public override MessageProperties Properties
    {
        get
        {
            return _message.Properties;
        }
    }
    public override MessageVersion Version
    {
        get
        {
            return _message.Version;
        }
    }
    protected override void OnWriteStartBody(XmlDictionaryWriter writer)
    {
        writer.WriteStartElement("Body", "http://schemas.xmlsoap.org/soap/envelope/");
    }
    protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
    {
        _message.WriteBodyContents(writer);
    }
    protected override void OnWriteStartEnvelope(XmlDictionaryWriter writer)
    {
        writer.WriteStartElement("soapenv", "Envelope", "http://schemas.xmlsoap.org/soap/envelope/");
        writer.WriteAttributeString("xmlns", "v2", null, "http://www.royalmailgroup.com/api/ship/V2");
        writer.WriteAttributeString("xmlns", "v1", null, "http://www.royalmailgroup.com/integration/core/V1");
        writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance");
        writer.WriteAttributeString("xmlns", "xsd", null, "http://www.w3.org/2001/XMLSchema");
    }
}

皇家邮件消息格式化程序.cs

public class RoyalMailMessageFormatter : IClientMessageFormatter
{
    private readonly IClientMessageFormatter _formatter;
    public RoyalMailMessageFormatter(IClientMessageFormatter formatter)
    {
        _formatter = formatter;
    }
    public object DeserializeReply(Message message, object[] parameters)
    {
        return _formatter.DeserializeReply(message, parameters);
    }
    public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
    {
        var message = _formatter.SerializeRequest(messageVersion, parameters);
        return new RoyalMailMessage(message);
    }
}

皇家邮件IEndpointBehavior.cs

internal class RoyalMailIEndpointBehavior : IOperationBehavior
{
    public void ApplyClientBehavior(OperationDescription description, ClientOperation proxy)
    {
        proxy.Formatter = new RoyalMailMessageFormatter(proxy.Formatter);
    }
    public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
    {
    }
    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
    {
    }
    public void Validate(OperationDescription operationDescription)
    {
    }
}

皇家邮政运输 API C#

您遇到的错误基本上是因为证书。

话虽如此,我认为您应该使用 API 的 v2,因为尽管它仍然很糟糕,但有一些示例,您不需要使用证书。

Rick Strahl 已成功更改 v2 版本中的命名空间,请参阅此处 https://weblog.west-wind.com/posts/2016/Apr/02/Custom-Message-Formatting-in-WCF-to-add-all-Namespaces-to-the-SOAP-Envelope 。

有一个新的皇家邮政运输API 2可用,在我浪费了很多时间尝试开发与皇家邮政的集成之后,我终于找到了一种方法。我正在 git 中分享我的项目。https://github.com/americoa/RoyalMailShippingAPIV2