IRS-A2A BulkRequestTransmitter message not formmatted properly and/or cannot be interpreted

后端 未结 6 1560
一个人的身影
一个人的身影 2020-12-01 17:47

I am receiving the following error when attempting to submit through the BulkRequestTransmitter Web Service. The Composition Guide is less than helpful as far as this messa

6条回答
  •  一个人的身影
    2020-12-01 17:55

    First off, a quick disclaimer. This answer was made possible by the great stuff provided by fatherOfWine, Russ, and Bon across this and other SO questions. All I really did was combine a bunch of stuff from them them and hack through the issues I still had until it worked. More importantly, the code provided here is BAD and should probably not be used as-is. I plan on cleaning this up quite a bit now that I know what works, and I'd recommend anyone making use of it to do the same. A big thing that will likely jump out to anyone looking at this is the plethora of static variables I used as a quick hack to get to things all through the pipeline. Seriously, don't use this as-is in production, it is the product of many hours of just throwing things at the wall until something stuck, but it should provide a good starting point to get something better going.


    There's too much code to really include it all here, so I'll just go through some highlights and general discoveries then include a link to the VS solution.

    1. Make sure you have your TCC and various other IDs setup already, that you've purchased the right kind of certificate (Page 41 of this doc) and that you've registered the cert properly (see this doc)
    2. I found that removing all the CR-LFs in the soap envelope was necessary to get the message to be accepted. With them in there I would gets faults on some elements of "unexpected child elements" or something like that.
    3. The documentation contradicts itself in several place (see pages 74 and 84 and look at what they say the BulkExchangeFile element should contain for an example) and the wsdl/xsds are just straight up wrong as well. Maybe I just had old ones somehow, but I had to make changes and try things until I found what the service on their side would actually accept.
    4. It is very important that you add the keyinfo to the signedxml section properly, that all of your references there are built right and include the proper InclusiveNamespaces lists, and that once you call ComputeSignature the only change you make to your envelope is to add the signature element to it.
    5. Speaking of the signature element, if it appears after the timestamp element inside the security element the IRS system will return a fault. It has to be first.
    6. Because the namespace prefixes were so important when it came to generating the signature references, I went the route of building the envelope xml by hand so I could be certain that everything matched up exactly with what they wanted. Even then, there were several elements whose prefixes I had to change as I tested because what the XSD or some page in the docs said it should be was not what their service actually wanted. Luckily the faults returned by the service actually provided some help by indicating which namespace it was expecting a value from.
    7. Outside of getting all the gzip and MTOM stuff setup (again, thanks a million to fatherOfWine for that help) the bulk of what finally worked for me is done in single general-use class (which I cleverly called "General"). Again, this is bad code and was the product of just needing to get something (anything!) to work properly. I'll go ahead and include it here in the answer though in case it provides a quick "ah ha!" to anyone else working this problem.

      using System;
      using System.IO;
      using System.Net;
      using System.Security.Cryptography.X509Certificates;
      using System.Security.Cryptography.Xml;
      using System.ServiceModel;
      using System.ServiceModel.Channels;
      using System.Text;
      using System.Xml;
      using IrsAcaClient.ACABulkRequestTransmitterService;
      
      namespace IrsAcaClient
      {
      public class General
      {
          /*****************************************************
           * 
           * What I'm doing here (with static vars) is VERY BAD but this whole thing is just a dirty hack for now.
           * Hopefully I can clean this up later.
           * - JRS 2016-05-10
           * 
           *****************************************************/
          public const string SecurityTimestampStringFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffZ";
      
          public const string EnvelopeContentID = "";
      
          public static string AttachmentFilePath;
      
          public static string AttachmentFileName { get { return Path.GetFileName(General.AttachmentFilePath); } }
          public static string AttachmentContentID {get { return string.Format("<{0}>", General.AttachmentFileName); }}
      
          public const string MIMEBoundary = "MIME_boundary";
      
          public static string TCCode;
      
          public static Guid TransmissionGuid;
      
          public static string UniqueTransmissionId
          {
              get { return string.Format("{0}:SYS12:{1}::T", TransmissionGuid, TCCode); }
          }
      
          public static string SecurityTimeStampWsuId;
          public static string ManifestWsuId;
          public static string BusinessHeaderWsuId;
          public static string SignatureWsuId;
      
          public static string CertificatePath;
          public static string CertificatePassword;
      
          public static DateTime SecurityTimestampUTC;
      
          private static string _replacementSoapEnvelope;
      
          public static string ReplacementSoapEnvelope{get { return _replacementSoapEnvelope; }}
      
          private static void GenerateReference(string elementID, string inclusivePrefixList, SignedXmlWithId xSigned)
          {
              var reference = new Reference()
              {
                  Uri = "#" + elementID
              };
      
              XmlDsigExcC14NTransform env = new XmlDsigExcC14NTransform();
              env.InclusiveNamespacesPrefixList = inclusivePrefixList;
              reference.AddTransform(env);
      
              xSigned.AddReference(reference);
          }
      
          public static string GetAttachmentFileContent()
          {
              //probably not ideal
              return File.ReadAllText(AttachmentFilePath);
          }
      
          public static string GetFileName()
          {
              //TODO: this may need to be tweaked slightly from the real filename
              return General.AttachmentFileName;
          }
      
          public static string GenerateWsuId(string prefix)
          {
              return string.Format("{0}-{1}", prefix, Guid.NewGuid().ToString().Replace("-", "").ToUpper());
          }
      
          internal static void GenerateReplacementSoapEnvelope(ACABulkRequestTransmitterService.SecurityHeaderType securityHeader, ACABulkRequestTransmitterService.ACABulkBusinessHeaderRequestType businessHeader, ACABulkRequestTransmitterService.ACATrnsmtManifestReqDtlType manifest, ACABulkRequestTransmitterService.ACABulkRequestTransmitterType bulkTrans)
          {
              //load the base envelope xml
              var doc = new XmlDocument();
              doc.PreserveWhitespace = false;
              doc.Load("BaseSoapEnvelope.xml");
      
              /* Need a bunch of namespaces defined
               * xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
               * xmlns:urn="urn:us:gov:treasury:irs:ext:aca:air:7.0"
               * xmlns:urn1="urn:us:gov:treasury:irs:common"
               * xmlns:urn2="urn:us:gov:treasury:irs:msg:acabusinessheader"
               * xmlns:urn3="urn:us:gov:treasury:irs:msg:irsacabulkrequesttransmitter"
               * xmlns:wsa="http://www.w3.org/2005/08/addressing"
               * xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
               * xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
               * xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
               * xmlns:xop="http://www.w3.org/2004/08/xop/include"
               */
              XmlNamespaceManager nsMgr = new XmlNamespaceManager(doc.NameTable);
              nsMgr.AddNamespace("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
              nsMgr.AddNamespace("urn", "urn:us:gov:treasury:irs:ext:aca:air:7.0");
              nsMgr.AddNamespace("urn1", "urn:us:gov:treasury:irs:common");
              nsMgr.AddNamespace("urn2", "urn:us:gov:treasury:irs:msg:acabusinessheader");
              nsMgr.AddNamespace("urn3", "urn:us:gov:treasury:irs:msg:irsacabulkrequesttransmitter");
              nsMgr.AddNamespace("wsa", "http://www.w3.org/2005/08/addressing");
              nsMgr.AddNamespace("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
              nsMgr.AddNamespace("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
              nsMgr.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#");
              nsMgr.AddNamespace("xop","http://www.w3.org/2004/08/xop/include");
      
      
              //start replacing values in it
              //for securityHeader, should have the following
              /*
               * securityHeader.Signature.Id
               * securityHeader.Timestamp.Id
               * securityHeader.Timestamp.Created.Value
               * securityHeader.Timestamp.Expires.Value
               */
              //doc.SelectSingleNode("//wsse:Security/ds:Signature", nsMgr).Attributes["Id"].Value = securityHeader.Signature.Id;
              doc.SelectSingleNode("//wsse:Security/wsu:Timestamp", nsMgr).Attributes["wsu:Id"].Value = securityHeader.Timestamp.Id;
              doc.SelectSingleNode("//wsse:Security/wsu:Timestamp/wsu:Created", nsMgr).InnerText = securityHeader.Timestamp.Created.Value;
              doc.SelectSingleNode("//wsse:Security/wsu:Timestamp/wsu:Expires", nsMgr).InnerText = securityHeader.Timestamp.Expires.Value;
      
      
              //for businessHeader, should have the following
              /*
               * businessHeader.UniqueTransmissionId
               * businessHeader.Timestamp
               * businessHeader.Id 
               */
              doc.SelectSingleNode("//urn2:ACABusinessHeader", nsMgr).Attributes["wsu:Id"].Value = businessHeader.Id;
              doc.SelectSingleNode("//urn2:ACABusinessHeader/urn:UniqueTransmissionId", nsMgr).InnerText = businessHeader.UniqueTransmissionId;
              doc.SelectSingleNode("//urn2:ACABusinessHeader/urn1:Timestamp", nsMgr).InnerText = businessHeader.Timestamp.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssZ");
      
      
              //for manifest, should have the following, some of which will need some conversions
              /*
               * manifest.Id
               * manifest.BinaryFormatCd - convert from enum
               * manifest.PaymentYr
               * manifest.PriorYearDataInd - convert from enum
               * manifest.EIN
               * manifest.TransmissionTypeCd - convert from enum
               * manifest.TestFileCd
               * manifest.TransmitterNameGrp.BusinessNameLine1Txt
               * manifest.CompanyInformationGrp.CompanyNm
               * manifest.CompanyInformationGrp.MailingAddressGrp.Item.AddressLine1Txt
               * manifest.CompanyInformationGrp.MailingAddressGrp.Item.CityNm
               * manifest.CompanyInformationGrp.MailingAddressGrp.Item.USStateCd - convert from enum
               * manifest.CompanyInformationGrp.MailingAddressGrp.Item.USZIPCd
               * manifest.CompanyInformationGrp.ContactNameGrp.PersonFirstNm
               * manifest.CompanyInformationGrp.ContactNameGrp.PersonLastNm
               * manifest.CompanyInformationGrp.ContactPhoneNum
               * manifest.VendorInformationGrp.VendorCd
               * manifest.VendorInformationGrp.ContactNameGrp.PersonFirstNm
               * manifest.VendorInformationGrp.ContactNameGrp.PersonLastNm
               * manifest.VendorInformationGrp.ContactPhoneNum
               * manifest.TotalPayeeRecordCnt
               * manifest.TotalPayerRecordCnt
               * manifest.SoftwareId
               * manifest.FormTypeCd - convert from enum
               * manifest.ChecksumAugmentationNum
               * manifest.AttachmentByteSizeNum
               * manifest.DocumentSystemFileNm
               */
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl", nsMgr).Attributes["wsu:Id"].Value = manifest.Id;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:PaymentYr", nsMgr).InnerText = manifest.PaymentYr;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:PriorYearDataInd", nsMgr).InnerText = manifest.PriorYearDataInd.GetXmlEnumAttributeValueFromEnum();
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn1:EIN", nsMgr).InnerText = manifest.EIN;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:TransmissionTypeCd", nsMgr).InnerText = manifest.TransmissionTypeCd.ToString();
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:TestFileCd", nsMgr).InnerText = manifest.TestFileCd;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:TransmitterNameGrp/urn:BusinessNameLine1Txt", nsMgr).InnerText = manifest.TransmitterNameGrp.BusinessNameLine1Txt;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:CompanyInformationGrp/urn:CompanyNm", nsMgr).InnerText = manifest.CompanyInformationGrp.CompanyNm;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:CompanyInformationGrp/urn:MailingAddressGrp/urn:USAddressGrp/urn:AddressLine1Txt", nsMgr).InnerText = ((USAddressGrpType)manifest.CompanyInformationGrp.MailingAddressGrp.Item).AddressLine1Txt;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:CompanyInformationGrp/urn:MailingAddressGrp/urn:USAddressGrp/urn1:CityNm", nsMgr).InnerText = ((USAddressGrpType)manifest.CompanyInformationGrp.MailingAddressGrp.Item).CityNm;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:CompanyInformationGrp/urn:MailingAddressGrp/urn:USAddressGrp/urn:USStateCd", nsMgr).InnerText = ((USAddressGrpType)manifest.CompanyInformationGrp.MailingAddressGrp.Item).USStateCd.ToString();
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:CompanyInformationGrp/urn:MailingAddressGrp/urn:USAddressGrp/urn1:USZIPCd", nsMgr).InnerText = ((USAddressGrpType)manifest.CompanyInformationGrp.MailingAddressGrp.Item).USZIPCd;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:CompanyInformationGrp/urn:ContactNameGrp/urn:PersonFirstNm", nsMgr).InnerText = manifest.CompanyInformationGrp.ContactNameGrp.PersonFirstNm;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:CompanyInformationGrp/urn:ContactNameGrp/urn:PersonLastNm", nsMgr).InnerText = manifest.CompanyInformationGrp.ContactNameGrp.PersonLastNm;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:CompanyInformationGrp/urn:ContactPhoneNum", nsMgr).InnerText = manifest.CompanyInformationGrp.ContactPhoneNum;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:VendorInformationGrp/urn:VendorCd", nsMgr).InnerText = manifest.VendorInformationGrp.VendorCd;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:VendorInformationGrp/urn:ContactNameGrp/urn:PersonFirstNm", nsMgr).InnerText = manifest.VendorInformationGrp.ContactNameGrp.PersonFirstNm;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:VendorInformationGrp/urn:ContactNameGrp/urn:PersonLastNm", nsMgr).InnerText = manifest.VendorInformationGrp.ContactNameGrp.PersonLastNm;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:VendorInformationGrp/urn:ContactPhoneNum", nsMgr).InnerText = manifest.VendorInformationGrp.ContactPhoneNum;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:TotalPayeeRecordCnt", nsMgr).InnerText = manifest.TotalPayeeRecordCnt;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:TotalPayerRecordCnt", nsMgr).InnerText = manifest.TotalPayerRecordCnt;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:SoftwareId", nsMgr).InnerText = manifest.SoftwareId;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:FormTypeCd", nsMgr).InnerText = manifest.FormTypeCd.GetXmlEnumAttributeValueFromEnum();
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn1:BinaryFormatCd", nsMgr).InnerText = manifest.BinaryFormatCd.GetXmlEnumAttributeValueFromEnum();
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn1:ChecksumAugmentationNum", nsMgr).InnerText = manifest.ChecksumAugmentationNum;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn1:AttachmentByteSizeNum", nsMgr).InnerText = manifest.AttachmentByteSizeNum;
              doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:DocumentSystemFileNm", nsMgr).InnerText = manifest.DocumentSystemFileNm;
      
      
              //for bulkTrans, should have the following
              /*
               * bulkTrans.BulkExchangeFile.Include.href
               */
              doc.SelectSingleNode("//urn3:ACABulkRequestTransmitter/urn1:BulkExchangeFile/xop:Include", nsMgr).Attributes["href"].Value = bulkTrans.BulkExchangeFile.Include.href;
      
      
              //now do some more security setup
              var cert = new X509Certificate2(CertificatePath, CertificatePassword, X509KeyStorageFlags.MachineKeySet);
      
              var exported = cert.Export(X509ContentType.Cert, CertificatePassword);
              var base64 = Convert.ToBase64String(exported);
      
              //now compute all the signing stuff
              var xSigned = new SignedXmlWithId(doc);
              xSigned.Signature.Id = securityHeader.Signature.Id;
      
              // Add the key to the SignedXml document.
              xSigned.SigningKey = cert.PrivateKey;
              xSigned.Signature.Id = SignatureWsuId;
              xSigned.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NWithCommentsTransformUrl;
      
              var keyInfo = new KeyInfo
              {
                  Id = GenerateWsuId("KI")
              };
      
              //need to get the keyinfo into the signed xml stuff before we compute sigs, and because it is using some stuff that
              //doesn't appear to be supported out of the box we'll work around it by adding a node directly
              var sbKeyInfo = new StringBuilder();
              sbKeyInfo.Append("");
              sbKeyInfo.Append("");
              sbKeyInfo.Append("" + base64.ToString());
              sbKeyInfo.Append("");
              sbKeyInfo.Append("");
              sbKeyInfo.Append("");
              XmlDocument tempDoc = new XmlDocument();
              tempDoc.LoadXml(sbKeyInfo.ToString());
      
              keyInfo.AddClause(new KeyInfoNode((XmlElement)tempDoc.FirstChild.FirstChild));
      
              xSigned.KeyInfo = keyInfo;
      
              GenerateReference(SecurityTimeStampWsuId, "wsse wsa soapenv urn urn1 urn2 urn3", xSigned);
              GenerateReference(BusinessHeaderWsuId, "wsa soapenv urn urn1 urn3", xSigned);
              GenerateReference(ManifestWsuId, "wsa soapenv urn1 urn2 urn3", xSigned);
      
              // Compute the Signature.
              xSigned.ComputeSignature();
      
              //signing stuff must come before the timestamp or the IRS service complains
              doc.SelectSingleNode("//wsse:Security", nsMgr).InsertBefore(xSigned.GetXml(), doc.SelectSingleNode("//wsse:Security", nsMgr).FirstChild);
      
              //
              _replacementSoapEnvelope = doc.OuterXml;
          }
      
          public static ACABulkRequestTransmitterResponseType Run(ACABulkRequestTransmitterService.SecurityHeaderType securityHeader, ACABulkRequestTransmitterService.ACABulkBusinessHeaderRequestType businessHeader, ACABulkRequestTransmitterService.ACATrnsmtManifestReqDtlType manifest, ACABulkRequestTransmitterService.ACABulkRequestTransmitterType bulkTrans)
          {
              //had some issues early on with the cert on the IRS server, this should probably be removed and retested without it
              ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, errors) => true;
              ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3 |
                                                     SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
      
              var acaSecurityHeader = new ACABulkRequestTransmitterService.TransmitterACASecurityHeaderType(); //leave this empty for transmitting via ISS-A2A
      
              var requestClient = new ACABulkRequestTransmitterService.BulkRequestTransmitterPortTypeClient("BulkRequestTransmitterPort");
      
              requestClient.Endpoint.Contract.ProtectionLevel = System.Net.Security.ProtectionLevel.None;
              //var vs = requestClient.Endpoint.Behaviors.Where((i) => i.GetType().Namespace.Contains("VisualStudio"));
              //if (vs != null)
              //    requestClient.Endpoint.Behaviors.Remove((System.ServiceModel.Description.IEndpointBehavior)vs.Single());
      
              //generate the real envelope we want
              GenerateReplacementSoapEnvelope(securityHeader, businessHeader, manifest, bulkTrans);
      
              using (var scope = new OperationContextScope(requestClient.InnerChannel))
              {
      
                  //Adding proper HTTP Header to an outgoing requqest.
                  HttpRequestMessageProperty requestMessage = new HttpRequestMessageProperty();
      
                  requestMessage.Headers["Content-Encoding"] = "gzip";
                  requestMessage.Headers["Content-Type"] = string.Format(@"multipart/related; type=""application/xop+xml"";start=""{0}"";start-info=""text/xml"";boundary=""{1}""", General.EnvelopeContentID, General.MIMEBoundary);
                  OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestMessage;
      
                  var response = requestClient.BulkRequestTransmitter(acaSecurityHeader,
                                                                      securityHeader,
                                                                      ref businessHeader,
                                                                      manifest,
                                                                      bulkTrans);
      
                  //we got a response!  now do something with it
                  return response;
      
              }
          }
      }
      

    Here is the complete solution, just needs all of your own data supplied (including the complete attachment file with all the payee and payer records, which is outside the scope of this but should be pretty easy to generate). Also note that this is submission of forms only, not status checks. When I get that working I'll try to remember to return and update this answer (but if someone else already has it and wants to share, that'd be pretty rad as well).


    Edit for Status Service

    I've combined a cleaned up version of the classes generated from the wsdl and my own junk code to get messages through and process the responses. Note that this isn't 100% tested yet, needs sanity checks, etc. but like the previous stuff should at least help anyone else struggling with this mess. Usage here is pretty straightforward:

    var statusResponse = StatusService.CheckStatus(receipt, tCCode, certificatePath, certificatePassword, "https://la.www4.irs.gov/airp/aca/a2a/1095BC_Status_Request_AATS2016");
    

    And here is the full class (with bonus generated classes namespace):

    See my second answer for the status service code

提交回复
热议问题