BingAds / BingAds-Python-SDK

Other
116 stars 162 forks source link

CustomerBillingService:AddInsertionOrderRequest creates double nested InsertionOrder #94

Closed chadn closed 6 years ago

chadn commented 6 years ago

When debugging our code I noticed that the SOAP request generated by 11.12.x contains an InsertionOrder inside another InsertionOrder. The effect is that the response from bingads server contains error code 1001, The user is not authorized to perform this action. When you manually submit the same SOAP request but with only one level of InsertionOrder, it works as expected.

The problem appears to be with this SDK. I've developed a workaround that has 2 parts. First I'm using InsertionOrderTagDeduplicatorPlugin, a suds plugin to edit context.envelope on sending. Second is a fix to SDK via #93 to enable the plugin to work.

Here's my calling code, simplified - an error is thrown at the last line without the plugin or without #93.

from bingads.service_client import ServiceClient
logging.getLogger('suds.client').setLevel(logging.DEBUG)
logging.getLogger('suds.transport').setLevel(logging.DEBUG)

class InsertionOrderTagDeduplicatorPlugin(MessagePlugin):
    def sending(self, context):
        context.envelope = context.envelope.replace(b"<ns0:InsertionOrder><ns0:InsertionOrder>",    b"<ns0:InsertionOrder>")
        context.envelope = context.envelope.replace(b"</ns0:InsertionOrder></ns0:InsertionOrder>", b"</ns0:InsertionOrder>")
        context.envelope = context.envelope.replace(b"<ns1:InsertionOrder><ns1:InsertionOrder>",    b"<ns1:InsertionOrder>")
        context.envelope = context.envelope.replace(b"</ns1:InsertionOrder></ns1:InsertionOrder>", b"</ns1:InsertionOrder>")
        context.envelope = context.envelope.replace(b"<ns2:InsertionOrder><ns2:InsertionOrder>",    b"<ns2:InsertionOrder>")
        context.envelope = context.envelope.replace(b"</ns2:InsertionOrder></ns2:InsertionOrder>", b"</ns2:InsertionOrder>")
        context.envelope = context.envelope.replace(b"<ns3:InsertionOrder><ns3:InsertionOrder>",    b"<ns3:InsertionOrder>")
        context.envelope = context.envelope.replace(b"</ns3:InsertionOrder></ns3:InsertionOrder>", b"</ns3:InsertionOrder>")
        return context

....

self.master_customer_billing_service = ServiceClient(
    service='CustomerBillingService',
    authorization_data=self.authorization_data,
    environment=Config.environment,
    version=11,
    plugins=[InsertionOrderTagDeduplicatorPlugin()]
)

request = self.master_customer_billing_service.factory.create("AddInsertionOrderRequest")
request.InsertionOrder.AccountId = self.account_id
request.InsertionOrder.BookingCountryCode = country_code
request.InsertionOrder.Comment = comment or "--No Comment--"
request.InsertionOrder.StartDate = start_date.format("YYYY-MM-DD")
request.InsertionOrder.EndDate = end_date
request.InsertionOrder.PurchaseOrder = po_number
request.InsertionOrder.Name = budget_name.strip()
request.InsertionOrder.SpendCapAmount = budget
request.InsertionOrder.Status = None

nested_partial_errors = self.master_customer_billing_service.AddInsertionOrder(request)

Here's the SOAP request with InsertionOrder inside another InsertionOrder

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:tns="https://bingads.microsoft.com/Billing/v11"
  xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:ns1="https://bingads.microsoft.com/Customer/v11/Entities"
  xmlns:ns2="https://bingads.microsoft.com/Billing/v11">
  <SOAP-ENV:Header>
    <tns:AuthenticationToken>____SNIP____</tns:AuthenticationToken>
    <tns:DeveloperToken>____SNIP____</tns:DeveloperToken>
  </SOAP-ENV:Header>
  <ns0:Body>
    <ns2:AddInsertionOrderRequest>
      <ns2:InsertionOrder>
        <ns2:InsertionOrder>
          <ns1:AccountId>152028363</ns1:AccountId>
          <ns1:BookingCountryCode>US</ns1:BookingCountryCode>
          <ns1:Comment>--No Comment--</ns1:Comment>
          <ns1:EndDate>2018-08-31</ns1:EndDate>
          <ns1:SpendCapAmount>500.0</ns1:SpendCapAmount>
          <ns1:StartDate>2018-08-03</ns1:StartDate>
          <ns1:Name>2018-08-01</ns1:Name>
          <ns1:PurchaseOrder></ns1:PurchaseOrder>
        </ns2:InsertionOrder>
      </ns2:InsertionOrder>
    </ns2:AddInsertionOrderRequest>
  </ns0:Body>
</SOAP-ENV:Envelope>

DEBUG:suds.client:HTTP failed - 500 - Internal Server Error:

