如何格式化 Cybersource SOAP API 的 Savon 请求

2022-08-30 18:36:40

试图弄清楚如何使用Savon和Ruby格式化Cybersource支付请求。

我已经在这里呆了一段时间了,没有运气。我不断收到请求消息不受支持

我猜这是由于我如何将参数传递到消息正文,和/或标头设置不正确。

下面是 SOAP API 的预期 xml:

<?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>null@cybersource.com</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>   

这是我在尝试发出请求时获得的xml。

 <?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>test@example.com</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>

这是返回的错误:

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'

这是我的 ruby 类,用于将所有内容打包并进行调用。

    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: "test@example.com",
          },
          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

我只有一个PHP代码示例,但我不确定如何将其转换为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 = "null@cybersource.com";
    $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>

任何帮助将不胜感激。


答案 1

首先,我要说的是,我对 Ruby 的了解微乎其微,所以我无法帮助 Savon 客户端。但是我确实尝试了您的原始xml请求,并意识到恰恰最终出现在错误的命名空间中:requestMessage

<data:requestMessage>

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

什么时候应该是xmlns:data="urn:schemas-cybersource-com:transaction-data-1.129"

我看到您已经注释掉了客户端初始化中的命名空间参数。这可能是设置命名空间的方法,以防客户端没有从 wsdl 读取它。

根据此答案,您可以根据需要指定不同的命名空间。


更新

好吧,我想我让它工作了。看看这个脚本:

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: "test@example.com",
    },
    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]

有了这个,我得到身份验证失败,如预期的那样。所以,有几件事是错的:

  • 标头定义应省略节点,只需从内部节点 (HeaderSecurity)

  • in 是一个属性,而不是内部节点。run => trueccAuthService

  • 整个命名空间混乱的.requestMessage


答案 2

推荐