Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to format Savon Request for Cybersource SOAP API

Trying to figure out how to format a request for Cybersource payments, using Savon and Ruby.

I've been at this for a while now, with no luck. I keep getting requestMessage not supported

I'm guessing it's due to how I'm passing in the parameters to the message body, and/or the header not being setup correctly.

Here is the expected xml for the SOAP API:

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Header>
      <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="1">
         <wsse:UsernameToken>
            <wsse:Username>yourMerchantID</wsse:Username>
            <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">yourPassword</wsse:Password>
         </wsse:UsernameToken>
      </wsse:Security>
   </soapenv:Header>
   <soapenv:Body>
      <requestMessage xmlns="urn:schemas-cybersource-com:transaction-data-N.NN">
         <merchantID>yourMerchantID</merchantID>
         <merchantReferenceCode>MRC-123</merchantReferenceCode>
         <billTo>
            <firstName>John</firstName>
            <lastName>Doe</lastName>
            <street1>1295 Charleston Road</street1>
            <city>Mountain View</city>
            <state>CA</state>
            <postalCode>94043</postalCode>
            <country>US</country>
            <email>[email protected]</email>
         </billTo>
         <item id="0">
            <unitPrice>5.00</unitPrice>
            <quantity>1</quantity>
         </item>
         <item id="1">
            <unitPrice>10.00</unitPrice>
            <quantity>2</quantity>
         </item>
         <purchaseTotals>
            <currency>USD</currency>
         </purchaseTotals>
         <card>
            <accountNumber>4111111111111111</accountNumber>
            <expirationMonth>11</expirationMonth>
            <expirationYear>2020</expirationYear>
         </card>
         <ccAuthService run="true" />
      </requestMessage>
   </soapenv:Body>
</soapenv:Envelope>   

This is the xml that I'm getting when I try to make a request.

 <?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:data="urn:schemas-cybersource-com:transaction-data:TransactionProcessor" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
  <env:Header>
    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-1">
        <wsse:Username>GiveCampusCDW</wsse:Username>
        <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">ju12trzJpnO81ZwSxPdy5htTVeOyUmICDWNmWjXuimTx9Qy+myOB4B4G8ItiJdfu37pJ6jJO2OAmCDIAoWjlgeMO5mvlYxKkVAoDEi2b2dxwLzJlkjUhhyznNzbz71b96lFRgoHGO2YpSlmT5VzTATNVt6SBUVV+iG3D3nndMwAPOmw5M+jSwP0xubZGYPV9bvuCFXI/GcNTsQYN9DWinqMjmq5zw13VgSObQFTPTn5iR+wGcOaj+1fK7IJjYlz82uRF0RHK7JTt0UIDsxULarEiJZBs+VFq9LjPblWI28365bHFs7ooNrgYJkVz+byCaswTj1wWeUecOX3L452zsQ==</wsse:Password>
      </wsse:UsernameToken>
    </wsse:Security>
  </env:Header>
  <env:Body>
    <data:requestMessage xmlns="urn:schemas-cybersource-com:transaction-data-1.129">
      <merchantID>GiveCampusCDW</merchantID>
      <merchantReferenceCode>ContributionID</merchantReferenceCode>
      <billTo>
        <firstName>Saul</firstName>
        <lastName>Goodman</lastName>
        <street1>1295 Charleston Road</street1>
        <city>Mountain View</city>
        <state>CA</state>
        <postalCode>94043</postalCode>
        <country>US</country>
        <email>[email protected]</email>
      </billTo>
      <item>
        <unitPrice>50.00</unitPrice>
        <quantity>1</quantity>
      </item>
      <purchaseTotals>
        <currency>USD</currency>
      </purchaseTotals>
      <card>
        <accountNumber>4111111111111111</accountNumber>
        <expirationMonth>12</expirationMonth>
        <expirationYear>2020</expirationYear>
      </card>
      <ccAuthService>
        <run>true</run>
      </ccAuthService>
    </data:requestMessage>
  </env:Body>
</env:Envelope>

This is the error that is returned:

