Royal Mail Shipping API – запрос SOAP и запрос pem / сертификатов

Я пытаюсь настроить Royal Mail Shipping API (если у кого-то есть опыт, я был бы благодарен, если бы вы могли помочь).

В документации, которую они предоставляют, мне нужно загрузить сертификат (файл .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» или, в некоторых случаях, к одному объединенный файл, содержащий как cert, так и ключ.

Поэтому нигде не говорится, как эти два файла используются приложением, на данный момент я только что оставил их в том же каталоге, что и файл 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@yoursite.co.ukAPI 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: myemail@company.co.ukAPI dgCW98Vqw3ladYgPPpNialODhvI= MTMzMjE1NjM4 2015-10-13T13:25:30Z 2015-10-13T13:25:302.00526348001DeliveryDSD12015-10-13Jon DoeSS23, Some RoadLondonE10g1000000 

Я объединил два файла pem в один файл с именем «bundle.pem» и ссылался на это в переменной «local_cert» для SoapClient & BINGO, теперь это соединение. Теперь это длиннее показывает, что « Не удалось подключиться», но вместо этого указывает «Недействительный запрос», поэтому, по крайней мере, сейчас это соединение и дает мне другую ошибку.

Весь мой 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 = "xxxxxxxxx@xxxxxxxxx.co.ukAPI"; $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('Ymd\TH:i:s'); $created = gmdate('Ymd\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('Ym-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('Ymd\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('Ymd\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('Ym-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) ) ) ) ) ); 

! Хотя вы уже упоминали, что сделали большинство этих шагов, я все равно их детализую, чтобы выполнить инструкции

Установка CA на сервер

  • Загрузите файл p12 на сервер (например, в /root/Desktop )
  • Запустите следующие 3 команды для установки
 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)

У вас должны быть следующие файлы;

введите описание изображения здесь

Использование SOAPClient

Теперь, когда установлены сертификаты, мы можем проверить соединение (при условии, что ваши сертификаты находятся в /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 может возникнуть сбой 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('Ymd\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>