<s:Envelope
  xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <s:Fault>
      <faultcode>s:Server</faultcode>
      <faultstring xml:lang="en-US">Invalid client data. Check the SOAP fault details for more information</faultstring>
      <detail>
        <ApiFault
          xmlns="https://bingads.microsoft.com/Billing/v11"
          xmlns:a="https://bingads.microsoft.com/Customer/v11/Exception"
          xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
          <TrackingId
            xmlns="https://adapi.microsoft.com">44897043-606c-472f-ac2e-146bbec382ed
          </TrackingId>
          <a:OperationErrors>
            <a:OperationError>
              <a:Code>1001</a:Code>
              <a:Details/>
              <a:Message>The user is not authorized to perform this action.</a:Message>
            </a:OperationError>
          </a:OperationErrors>
        </ApiFault>
      </detail>
    </s:Fault>
  </s:Body>
</s:Envelope>

Here's a successful SOAP request and response

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:tns="https://bingads.microsoft.com/Billing/v11"
  xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:ns1="https://bingads.microsoft.com/Customer/v11/Entities"
  xmlns:ns2="https://bingads.microsoft.com/Billing/v11">
  <SOAP-ENV:Header>
    <tns:AuthenticationToken>____SNIP____</tns:AuthenticationToken>
    <tns:DeveloperToken>____SNIP____</tns:DeveloperToken>
  </SOAP-ENV:Header>
  <ns0:Body>
    <ns2:AddInsertionOrderRequest>
      <ns2:InsertionOrder>
        <ns1:AccountId>152028363</ns1:AccountId>
        <ns1:BookingCountryCode>US</ns1:BookingCountryCode>
        <ns1:Comment>--No Comment--</ns1:Comment>
        <ns1:EndDate>2018-08-31</ns1:EndDate>
        <ns1:SpendCapAmount>500.0</ns1:SpendCapAmount>
        <ns1:StartDate>2018-08-08</ns1:StartDate>
        <ns1:Name>2018-08-01</ns1:Name>
        <ns1:PurchaseOrder></ns1:PurchaseOrder>
      </ns2:InsertionOrder>
    </ns2:AddInsertionOrderRequest>
  </ns0:Body>
</SOAP-ENV:Envelope>

<s:Envelope
  xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <h:TrackingId
      xmlns:h="https://bingads.microsoft.com/Billing/v11">2c244a4d-0e80-4f68-a4b7-e1f14ba993a7
    </h:TrackingId>
  </s:Header>
  <s:Body>
    <AddInsertionOrderResponse
      xmlns="https://bingads.microsoft.com/Billing/v11">
      <InsertionOrderId>419433954</InsertionOrderId>
      <CreateTime>2018-08-08T12:22:55</CreateTime>
    </AddInsertionOrderResponse>
  </s:Body>
</s:Envelope>
eric-urban commented 6 years ago

@chadn please try creating the InsertionOrder SUDS object and calling the operation without the 'Request' intermediate object. I was able to repro the 1001 error using factory.create("AddInsertionOrderRequest") as you did, and then got past the 1001 error with code similar to the following:

insertion_order=self.master_customer_billing_service.factory.create('ns4:InsertionOrder')
insertion_order.AccountId = self.account_id
insertion_order.BookingCountryCode = country_code
insertion_order.Comment = comment or "--No Comment--"
insertion_order.StartDate = start_date.format("YYYY-MM-DD")
insertion_order.EndDate = end_date
insertion_order.PurchaseOrder = po_number
insertion_order.Name = budget_name.strip()
insertion_order.SpendCapAmount = budget
insertion_order.Status = None
nested_partial_errors = self.master_customer_billing_service.AddInsertionOrder(InsertionOrder=insertion_order)

Caveat: I used different values for AccountId, SpendCapAmount, etc, but was able to create the valid SOAP request without nested insertion orders.

qitia commented 6 years ago

I believe this is something confusion caused by Suds.

if you try below code

from suds.client import Client url='https://clientcenter.api.sandbox.bingads.microsoft.com/Api/Billing/v12/CustomerBillingService.svc?singleWsdl' client = Client(url) print client

You will find the method for adding InsertionOrder is AddInsertionOrder(ns4:InsertionOrder InsertionOrder), this is also the same as shown in Eric's example.

AddInsertionOrder(AddInsertionOrderRequest) is not defined at all, however, if you look into java or dotnet SDK, you will need to create AddInsertionOrderRequest object.

chadn commented 6 years ago

@eric-urban that works for me! thanks for the pointer.

@qitia that does not work for me, i just get <suds.client.Client object at 0x10b55ae10> We have a complicated environment, so it may be something on our end. i tried wrapping with pformat(), vars(), dir() .. nothing yielded useful info. But I see how that approach could have led me to the same solution that @eric-urban shared.