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.
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With