Я пытаюсь настроить API Royal Mail Shipping (если у кого-то есть подобный опыт, я был бы признателен, если бы вы могли помочь).
В документации, которую они предоставляют, мне нужно скачать сертификат (файл .p12) & импортировать это на мой компьютер с Windows — это довольно просто, используя «Мастер импорта сертификатов». Как только он достигнет «Установить уровень безопасности», я должен выбрать Высоко & это будет запрашивать разрешение с паролем каждый раз, когда это используется.
В Internet Explorer в разделе «Свойства обозревателя» на вкладке «Содержимое» я могу просмотреть сертификаты и четко видеть, что этот сертификат был импортирован и срок его действия не истек.
Следующим шагом является извлечь компоненты сертификата, здесь я должен выполнить три следующие команды, используя OpenSSL для генерации файлов .pem.
$ openssl pkcs12 -in mycert.p12 -cacerts -nokeys -out cacert.pem
$ openssl pkcs12 -in mycert.p12 -clcerts -nokeys -out mycert.pem
$ openssl pkcs12 -in mycert.p12 -nocerts -nodes -out mykey.pem
В документации говорится Файл cacert.pem может напрямую ссылаться приложением, используя сам файл, что я думаю, что я сделал в моем скрипте PHP, однако не ясно, где я должен положить другой mycert & файлы mykey pem.
В документации говорится следующее:
То, как приложение передает выданный клиентский SSL-сертификат при установлении сетевого SSL-подключения, зависит от приложения и среды, но по существу ему потребуется доступ как к файлам «mycert.pem» и «mykey.pem», так и в некоторых случаях к одному объединенный файл, содержащий как сертификат, так и ключ.
Так что нигде не сказано, как эти два файла используются приложением, на данный момент я просто оставил их в том же каталоге, что и файл cacert.pem.
Если я пытаюсь получить доступ к URL https://api.royalmail.com/shipping/onboarding прямо из браузера запрашивает у меня сертификат, выбираю этот & затем введите правильный пароль, когда он запросит «Предоставить или отказать в разрешении использовать этот ключ». Как только я ввожу правильный пароль, появляется следующая страница — может ли кто-нибудь подтвердить, будет ли это означать, что проблема на моем конце или что-то, что Royal Mail не настроило правильно с их стороны.
В дополнение к этому, реальный PHP-скрипт, который у меня есть, используется для отправки SOAP-запросов в API доставки, не работает (вероятно, относится ко всему вышеописанному).
В моем PHP-скрипте параметры soapclient настроены следующим образом:
$soapclient_options['cache_wsdl'] = 'WSDL_CACHE_NONE';
$soapclient_options['local_cert'] = 'certs/cacert.pem';
$soapclient_options['passphrase'] = $api_certificate_passphrase;
$soapclient_options['trace'] = true;
$soapclient_options['ssl_method'] = 'SOAP_SSL_METHOD_SSLv3';
$soapclient_options['location'] = 'https://api.royalmail.com/shipping/onboarding';
$client = new SoapClient('SAPI/ShippingAPI_V2_0_8.wsdl', $soapclient_options);
$client->__setLocation($soapclient_options['location']);
Когда я запускаю скрипт PHP (это в основном тот же код, который Royal Mail предоставляет для себя, используя свои личные данные для входа в API), я получаю следующее сообщение в браузере:
Could not connect to host
REQUEST: [email protected] rngfJ+4dt4Gt855a5pr6u38i3B4= ODcwMTE5Nzc3 2015-10-13T11:02:20Z 2015-10-13T11:02:201.00526348001DeliveryDSD12015-10-13bobSS23, Some AvenueLondonE10g1000000
Очевидно, что это не может соединиться с хостом по неизвестной причине, последняя — просто запрос, который был отправлен. Остальная часть сценария PHP точно такая же, как и сценарий Royal Mail, который мне прислали & подтвердили, используется другими и работает нормально.
Я работаю в среде WAMP, хотя возможный код будет в среде Linux. Может ли кто-нибудь помочь Я действительно сбит с толку & Сами Royal Mail пока не смогли обеспечить надежную техническую поддержку.
Это полное сообщение об ошибке, отображаемое в браузере (я изменил адрес электронной почты в целях безопасности)
Invalid Request REQUEST: [email protected] dgCW98Vqw3ladYgPPpNialODhvI= MTMzMjE1NjM4 2015-10-13T13:25:30Z 2015-10-13T13:25:302.00526348001DeliveryDSD12015-10-13Jon DoeSS23, Some RoadLondonE10g1000000
Я объединил два файла pem в один файл под названием «bundle.pem» & ссылался на это в переменной local_cert для SoapClient & Бинго это сейчас подключается. Это теперь дольше показывает Не может подключиться но вместо этого выдает «Неверный запрос», так что, по крайней мере, теперь он соединяется и выдает мне другую ошибку.
Весь мой PHP-скрипт ниже:
<?php
ini_set('default_socket_timeout', 120);
ini_set('soap.wsdl_cache_enabled',1);
ini_set('soap.wsdl_cache_ttl',1);
$api_password = "xxxxxxxxxxxxxx!";
$api_username = "[email protected]";
$api_application_id = "xxxxxxxxxxxx";
$api_service_type = "D";
$api_service_code = "SD1";
$api_service_format = "";
$api_certificate_passphrase = 'xxxxxxxxxx';
$api_service_enhancements = "";
$data = new ArrayObject();
$data->order_tracking_id = "";
$data->shipping_name = "Jon Doe";
$data->shipping_company = "SS";
$data->shipping_address1 = "23, Some Road";
$data->shipping_address2 = "";
$data->shipping_town = "London";
$data->shipping_postcode = "E1";
$data->order_tracking_boxes = "0";
$data->order_tracking_weight = "1000";
$time = gmdate('Y-m-d\TH:i:s');
$created = gmdate('Y-m-d\TH:i:s\Z');
$nonce = mt_rand();
$nonce_date_pwd = pack("A*",$nonce) . pack("A*",$created) . pack("H*", sha1($api_password));
$passwordDigest = base64_encode(pack('H*',sha1($nonce_date_pwd)));
$ENCODEDNONCE = base64_encode($nonce);
$soapclient_options = array();
$soapclient_options['cache_wsdl'] = 'WSDL_CACHE_NONE';
$soapclient_options['local_cert'] = 'royalmail/cert/bundle.pem';
$soapclient_options['passphrase'] = $api_certificate_passphrase;
$soapclient_options['trace'] = true;
$soapclient_options['ssl_method'] = 'SOAP_SSL_METHOD_SSLv3';
$soapclient_options['exceptions'] = true;
$soapclient_options['location'] = 'https://api.royalmail.com/shipping/onboarding';
//launch soap client
$client = new SoapClient('royalmail/ShippingAPI_V2_0_8.wsdl', $soapclient_options);
$client->__setLocation($soapclient_options['location']);
//headers needed for royal mail
$HeaderObjectXML = '<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-000">
<wsse:Username>'.$api_username.'</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$passwordDigest.'</wsse:Password>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.$ENCODEDNONCE.'</wsse:Nonce>
<wsu:Created>'.$created.'</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>';
//push the header into soap
$HeaderObject = new SoapVar( $HeaderObjectXML, XSD_ANYXML );
//push soap header
$header = new SoapHeader( 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd', 'Security', $HeaderObject );
$client->__setSoapHeaders($header);
//build the request
$request = array(
'integrationHeader' => array(
'dateTime' => $time,
'version' => '1.0',
'identification' => array(
'applicationId' => $api_application_id,
'transactionId' => $data->order_tracking_id
)
),
'requestedShipment' => array(
'shipmentType' => array('code' => 'Delivery'),
'serviceOccurence' => '1',
'serviceType' => array('code' => $api_service_type),
'serviceOffering' => array('serviceOfferingCode' => array('code' => $api_service_code)),
'serviceFormat' => array('serviceFormatCode' => array('code' => $api_service_format)),
'shippingDate' => date('Y-m-d'),
'recipientContact' => array('name' => $data->shipping_name, 'complementaryName' => $data->shipping_company),
'recipientAddress' => array('addressLine1' => $data->shipping_address1, 'addressLine2' => $data->shipping_address2, 'postTown' => $data->shipping_town, 'postcode' => $data->shipping_postcode),
'items' => array('item' => array(
'numberOfItems' => $data->order_tracking_boxes,
'weight' => array( 'unitOfMeasure' => array('unitOfMeasureCode' => array('code' => 'g')), 'value' => ($data->order_tracking_weight*1000) //weight of each individual item
)
)
)
)
);
//if any enhancements, add it into the array
if($api_service_enhancements != "") {
$request['requestedShipment']['serviceEnhancements'] = array('enhancementType' => array('serviceEnhancementCode' => array('code' => $api_service_enhancements)));
}
//try make the call
try {
$response = $client->__soapCall( 'createShipment', array($request), array('soapaction' => 'https://api.royalmail.com/shipping/onboarding') );
} catch (Exception $e) {
//catch the error message and echo the last request for debug
echo $e->getMessage();
echo " REQUEST:\n" . $client->__getLastRequest() . "\n";
die;
}
//check for any errors
if(isset($response->integrationFooter->errors)) {
$build = "";
//check it wasn't a single error message
if(isset($response->integrationFooter->errors->error->errorCode)) {
$build .= $output_error->errorCode.": ".$output_error->errorDescription."<br/>";
} else {
//loop out each error message, throw exception will be added ehre
foreach($response->integrationFooter->errors->error as $output_error) {
$build .= $output_error->errorCode.": ".$output_error->errorDescription."<br/>";
}
}
echo $build; die;
}
print_r($response);
echo "REQUEST:\n" . $client->__getLastRequest() . "\n";
die;
?>
Просто для большей ясности я добавил дамп переменной $ request непосредственно перед тем, как она достигнет блока try / catch (обратите внимание, это довольно долго).
Array
(
[integrationHeader] => Array
(
[dateTime] => 2015-10-13T13:34:44
[version] => 1.0
[identification] => Array
(
[applicationId] => 0526348001
[transactionId] =>
)
)
[requestedShipment] => Array
(
[shipmentType] => Array
(
[code] => Delivery
)
[serviceOccurence] => 1
[serviceType] => Array
(
[code] => D
)
[serviceOffering] => Array
(
[serviceOfferingCode] => Array
(
[code] => SD1
)
)
[serviceFormat] => Array
(
[serviceFormatCode] => Array
(
[code] =>
)
)
[shippingDate] => 2015-10-13
[recipientContact] => Array
(
[name] => Jon Doe
[complementaryName] => SS
)
[recipientAddress] => Array
(
[addressLine1] => 23, Some Road
[addressLine2] =>
[postTown] => London
[postcode] => E1
)
[items] => Array
(
[item] => Array
(
[numberOfItems] => 0
[weight] => Array
(
[unitOfMeasure] => Array
(
[unitOfMeasureCode] => Array
(
[code] => g
)
)
[value] => 1000000
)
)
)
)
)
Во-первых, доступ к https://api.royalmail.com/shipping/onboarding напрямую не будет работать, потому что он доступен только через API.
С Royal Mail у вас есть все файлы CDM и WSDL? Убедитесь, что файлы CDM находятся в том же каталоге, что и файлы WSDL.
Вот что я сделал при работе с API;
$client = new SoapClient("/royalmail/ShippingAPI_V2_0_8.wsdl", array(
'trace' => 1,
'location' => $location, //https://api.royalmail.com/shipping
'soap_version' => SOAP_1_1,
'local_cert' => '/royalmail/cert/cert.pem',
'passphrase' => 'xxx',
'exceptions' => true
));
Затем, когда дело дошло до установления соединения, я сделал что-то вроде этого:
$password = 'xxx';
$date = gmdate('Y-m-d\TH:i:s\Z');
$nonce = mt_rand();
$nonce_date_pwd = pack("A*",$nonce) . pack("A*",$date) . pack("H*", sha1($password));
$encoded_password = base64_encode(pack('H*',sha1($nonce_date_pwd)));
$encoded_nonce = base64_encode($nonce);
$HeaderObjectXML = '<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-0000">
<wsse:Username>Username</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">' . $encoded_password . '</wsse:Password>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">' . $encoded_nonce . '</wsse:Nonce>
<wsu:Created>'.$date.'</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>';
$HeaderObject = new SoapVar( $HeaderObjectXML, XSD_ANYXML );
$header = new SoapHeader( 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd', 'Security', $HeaderObject );
$client->__setSoapHeaders( $header );
$request = array('the shipment request');
try {
$client->__soapCall( 'createShipment', array($request) );
}
catch (SoapFault $soapFault) {
print_r($soapFault);
}
Надеюсь, это поможет.
редактировать
Попробуйте этот запрос
я считать вам нужно использовать версию 2 для этого Сервисного Происшествия, если я правильно помню. Кроме того, просто … форматирование. Облегчает отладку.
$request = Array(
'integrationHeader' => array(
'dateTime' => date('Y-m-d\TH:i:s'),
'version' => '2',
'identification' => array(
'applicationId' => $api_application_id,
'transactionId' => $data->order_tracking_id
),
),
'requestedShipment' => array(
'shipmentType' => array(
'code' => 'Delivery'
),
'serviceOccurrence' => 1,
'serviceType' => array(
'code' => $api_service_type
),
'serviceOffering' => array(
'serviceOfferingCode' => array(
'code' => $api_service_code
)
),
'serviceFormat' => array(
'serviceFormatCode' => array(
'code' => $api_service_format
)
),
'shippingDate' => gmdate('Y-m-d'),
'recipientContact' => array(
'name' => $data->shipping_name,
'complementaryName' => $data->shipping_company
),
'recipientAddress' => array(
'addressLine1' => $data->shipping_address1,
'addressLine2' => $data->shipping_address2,
'postTown' => $data->shipping_town,
'postcode' => $data->shipping_postcode
),
'items' => array(
'item' => array(
'numberOfItems' => $data->order_tracking_boxes,
'weight' => array(
'unitOfMeasure' => array(
'unitOfMeasureCode' => array(
'code' => 'g'
)
),
'value' => ($data->order_tracking_weight*1000)
)
)
)
)
);
! Хотя вы упоминали, что выполнили большинство из этих шагов, я все равно подробно опишу их, чтобы выполнить инструкции
/root/Desktop
например)openssl pkcs12 -in mycert.p12 -cacerts -nokeys -out cacert.pem
openssl pkcs12 -in mycert.p12 -clcerts -nokeys -out mycert.pem
openssl pkcs12 -in mycert.p12 -nocerts -nodes -out mykey.pem
*.pem
файлы в /etc/ssl/certs
mkdir royalmail
)*.pem
файлы (mv *.pem /etc/ssl/certs/certificates
)mycert.pem
а также mykey.pem
в него (только из -----BEGIN .... -------
в EOF)У вас должны быть следующие файлы;
Теперь, когда сертификаты установлены, мы можем проверить соединение (если ваши сертификаты находятся в /etc/pki/tls/certs/certificates/royalmail/shippingv2
)
Если вы бежите
wget https://api.royalmail.com/shipping/onboarding --private-key=/etc/ssl/certs/certificates/royalmail/shippingv2/rm_bundle.pem --private-key-type=PEM
Вы должны быть в состоянии подключиться к порту 443 (может быть сбой рукопожатия в OpenSSL
хотя — по крайней мере, с моим тестом только сейчас на нашей промежуточной среде).
Теперь мы можем создать экземпляр SoapClient
используя локальную копию WSDL и указав локальный сертификат в $options
параметр.
$objSoapClient = new \SoapClient('lib/wsdl/royalmail/shipping/ShippingAPI_V2_0_8.wsdl', array(
'soap_version' => SOAP_1_1,
'trace' => 1,
'uri' => 'http://www.royalmailgroup.com/api/ship/V2',
'location' => 'https://api.royalmail.com/shipping/onboarding',
'local_cert' => '/etc/ssl/certs/certificates/royalmail/shippingv2/rm_bundle.pem',
'passphrase' => '', //Your passphrase when doing step 1
'ssl_method' => 'SOAP_SSL_METHOD_TLS',
'exceptions' => 1,
'trace' => 1
));
В вашей документации вы должны найти файл с именем rm_password_digest.php
или что-то подобное, в котором подробно описано, как создавать заголовки аутентификации.
/* The value below should be changed to your password. If you store the password */
/* as hashed in your database, you will need to change the code below to remove hashing */
$password = 'just_my_royalmail_api_password';
/* CREATIONDATE - The timestamp. The computer must be on correct time or the server you are
* connecting may reject the password digest for security.
*/
$CREATIONDATE = gmdate('Y-m-d\TH:i:s\Z');
/* NONCE - A random word. The use of rand() may repeat the word if the server is
* very loaded.
*/
$nonce = mt_rand();
/* PASSWORDDIGEST This is the way to create the password digest. As per OASIS standard
* digest = base64_encode(Sha1(nonce + creationdate + password)
* however note that we use a SHA1(password) instead of the password above
*/
$nonce_date_pwd = pack("A*",$nonce) . pack("A*",$CREATIONDATE) . pack("H*", sha1($password));
$PASSWORDDIGEST = base64_encode(
pack('H*', sha1($nonce_date_pwd)));
/* ENCODEDNONCE - Now encode the nonce for security header */
$ENCODEDNONCE = base64_encode($nonce);
/* Now Print all the values - so we can use it for testing with tools like soapui */
print "WS Security Header elements \n";
print "--------------------------- \n";
print 'Nonce = ' . $nonce;
print "\n";
print 'PASSWORDDIGEST= ' . $PASSWORDDIGEST;
print "\n";
print 'ENCODEDNONCE= ' . $ENCODEDNONCE;
print "\n";
print "CREATIONDATE= " . $CREATIONDATE;
Это поможет вам построить следующее в SOAPHeader
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-0000">
<wsse:Username>[...]</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">[...]</wsse:Password>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">[...]</wsse:Nonce>
<wsu:Created>[...]</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>