ybersource::SoapException ((soap:Client) 
Element (urn:schemas-cybersource-com:transaction-data:TransactionProcessor):requestMessage not supported.
):
  lib/cybersource/client.rb:73:in `rescue in run_transaction'
  lib/cybersource/client.rb:38:in `run_transaction'
  app/controllers/transactions_controller.rb:7:in `new'

Here is my ruby class, used to wrap this all up and make the call.

    module Cybersource
  class Client
    attr_reader :merchant_id, :transaction_key

    def initialize(merchant_id, transaction_key)
      @merchant_id = merchant_id
      @transaction_key = transaction_key
    end

    def client
      # set the header which includes the merchant_id and transaction_key
      soap_header = <<-HEREDOC
       <SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
        <wsse:Security SOAP-ENV:mustUnderstand="1">
          <wsse:UsernameToken>
            <wsse:Username>#{@merchant_id}</wsse:Username>
            <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">#{@transaction_key}</wsse:Password>
          </wsse:UsernameToken>
        </wsse:Security>
      </SOAP-ENV:Header>
      HEREDOC

      # initialize a Savon client
      Savon.client(
          env_namespace: 'soapenv',
          #namespace: "urn:schemas-cybersource-com:transaction-data:TransactionProcessor",
          soap_header: soap_header,
          #endpoint: "http://ics2wstest.ic3.com",
          wsdl: "https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor/CyberSourceTransaction_1.129.wsdl",
          pretty_print_xml: true,
          logger: Rails.logger,
          log: true
      )
    end

    def run_transaction
      # build up the xml message passed to the web service
      message = {
          merchantID: @merchant_id,
          merchantReferenceCode: rand(100),
          billTo: {
              firstName: "Saul",
              lastName: "Goodman",
              street1: "1295 Charleston Road",
              city: "Mountain View",
              state: "CA",
              postalCode: "94043",
              country: "US",
              email: "[email protected]",
          },
          item: {
              unitPrice: "50.00",
              quantity: "1",
          },
          purchaseTotals: {
              currency: "USD"
          },
          card: {
              accountNumber: "4111111111111111",
              expirationMonth: "12",
              expirationYear: "2020"
          },
          ccAuthService: {run: "true"},
      }
      response = client.call(:run_transaction, message: message, :attributes => {
          # sets the xmlns on the requestMessage tag
          'xmlns' => 'urn:schemas-cybersource-com:transaction-data-1.129',
      })

      # return the response body
      response.body[:response]
    rescue Savon::SOAPFault => error
      raise Cybersource::SoapException, error
    end

    protected
    def wsdl_url
      if Rails.env.production?
        ENV["CYBERSOURCE_LIVE_WSDL_URL"]
      else
        ENV["CYBERSOURCE_TEST_WSDL_URL"]
      end
    end
  end
end

I've only got a PHP code example to go off of, but I'm not sure how to convert that into ruby.

<HTML>
    <HEAD>
        <META HTTP-EQUIV="Content-Type" content="text/html; charset=iso-8859-1">
        <TITLE>Order Status</TITLE>
    </HEAD>
    <BODY>


<?php

// Before using this example, replace the generic values with your merchant ID and password.
define( 'MERCHANT_ID', 'your_merchant_id' );
define( 'TRANSACTION_KEY', 'your_transaction_key' );
define( 'WSDL_URL', 'https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor/CyberSourceTransaction_1.26.wsdl' );


class ExtendedClient extends SoapClient {

   function __construct($wsdl, $options = null) {
     parent::__construct($wsdl, $options);
   }

// This section inserts the UsernameToken information in the outgoing SOAP message.
   function __doRequest($request, $location, $action, $version) {

     $user = MERCHANT_ID;
     $password = TRANSACTION_KEY;

     $soapHeader = "<SOAP-ENV:Header xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\"><wsse:Security SOAP-ENV:mustUnderstand=\"1\"><wsse:UsernameToken><wsse:Username>$user</wsse:Username><wsse:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">$password</wsse:Password></wsse:UsernameToken></wsse:Security></SOAP-ENV:Header>";

     $requestDOM = new DOMDocument('1.0');
     $soapHeaderDOM = new DOMDocument('1.0');

     try {

         $requestDOM->loadXML($request);
     $soapHeaderDOM->loadXML($soapHeader);

     $node = $requestDOM->importNode($soapHeaderDOM->firstChild, true);
     $requestDOM->firstChild->insertBefore(
            $node, $requestDOM->firstChild->firstChild);

         $request = $requestDOM->saveXML();

     // printf( "Modified Request:\n*$request*\n" );

     } catch (DOMException $e) {
         die( 'Error adding UsernameToken: ' . $e->code);
     }

     return parent::__doRequest($request, $location, $action, $version);
   }
}

try {
    $soapClient = new ExtendedClient(WSDL_URL, array());

    /*
    To see the functions and types that the SOAP extension can automatically
    generate from the WSDL file, uncomment this section:
    $functions = $soapClient->__getFunctions();
    print_r($functions);
    $types = $soapClient->__getTypes();
    print_r($types);
    */

        $request = new stdClass();

    $request->merchantID = MERCHANT_ID;

    // Before using this example, replace the generic value with your own.
    $request->merchantReferenceCode = "your_merchant_reference_code";

    // To help us troubleshoot any problems that you may encounter,
    // please include the following information about your PHP application.
    $request->clientLibrary = "PHP";
        $request->clientLibraryVersion = phpversion();
        $request->clientEnvironment = php_uname();

    // This section contains a sample transaction request for the authorization
    // service with complete billing, payment card, and purchase (two items) information.
    $ccAuthService = new stdClass();
    $ccAuthService->run = "true";
    $request->ccAuthService = $ccAuthService;

    $billTo = new stdClass();
    $billTo->firstName = "John";
    $billTo->lastName = "Doe";
    $billTo->street1 = "1295 Charleston Road";
    $billTo->city = "Mountain View";
    $billTo->state = "CA";
    $billTo->postalCode = "94043";
    $billTo->country = "US";
    $billTo->email = "[email protected]";
    $billTo->ipAddress = "10.7.111.111";
    $request->billTo = $billTo;

    $card = new stdClass();
    $card->accountNumber = "4111111111111111";
    $card->expirationMonth = "12";
    $card->expirationYear = "2020";
    $request->card = $card;

    $purchaseTotals = new stdClass();
    $purchaseTotals->currency = "USD";
    $request->purchaseTotals = $purchaseTotals;

    $item0 = new stdClass();
    $item0->unitPrice = "12.34";
    $item0->quantity = "2";
    $item0->id = "0";

    $item1 = new stdClass();
    $item1->unitPrice = "56.78";
    $item1->id = "1";

    $request->item = array($item0, $item1);

    $reply = $soapClient->runTransaction($request);

    // This section will show all the reply fields.
    // var_dump($reply);

    // To retrieve individual reply fields, follow these examples.
    printf( "decision = $reply->decision<br>" );
    printf( "reasonCode = $reply->reasonCode<br>" );
    printf( "requestID = $reply->requestID<br>" );
    printf( "requestToken = $reply->requestToken<br>" );
    printf( "ccAuthReply->reasonCode = " . $reply->ccAuthReply->reasonCode . "<br>");
} catch (SoapFault $exception) {
    var_dump(get_class($exception));
    var_dump($exception);
}
?>

    </BODY>
</HTML>

Any help would be much appreciated.

like image 443
Carl Weis Avatar asked Aug 01 '16 21:08

Carl Weis


1 Answers

First let me say my knowledge of Ruby is minimal, so I can't help with the Savon client. But I did try your raw xml request, and realized that precisely the requestMessage ended up in the wrong namespace:

<data:requestMessage>

refers to xmlns:data="urn:schemas-cybersource-com:transaction-data:TransactionProcessor"

when it should be xmlns:data="urn:schemas-cybersource-com:transaction-data-1.129"

I see you have commented out the namespace parameter in your client initialization. That may be the way to set the namespace in case the client doesn't read it from the wsdl.

As per this answer you can specify different namespaces as needed.


Update

Ok, I think I got it to work. Take a look at this script:

require 'savon'

soap_header = <<-HEREDOC     
    <wsse:Security>
      <wsse:UsernameToken>
    <wsse:Username>username</wsse:Username>
    <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">transaction_key</wsse:Password>
      </wsse:UsernameToken>
    </wsse:Security>    
    HEREDOC

client = Savon.client(
  wsdl: 'https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor/CyberSourceTransaction_1.129.wsdl',
  soap_header: soap_header,
  env_namespace: 'soapenv',
  element_form_default: :unqualified,
  namespace: "urn:schemas-cybersource-com:transaction-data-1.129",
  namespaces: {
    "xmlns:wsse": "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
  },
  pretty_print_xml: true,
  #logger: Rails.logger,
  log: true
)

message = {
    merchantID: "merch_id",
    merchantReferenceCode: rand(100),
    billTo: {
    firstName: "Saul",
    lastName: "Goodman",
    street1: "1295 Charleston Road",
    city: "Mountain View",
    state: "CA",
    postalCode: "94043",
    country: "US",
    email: "[email protected]",
    },
    item: {
    unitPrice: "50.00",
    quantity: "1",
    },
    purchaseTotals: {
    currency: "USD"
    },
    card: {
    accountNumber: "4111111111111111",
    expirationMonth: "12",
    expirationYear: "2020"
    },
    ccAuthService: {
      :@run => "true"
    }
}


response = client.call(:run_transaction, message: message, :attributes => {          
    'xmlns' => 'urn:schemas-cybersource-com:transaction-data-1.129',
})

response.body[:response]

With this I'm getting Authentication failed as expected. So, a couple of things that were wrong:

  • The header definition should leave out the Header node, just start with the inner nodes (Security)

  • The run => true in ccAuthService is an attribute, not an inner node.

  • The whole namespacing mess of the requestMessage.

like image 130
alepeino Avatar answered Nov 15 '22 13:11

alepeino