php - cybersource api
Cómo formatear Savon Request for Cybersource SOAP API (1)
Tratando de averiguar cómo formatear una solicitud de pagos de Cybersource, utilizando Savon y Ruby.
He estado en esto por un tiempo, sin suerte. Sigo recibiendo RequestMessage no compatible
Supongo que se debe a cómo paso los parámetros al cuerpo del mensaje y / o al encabezado no configurado correctamente.
Aquí está el xml esperado para la API de SOAP:
<?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>
Este es el xml que obtengo cuando intento hacer una solicitud.
<?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>
Este es el error que se devuelve:
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''
Aquí está mi clase de ruby, usada para envolver todo y hacer la llamada.
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
Solo tengo un ejemplo de código PHP para irme, pero no estoy seguro de cómo convertirlo en 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>
Cualquier ayuda sería muy apreciada.
Primero permítanme decir que mi conocimiento de Ruby es mínimo, así que no puedo ayudar con el cliente de Savon. Pero probé la solicitud XML sin requestMessage
y me di cuenta de que precisamente requestMessage
terminó en el espacio de nombres incorrecto:
<data:requestMessage>
se refiere a xmlns:data="urn:schemas-cybersource-com:transaction-data:TransactionProcessor"
cuando debería ser xmlns:data="urn:schemas-cybersource-com:transaction-data-1.129"
Veo que ha comentado el parámetro del espacio de nombres en la inicialización de su cliente. Esa puede ser la forma de establecer el espacio de nombres en caso de que el cliente no lo lea desde el wsdl.
Según esta respuesta , puede especificar diferentes espacios de nombres según sea necesario.
Actualizar
Ok, creo que lo hice funcionar. Echa un vistazo a este 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]
Con esto estoy obteniendo la autenticación fallida como se esperaba. Entonces, un par de cosas que estaban mal:
La definición del encabezado debe omitir el nodo
Header
, simplemente comience con los nodos internos (Security
)El
run => true
enccAuthService
es un atributo, no un nodo interno.Todo el desorden de
requestMessage
nombres derequestMessage
.