OAuth Request Authentication Method ############################################################ .. contents:: :local: .. role:: ex .. role:: code .. _HMAC-SHA1: OAuth HMAC-SHA1 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To generate request with OAuth HMAC-SHA1 authentication: #. :ref:`Generate Signature`, #. :ref:`Generate Headers`, #. :ref:`Generate Request`. .. _hmac_sha1_signature_generation: OAuth HMAC-SHA1 Signature Generation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To prepare the Signature Base String do the following steps: #. Gather all request body parameters and OAuth parameters (:code:`oauth_consumer_key`, :code:`oauth_nonce`, :code:`oauth_signature_method`, :code:`oauth_timestamp`, :code:`oauth_version`): | For :code:`oauth_consumer_key` use a merchant login provided by Payment Gateway, ex. :ex:`merchantlogin`. | For :code:`oauth_nonce` generate unique random :code:`nonce`, ex. :ex:`y3qlvMPky7g`. | For :code:`oauth_signature_method` set :ex:`HMAC-SHA1`. | For :code:`oauth_timestamp` header get current :code:`timestamp` in seconds, ex. :ex:`1669966913`. | For :code:`oauth_version` set :ex:`1.0`. | Parameter :code:`oauth_signature` is not included in request body parameters (only in OAuth header). #. Percent-encode value of each gathered on step 1 parameter. See `RFC 3986 `_ for reference. The unreserved characters can be encoded, but should not be encoded. The reserved characters have to be encoded, for example: ``%`` is encoded to ``%25``, ``/`` is encoded to ``%2F``, ``=`` is encoded to ``%3D``. #. Sort all gathered on step 1 parameters together with their values in lexicographical order by parameter name. #. | Concatenate all gathered on step 1 parameters and their values using :ex:`&` and :ex:`=` in the following way: | ``object1=valueOfObject1&object2=valueOfObject2...`` | Concatenated parameters string example: .. code-block:: guess client-order-id=1234567890&oauth_consumer_key=merchantlogin&oauth_nonce=y3qlvMPky7g&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1669966913&oauth_version=1.0&sending-card-ref-id=7654321 #. | Percent-encode each element (POST, URL, concatenated parameters) and construct Signature Base String with :code:`POST&URL¶meters` structure (as documented in OAuth `A.5.1. `_). | Signature Base String Example: .. code-block:: POST&https%3A%2F%2Fgate.payneteasy.com%2Fpaynet%2Fapi%2Fv2%2Fpan-eligibility%2Fsend%2FENDPOINTID&client-order-id%3D1234567890%26oauth_consumer_key%3Dmerchantlogin%26oauth_nonce%3Dy3qlvMPky7g%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1669966913%26oauth_version%3D1.0%26sending-card-ref-id%3D7654321 #. | Sign the Signature Base String with HMAC-SHA1. The signing key is the concatenation of :code:`consumer secret` (Merchant control key) + :code:`&` + :code:`token secret` (empty string). | HMAC-SHA1 Key Example: .. code-block:: 11111111-1111-1111-1111-111111111111& #. | Encode the signed string to Base64 to get signature for API. | Java HMAC-SHA1 Signature Generation Example: .. code-block:: java package com.Payneteasy; import org.apache.commons.codec.binary.Base64; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; public class HMAC_SHA1 { private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; public static String calculateHMAC(String data, String key) throws NoSuchAlgorithmException, InvalidKeyException { SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM); Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); mac.init(signingKey); return Base64.encodeBase64String(mac.doFinal(data.getBytes())); } } | Signature will look like following string: .. code-block:: d/IPlITUmPcniwjA7Vckjr6WQeE= .. _hmac_sha1_headers_generation: OAuth HMAC-SHA1 Headers Generation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A request with OAuth HMAC-SHA1 authentication method must contain next headers: * :ex:`content-type=application/x-www-form-urlencoded` * :ex:`Authorization: OAuth` | OAuth header must contain the same OAuth parameters and values which were included in signature base string (:code:`oauth_consumer_key`, :code:`oauth_nonce`, :code:`oauth_signature_method`, :code:`oauth_timestamp`, :code:`oauth_version`). Add signature as value of parameter :code:`oauth_signature` to header :ex:`Authorization`. All values of parameters in OAuth header must be in quotes (See OAuth `5.4.1. `_) with the following structure: :code:`parameter="value"` and be separated by mandatory comma (:ex:`,`) and optional space. OAuth :code:`realm` parameter is optional. | Authorization Header Example (Header is line-separated for display purposes): .. code-block:: guess Authorization: OAuth oauth_consumer_key="merchantlogin", oauth_nonce="y3qlvMPky7g", oauth_signature="d/IPlITUmPcniwjA7Vckjr6WQeE=", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1669966913", oauth_version="1.0" .. note:: Since headers are not usually encoded, the values of oauth parameters (especially oauth_nonce and oauth_signature) should be percent-encoded separately before being added to Authorization header. .. _hmac_sha1_request_generation: OAuth HMAC-SHA1 Request Generation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #. Use headers from previous section, #. Add request parameters to body, #. Percent-encode and send the request. Example of Request: .. code-block:: Request method: POST Request URI: https://gate.payneteasy.com/paynet/api/v2/pan-eligibility/send/1111 Headers: Authorization=OAuth realm="",oauth_version="1.0",oauth_consumer_key="merchantlogin",oauth_timestamp="1669966913",oauth_nonce="y3qlvMPky7g",oauth_signature_method="HMAC-SHA1",oauth_signature="d%2FIPlITUmPcniwjA7Vckjr6WQeE%3D" Accept=*/* Content-Type=application/x-www-form-urlencoded; charset=ISO-8859-1 Body: client-order-id=1234567890&oauth_consumer_key=merchantlogin&oauth_nonce=y3qlvMPky7g&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1669966913&oauth_version=1.0&sending-card-ref-id=7654321 Example of CURL Request: .. code-block:: curl -H ' Authorization: oauth_version="1.0", oauth_consumer_key="merchantlogin", oauth_timestamp="1669966913", oauth_nonce="y3qlvMPky7g", oauth_signature_method="HMAC-SHA1", oauth_signature="d%2FIPlITUmPcniwjA7Vckjr6WQeE%3D" ' --data ' client-order-id=1234567890 &oauth_consumer_key=merchantlogin &oauth_nonce=y3qlvMPky7g &oauth_signature_method=HMAC-SHA1 &oauth_timestamp=1669966913 &oauth_version=1.0 &sending-card-ref-id=7654321 ' 'https://gate.payneteasy.com/paynet/api/v2/pan-eligibility/send/ENDPOINTID' .. _RSA-SHA256: OAuth RSA-SHA256 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To generate request with OAuth RSA-SHA256 authentication: #. :ref:`Generate Key Pair`, #. :ref:`Generate Signature`, #. :ref:`Generate Headers`, #. :ref:`Generate Request`. .. _rsa_sha256_generating_key_pair: Generating Key Pair ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To authorize future requests with RSA-SHA256, generate the key pair: * A PRIVATE key to sign the request. This private key must be kept secret from everyone. * A PUBLIC key to verify that the request was signed with the respective private key. Send it to support manager. To generate keys download latest version of `openssl `_ and run following commands: .. code-block:: bash openssl genpkey -algorithm RSA -out private_key_pkcs_8.pem -pkeyopt rsa_keygen_bits:4096 openssl rsa -pubout -in private_key_pkcs_8.pem -out public_key.pem Use different keys for production and for testing purposes to avoid compromise. To use request builders in documentation, get the private key in PKCS#1 container. It should be unencrypted RSA private key in PKCS#1 PEM text format. To get it use the following command: .. code-block:: bash openssl rsa -in private_key_pkcs_8.pem -out private_key_pkcs_1.pem .. note:: **For OpenSSL v3+ (tested on v3.0.5)**, use the following command instead: .. code-block:: bash openssl rsa -traditional -in private_key_pkcs_8.pem -out private_key_pkcs_1.pem The resultant PKCS#1 RSA private key will start with :ex:`—–BEGIN RSA PRIVATE KEY—–`. For production purposes any format of the key supported by Connecting Party software can be used. Request builders in documentation support key length up to 4096. Example of the PKCS#1 RSA private key: .. code-block:: bash -----BEGIN RSA PRIVATE KEY----- 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 -----END RSA PRIVATE KEY----- .. _rsa_sha256_signature_generation: OAuth RSA-SHA256 Signature Generation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To prepare the Signature Base String do the following steps: #. Gather all request body parameters and OAuth parameters (:code:`oauth_consumer_key`, :code:`oauth_nonce`, :code:`oauth_signature_method`, :code:`oauth_timestamp`, :code:`oauth_version`): | For :code:`oauth_consumer_key` use a merchant login provided by Payment Gateway, ex. :ex:`paydroid`. | For :code:`oauth_nonce` generate unique random :code:`nonce`, ex. :ex:`hoFlZri9c17X1Tvb7yD2fsMEQUIWBQ3m`. | For :code:`oauth_signature_method` set :ex:`RSA-SHA256`. | For :code:`oauth_timestamp` header get current :code:`timestamp` in seconds, ex. :ex:`1669720957`. | For :code:`oauth_version` set :ex:`1.0`. | Parameter :code:`oauth_signature` is not included in request body parameters (only in OAuth header). #. Percent-encode value of each gathered on step 1 parameter. See `RFC 3986 `_ for reference. The unreserved characters can be encoded, but should not be encoded. The reserved characters have to be encoded, for example: ``%`` is encoded to ``%25``, ``/`` is encoded to ``%2F``, ``=`` is encoded to ``%3D``. #. Sort all gathered on step 1 parameters together with their values in lexicographical order by parameter name. #. | Concatenate all gathered on step 1 parameters and their values using :ex:`&` and :ex:`=` by the following way: ``object1=valueOfObject1&object2=valueOfObject2...`` #. | Percent-encode each element (POST, URL, concatenated parameters) and construct Signature Base String with :code:`POST&URL¶meters` structure (as documented in OAuth `A.5.1. `_). | Signature Base String Example: .. code-block:: POST&https%3A%2F%2Fgate.payneteasy.com%2Fpaynet%2Fapi%2Fv4%2Ftransfer%2FENDPOINTID&amount%3D10.42%26card_printed_name%3DJohn%2520Doe%26card_recurring_payment_id%3D42322%26client_orderid%3D1%26credit_card_number%3D4210708776705721%26currency%3DUSD%26cvv2%3D123%26deposit2card%3Dfalse%26destination-card-no%3D4232618181101636%26destination_card_recurring_payment_id%3D61622%26expire_month%3D12%26expire_year%3D2099%26ipaddress%3D1.1.1.1.1%26oauth_consumer_key%3Dpaydroid%26oauth_nonce%3DhoFlZri9c17X1Tvb7yD2fsMEQUIWBQ3m%26oauth_signature_method%3DRSA-SHA256%26oauth_timestamp%3D1669720957%26oauth_version%3D1.0%26order_desc%3DYour%2520order%2520description%26redirect_url%3Dhttp%253A%252F%252Fwww.example.com #. | Sign the Signature Base String with RSA-SHA256 using the private key obtained in previous section. #. | Base64 encode the signed string to get signature for API. | Base64 Encoded Signature Example: .. code-block:: K0hLc7GYh65UDTNvJvJbqoD95T7ekVEwIx+AxLBe2rNndPVzCAZMTi58J5pJlXZA1qOrgzUj/uL764NofP6qrqBXHX9Fpg+PdoMBey7zY9nMOmtdpHhkwyqA0n8e8oh68x+8RtC1+gmaIsIJDVurpCm2CdaViC2ny90GWPrrSin9CFwDmIKBtOJ7dxNnuFQJkvLxwK9JE9gRfQssG4vOrXrn2f5DvENFvFW3fL7meiN3mKuBFyEHIv2cibWopoUTQrxAgCfTHvRuU5nRIct9oWgCYLYzROOPyAIjtyDFKcTnUql9+tD2+p2rMDDU7HqJUGy764rb4ShuvuiuEvzIaNwg3JAxho9fqcKJz5LXt4efX1i8oFt33ztYSgZRojsoW4HCzuhZcQQmRexpmtCGYKqH3Q2BsG2jIkQAxL9BOUOzXNoeXoVQIf3+47cJ0KujEHuDXROblq3o9Uos5K+Mu9Carjs8jHMiBHo4aS4IAgXVY3mEuohCuRhL9/Y9buuyvdKSNsao7qHwD/6bb9Sj2MFFhYP6gHf+p1NipH05224aX9hMZty8Ovb3+ps/wNWYC8NVfft5bh8ETowaHQ5TUOPFdvU+5IkHOnnfbvz1/+jDeErd0Pdq4xH6c5/gZVQY9j6DFpazsw8d3karwXoBduv7I3mh7L3CSjTweABaRMw= .. _rsa_sha256_headers_generation: OAuth RSA-SHA256 Headers Generation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A request with OAuth RSA-SHA256 authentication method must contain next headers: * :ex:`content-type=application/x-www-form-urlencoded` * :ex:`Authorization: OAuth` | OAuth header must contain the same OAuth parameters and values which were included in signature base string (:code:`oauth_consumer_key`, :code:`oauth_nonce`, :code:`oauth_signature_method`, :code:`oauth_timestamp`, :code:`oauth_version`). Add signature as value of parameter :code:`oauth_signature` to header :ex:`Authorization`. All values of parameters in OAuth header must be in quotes (See OAuth `5.4.1. `_) with the following structure: :code:`parameter="value"` and be separated by mandatory comma (:ex:`,`) and optional space. OAuth :code:`realm` parameter is optional. | Authorization Header Example (Header is line-separated for display purposes): .. code-block:: guess Authorization: OAuth oauth_consumer_key="paydroid", oauth_nonce="hoFlZri9c17X1Tvb7yD2fsMEQUIWBQ3m", oauth_signature="K0hLc7GYh65UDTNvJvJbqoD95T7ekVEwIx%2BAxLBe2rNndPVzCAZMTi58J5pJlXZA1qOrgzUj%2FuL764NofP6qrqBXHX9Fpg%2BPdoMBey7zY9nMOmtdpHhkwyqA0n8e8oh68x%2B8RtC1%2BgmaIsIJDVurpCm2CdaViC2ny90GWPrrSin9CFwDmIKBtOJ7dxNnuFQJkvLxwK9JE9gRfQssG4vOrXrn2f5DvENFvFW3fL7meiN3mKuBFyEHIv2cibWopoUTQrxAgCfTHvRuU5nRIct9oWgCYLYzROOPyAIjtyDFKcTnUql9%2BtD2%2Bp2rMDDU7HqJUGy764rb4ShuvuiuEvzIaNwg3JAxho9fqcKJz5LXt4efX1i8oFt33ztYSgZRojsoW4HCzuhZcQQmRexpmtCGYKqH3Q2BsG2jIkQAxL9BOUOzXNoeXoVQIf3%2B47cJ0KujEHuDXROblq3o9Uos5K%2BMu9Carjs8jHMiBHo4aS4IAgXVY3mEuohCuRhL9%2FY9buuyvdKSNsao7qHwD%2F6bb9Sj2MFFhYP6gHf%2Bp1NipH05224aX9hMZty8Ovb3%2Bps%2FwNWYC8NVfft5bh8ETowaHQ5TUOPFdvU%2B5IkHOnnfbvz1%2F%2BjDeErd0Pdq4xH6c5%2FgZVQY9j6DFpazsw8d3karwXoBduv7I3mh7L3CSjTweABaRMw%3D", oauth_signature_method="RSA-SHA256", oauth_timestamp="1669720957", oauth_version="1.0" .. note:: Since headers are not usually encoded, the values of oauth parameters (especially oauth_nonce and oauth_signature) should be percent-encoded separately before being added to Authorization header. .. _rsa_sha256_request_generation: OAuth RSA-SHA256 Request Generation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To make the request: #. Use headers from previous section, #. Add request parameters to body, #. Percent-encode and send the request. Example of Request: .. code-block:: Request method: POST Request URI: https://gate.payneteasy.com/paynet/api/v4/transfer/4473 Headers: Authorization= OAuth oauth_nonce="C12mCYiI4RDeVTvyZDHS3f0vUbBNrK9G", oauth_signature="S4ahUgep8EV3RTIPdcpO%2FzEwB8V1XwFcGi5zhrYKySPVVA2PAY7AxezPcOCMhMwZQfcf8VOH8O9v5zZ%2BmYV2Qsq4kjPe1zEJIjjdjhI%2B2MX9VW8dWn9DyoTD2lkOYUwGsCteXU6mwGtNergN5KwGTJgqYPfzWFLllSAhGwuOd%2FgHYVRnA6jd4FvywRaRiSsnzgsasGJjWCGzX%2F1B8L78H%2FqD1W7dNJHjC2JbonUiHVT4aOs74qpqYE9zcb5rK8PH1l2ems3ArXhqFaSF85%2BYzJ%2BkSeDswqeIZ1y3NS8XPrmaLeymLNqKpOfAl9Ng47AmIjSYEw3s5fQ7Xi9t4j7Y6fQA4RnIzIdmH4oeMiyzn7dpA87wqnXm5AIm6Den2TJaDg90UoCMXuGHkqfL8GGUSjaWleTOHlk%2FO4dBGDRw4LP1aEaJktQRmT5xwyoaQfz%2Bh2MR7zDpRbVZDpUto1iYlQl5UlgojOHaLLjW1gqggbvrtVXUBT73KcIboWW00VUcbVUX5Yb%2FSu7hUZO2fNAh4LYrsVcXRJxCZuwhXCiqvbR3EziEFDGVEZnKUDLCodzHbVA9hbpxHGa7TvSLFEANCPDFtapMF9eo%2F1yOt5Tkxag4g5yNes93lrVabsTqckiFIfKR2R3CcwJ6MQtMfo8ticJcKxfo2v%2BTkC0bEaRkBGk%3D", oauth_consumer_key="ny_qa_merchant", oauth_signature_method="RSA-SHA256", oauth_timestamp="1669892964", oauth_version="1.0" Accept=*/* Content-Type=application/x-www-form-urlencoded; charset=ISO-8859-1 Body: cvv2=123&ipaddress=1.1.1.1&amount=15.42&credit_card_number=4210708776705721&client_orderid=1&deposit2card=false&destination-card-no=4232618181101636&expire_month=12&order_desc=Your%20order%20description&card_printed_name=Vasia%20Pupkin¤cy=RUB&expire_year=2099&redirect_url=http%3A%2F%2Fwww.example.com Example of CURL Request: .. code-block:: curl -H ' Authorization: OAuth oauth_consumer_key="paydroid", oauth_nonce="JOARJWjXJgfRUVqig8HIs0ouFWKfK4N5", oauth_signature="k5Z0XCVdDvb5h873XG6TDMO854PsuueSnSby4h0%2F3j4TKraY6ebjoFfDmndI09%2FQp6uEbCgKregNY5N0ccIVIay49l6v7jMdIFEfqU7E5eu%2BIJcoqG7kMFcdCu29hweYx7p4ZSk%2FUtdGlN3wyUUybCAx73XYoO0tkZteAleyzQlzdpzQ99vPS8FN2WMbNdzU3H2PLf0XOZy4DbAPleZGfu3GWxXe9erGsvzBJozs3WFxiPFeULfzWWsNc1h1P7cnzNbZqXkI%2BV3qiG3jc7tDqGRZP%2BLZFw3nihNlW%2F2Nlp%2FId6QG8kPNOEx2GxAuQa8kufv%2BbpohU8UftZG1SnNfoa4nDVgNbWoSbTXbgXxwHE0ZlccwT6q%2BHbTmgggvIdGN9JuuLUL8fDSCxqu3R7YMvcwArzrkd1XEFUFvYWxHc3QpfbBu0GyLkL7pnmz%2BFPTH48COZ7yXbK6nQDFSIy8lmaEJsnnjMYyMMVgjNkDNzqwNxOQuwprTZ7KzHqWrTj0zHTOHl44q1pJhIqMwxWDPMsziExYbAzxuOkdQFZi%2BK%2Bu3M7tvG5Foy7Vwj%2BDSPMPAhj7j2AmUG4HzaJcpiBWMq0CkGWRjvneOk3NiwkEwOxq5tJOFjsroditUqJQSX1PTqf%2FtuiqaE2Gt5EYl19ZzyXbxtMjGZwL%2BzF1cT6ftTi4%3D", oauth_signature_method="RSA-SHA256", oauth_timestamp="1669892687", oauth_version="1.0" ' --data ' amount=10.42 &card_printed_name=John%20Doe &card_recurring_payment_id=42322 &client_orderid=1 &credit_card_number=4210708776705721 ¤cy=USD &cvv2=123 &deposit2card=false &destination-card-no=4232618181101636 &destination_card_recurring_payment_id=61622 &expire_month=12 &expire_year=2099 &ipaddress=1.1.1.1.1 &order_desc=Your%20order%20description &redirect_url=http%3A%2F%2Fwww.example.com ' 'https://gate.payneteasy.com/paynet/api/v4/transfer/ENDPOINTID' OAuth RSA-SHA256 Integration example for PHP ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. raw:: html
.. code-block:: buffer = null; $this->reconfigure($config); $this->reset_request_settings(); $this->set_user_agent(); } public function reconfigure($config=array()) { // default configuration options $this->config = array_merge( array( // leave 'user_agent' blank for default, otherwise set this to // something that clearly identifies your app 'user_agent' => '', 'host' => 'api.twitter.com', 'method' => 'GET', 'consumer_key' => '', 'consumer_secret' => '', 'token' => '', 'secret' => '', // RSA private key (for RSA-SHA1 and RSA-SHA256 methods) // Please note that this is expected to be a string representing // the PEM-formatted key itself and NOT the file name 'private_key_pem' => '', // OAuth2 bearer token. This should already be URL encoded 'bearer' => '', // oauth signing variables that are not dynamic 'oauth_version' => '1.0', 'oauth_signature_method' => 'HMAC-SHA1', // you probably don't want to change any of these curl values 'curl_http_version' => CURL_HTTP_VERSION_1_1, 'curl_connecttimeout' => 30, 'curl_timeout' => 10, // for security this should always be set to 2. 'curl_ssl_verifyhost' => 2, // for security this should always be set to true. 'curl_ssl_verifypeer' => true, // for security this should always be set to true. 'use_ssl' => true, // you can get the latest cacert.pem from here http://curl.haxx.se/ca/cacert.pem // if you're getting HTTP 0 responses, check cacert.pem exists and is readable // without it curl won't be able to create an SSL connection 'curl_cainfo' => __DIR__ . DIRECTORY_SEPARATOR . 'cacert.pem', 'curl_capath' => __DIR__, // in some cases (very very odd ones) the SSL version must be set manually. // unless you know why your are changing this, you should leave it as false // to allow PHP to determine the value for this setting itself. 'curl_sslversion' => false, 'curl_followlocation' => false, // whether to follow redirects or not // support for proxy servers 'curl_proxy' => false, // really you don't want to use this if you are using streaming 'curl_proxyuserpwd' => false, // format username:password for proxy, if required 'curl_encoding' => '', // leave blank for all supported formats, else use gzip, deflate, identity etc // streaming API configuration 'is_streaming' => false, 'streaming_eol' => "\r\n", 'streaming_metrics_interval' => 10, // header or querystring. You should always use header! // this is just to help me debug other developers implementations 'as_header' => true, 'force_nonce' => false, // used for checking signatures. leave as false for auto 'force_timestamp' => false, // used for checking signatures. leave as false for auto ), $config ); } private function reset_request_settings($options=array()) { $this->request_settings = array( 'params' => array(), 'headers' => array(), 'with_user' => true, 'multipart' => false, ); if (!empty($options)) $this->request_settings = array_merge($this->request_settings, $options); } /** * Sets the useragent for PHP to use * If '$this->config['user_agent']' already has a value it is used instead of one * being generated. * * @return void value is stored to the config array class variable */ private function set_user_agent() { if (!empty($this->config['user_agent'])) return; $ssl = ($this->config['curl_ssl_verifyhost'] && $this->config['curl_ssl_verifypeer'] && $this->config['use_ssl']) ? '+' : '-'; $ua = 'tmhOAuth ' . self::VERSION . $ssl . 'SSL - //github.com/themattharris/tmhOAuth'; $this->config['user_agent'] = $ua; } /** * Generates a random OAuth nonce. * If 'force_nonce' is false a nonce will be generated, otherwise the value of '$this->config['force_nonce']' will be used. * * @param string $length how many characters the nonce should be before MD5 hashing. default 12 * @param string $include_time whether to include time at the beginning of the nonce. default true * @return $nonce as a string */ private function nonce($length=12, $include_time=true) { if ($this->config['force_nonce'] === false) { $prefix = $include_time ? microtime() : ''; return md5(substr($prefix . uniqid(), 0, $length)); } else { return $this->config['force_nonce']; } } /** * Generates a timestamp. * If 'force_timestamp' is false a timestamp will be generated, otherwise the value of '$this->config['force_timestamp']' will be used. * * @return $time as a string */ private function timestamp() { if ($this->config['force_timestamp'] === false) { $time = time(); } else { $time = $this->config['force_timestamp']; } return (string) $time; } /** * Encodes the string or array passed in a way compatible with OAuth. * If an array is passed each array value will will be encoded. * * @param mixed $data the scalar or array to encode * @return $data encoded in a way compatible with OAuth */ private function safe_encode($data) { if (is_array($data)) { return array_map(array($this, 'safe_encode'), $data); } else if (is_scalar($data)) { return str_ireplace( array('+', '%7E'), array(' ', '~'), rawurlencode($data) ); } else { return ''; } } /** * Decodes the string or array from it's URL encoded form * If an array is passed each array value will will be decoded. * * @param mixed $data the scalar or array to decode * @return string $data decoded from the URL encoded form */ private function safe_decode($data) { if (is_array($data)) { return array_map(array($this, 'safe_decode'), $data); } else if (is_scalar($data)) { return rawurldecode($data); } else { return ''; } } /** * Prepares OAuth1 signing parameters. * * @return void all required OAuth parameters, safely encoded, are stored to the class variable '$this->request_settings['oauth1_params']' */ private function prepare_oauth1_params() { $defaults = array( 'oauth_nonce' => $this->nonce(), 'oauth_timestamp' => $this->timestamp(), 'oauth_version' => $this->config['oauth_version'], 'oauth_consumer_key' => $this->config['consumer_key'], 'oauth_signature_method' => $this->config['oauth_signature_method'], ); // include the user token if it exists if ( $oauth_token = $this->token() ) $defaults['oauth_token'] = $oauth_token; $this->request_settings['oauth1_params'] = array(); // safely encode foreach ($defaults as $k => $v) { $this->request_settings['oauth1_params'][$this->safe_encode($k)] = $this->safe_encode($v); } } private function token() { if ( $this->request_settings['with_user'] ) { if (isset($this->config['token']) && !empty($this->config['token'])) return $this->config['token']; elseif (isset($this->config['user_token'])) return $this->config['user_token']; } return ''; } private function secret() { if ( $this->request_settings['with_user'] ) { if (isset($this->config['secret']) && !empty($this->config['secret'])) return $this->config['secret']; elseif (isset($this->config['user_secret'])) return $this->config['user_secret']; } return ''; } /** * Extracts and decodes OAuth parameters from the passed string * * @param string $body the response body from an OAuth flow method * @return array the response body safely decoded to an array of key => values */ public function extract_params($body) { $kvs = explode('&', $body); $decoded = array(); foreach ($kvs as $kv) { $kv = explode('=', $kv, 2); $kv[0] = $this->safe_decode($kv[0]); $kv[1] = $this->safe_decode($kv[1]); $decoded[$kv[0]] = $kv[1]; } return $decoded; } /** * Prepares the HTTP method for use in the base string by converting it to * uppercase. * * @return void value is stored to the class variable '$this->request_settings['method']' */ private function prepare_method() { $this->request_settings['method'] = strtoupper($this->request_settings['method']); } /** * Prepares the URL for use in the base string by ripping it apart and * reconstructing it. * * Ref: 3.4.1.2 * * @return void value is stored to the class array variable '$this->request_settings['url']' */ private function prepare_url() { $parts = parse_url($this->request_settings['url']); $port = isset($parts['port']) ? $parts['port'] : false; $scheme = $parts['scheme']; $host = $parts['host']; $path = isset($parts['path']) ? $parts['path'] : false; $port or $port = ($scheme == 'https') ? '443' : '80'; if (($scheme == 'https' && $port != '443') || ($scheme == 'http' && $port != '80')) { $host = "$host:$port"; } // the scheme and host MUST be lowercase $this->request_settings['url'] = strtolower("$scheme://$host"); // but not the path $this->request_settings['url'] .= $path; } /** * If the request uses multipart, and the parameter isn't a file path, prepend a space * otherwise return the original value. we chose a space here as twitter whitespace trims from * the beginning of the tweet. we don't use \0 here because it's the character for string * termination. * * @param the parameter value * @return string the original or modified string, depending on the request and the input parameter */ private function multipart_escape($value) { if (!$this->request_settings['multipart'] || strpos($value, '@') !== 0) return $value; // see if the parameter is a file. // we split on the semi-colon as it's the delimiter used on media uploads // for fields with semi-colons this will return the original string list($file) = explode(';', substr($value, 1), 2); if (file_exists($file)) return $value; return " $value"; } /** * Prepares all parameters for the base string and request. * Multipart parameters are ignored as they are not defined in the specification, * all other types of parameter are encoded for compatibility with OAuth. * * @param array $params the parameters for the request * @return void prepared values are stored in the class array variable '$this->request_settings' */ private function prepare_params() { $doing_oauth1 = false; $this->request_settings['prepared_params'] = array(); $prepared = &$this->request_settings['prepared_params']; $prepared_pairs = array(); $prepared_pairs_with_oauth = array(); if (isset($this->request_settings['oauth1_params'])) { $oauth1 = &$this->request_settings['oauth1_params']; $doing_oauth1 = true; $params = array_merge($oauth1, $this->request_settings['params']); // Remove oauth_signature if present // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.") unset($params['oauth_signature']); // empty the oauth1 array. we reset these values later in this method $oauth1 = array(); } else { $params = $this->request_settings['params']; } // Parameters are sorted by name, using lexicographical byte value ordering. // Ref: Spec: 9.1.1 (1) uksort($params, 'strcmp'); // set this now so we're not doing it on every parameter $supports_curl_file = class_exists('CurlFile', false); // encode params unless we're doing multipart foreach ($params as $k => $v) { $k = $this->request_settings['multipart'] ? $k : $this->safe_encode($k); if (is_array($v)) $v = implode(',', $v); // we don't need to do the multipart escaping if we support curlfile if ($supports_curl_file && ($v instanceof CurlFile)) { // leave $v alone } elseif ($this->request_settings['multipart']) { $v = $this->multipart_escape($v); } else { $v = $this->safe_encode($v); } // split parameters for the basestring and authorization header, and recreate the oauth1 array if ($doing_oauth1) { // if we're doing multipart, only store the oauth_* params, ignore the users request params if ((strpos($k, 'oauth') === 0) || !$this->request_settings['multipart']) $prepared_pairs_with_oauth[] = "{$k}={$v}"; if (strpos($k, 'oauth') === 0) { $oauth1[$k] = $v; continue; } } $prepared[$k] = $v; if (!$this->request_settings['multipart']) $prepared_pairs[] = "{$k}={$v}"; } if ($doing_oauth1) { $this->request_settings['basestring_params'] = implode('&', $prepared_pairs_with_oauth); } // setup params for GET/POST/PUT method handling if (!empty($prepared)) { $content = implode('&', $prepared_pairs); switch ($this->request_settings['method']) { case 'PUT': // fall through to POST as PUT should be treated the same case 'POST': $this->request_settings['postfields'] = $this->request_settings['multipart'] ? $prepared : $content; break; default: $this->request_settings['querystring'] = $content; break; } } } /** * Prepares the OAuth signing key * * @return void prepared signing key is stored in the class variable 'signing_key' */ private function prepare_signing_key() { $left = $this->safe_encode($this->config['consumer_secret']); $right = $this->safe_encode($this->secret()); $this->request_settings['signing_key'] = $left . '&' . $right; } /** * Prepare the base string. * Ref: Spec: 9.1.3 ("Concatenate Request Elements") * * @return void prepared base string is stored in the class variable 'base_string' */ private function prepare_base_string() { $url = $this->request_settings['url']; # if the host header is set we need to rewrite the basestring to use # that, instead of the request host. otherwise the signature won't match # on the server side if (!empty($this->request_settings['headers']['Host'])) { $url = str_ireplace( $this->config['host'], $this->request_settings['headers']['Host'], $url ); } $base = array( $this->request_settings['method'], $url, $this->request_settings['basestring_params'] ); $this->request_settings['basestring'] = implode('&', $this->safe_encode($base)); } /** * Signs the OAuth 1 request * * @return void oauth_signature is added to the parameters in the class array variable '$this->request_settings' */ private function prepare_oauth_signature() { switch ($this->config['oauth_signature_method']) { case 'HMAC-SHA1': $signature = $this->sign_with_hmac('sha1'); break; case 'HMAC-SHA256': $signature = $this->sign_with_hmac('sha256'); break; case 'RSA-SHA1': $signature = $this->sign_with_rsa(OPENSSL_ALGO_SHA1); break; case 'RSA-SHA256': $signature = $this->sign_with_rsa(OPENSSL_ALGO_SHA256); break; default: throw new Exception("Unsupported oauth_signature_method: '" . $this->config['oauth_signature_method'] . "'"); } $this->request_settings['oauth1_params']['oauth_signature'] = $this->safe_encode(base64_encode($signature)); } /** * Signs the OAuth 1 request using HMAC-based signature algorithm * * @param string $algorithm algorithm name (like sha1 or sha256) * @return binary signature */ private function sign_with_hmac($algorithm) { return hash_hmac( $algorithm, $this->request_settings['basestring'], $this->request_settings['signing_key'], true ); } /** * Signs the OAuth 1 request using RSA-based signature algorithm * * @param mixed $algorithm ID or name of hash algorithm that will be * used to compute base string hash before encrypting it with RSA; * values understood by openssl_sign()'s $signature_alg parameter * are accepted here (like 'sha1' or OPENSSL_ALGO_SHA256) * @return binary signature */ private function sign_with_rsa($algorithm) { if (!function_exists('openssl_sign')) { throw new Exception("openssl_sign function does not exist. Please make sure Openssl extension is installed"); } if ($this->config['private_key_pem'] == '') { throw new Exception("No private key PEM is configured, cannot sign"); } $ok = openssl_sign($this->request_settings['basestring'], $signature, $this->config['private_key_pem'], $algorithm); if (!$ok) { throw new Exception("Cannot sign: " . openssl_error_string()); } return $signature; } /** * Prepares the Authorization header * * @return void prepared authorization header is stored in the class variable headers['Authorization'] */ private function prepare_auth_header() { if (!$this->config['as_header']) return; // oauth1 if (isset($this->request_settings['oauth1_params'])) { // sort again as oauth_signature was added post param preparation uksort($this->request_settings['oauth1_params'], 'strcmp'); $encoded_quoted_pairs = array(); foreach ($this->request_settings['oauth1_params'] as $k => $v) { $encoded_quoted_pairs[] = "{$k}=\"{$v}\""; } $header = 'OAuth ' . implode(', ', $encoded_quoted_pairs); } elseif (!empty($this->config['bearer'])) { $header = 'Bearer ' . $this->config['bearer']; } if (isset($header)) $this->request_settings['headers']['Authorization'] = $header; } /** * Create the bearer token for OAuth2 requests from the consumer_key and consumer_secret. * * @return string the bearer token */ public function bearer_token_credentials() { $credentials = implode(':', array( $this->safe_encode($this->config['consumer_key']), $this->safe_encode($this->config['consumer_secret']) )); return base64_encode($credentials); } /** * Make an HTTP request using this library. This method doesn't return anything. * Instead the response should be inspected directly. * * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc * @param string $url the request URL without query string parameters * @param array $params the request parameters as an array of key=value pairs. Default empty array * @param string $useauth whether to use authentication when making the request. Default true * @param string $multipart whether this request contains multipart data. Default false * @param array $headers any custom headers to send with the request. Default empty array * @return int the http response code for the request. 0 is returned if a connection could not be made */ public function request($method, $url, $params=array(), $useauth=true, $multipart=false, $headers=array()) { $options = array( 'method' => $method, 'url' => $url, 'params' => $params, 'with_user' => true, 'multipart' => $multipart, 'headers' => $headers ); $options = array_merge($this->default_options(), $options); if ($useauth) { return $this->user_request($options); } else { return $this->unauthenticated_request($options); } } public function apponly_request($options=array()) { $options = array_merge($this->default_options(), $options, array( 'with_user' => false, )); $this->reset_request_settings($options); if ($options['without_bearer']) { return $this->oauth1_request(); } else { $this->prepare_method(); $this->prepare_url(); $this->prepare_params(); $this->prepare_auth_header(); return $this->curlit(); } } public function user_request($options=array()) { $options = array_merge($this->default_options(), $options, array( 'with_user' => true, )); $this->reset_request_settings($options); return $this->oauth1_request(); } public function unauthenticated_request($options=array()) { $options = array_merge($this->default_options(), $options, array( 'with_user' => false, )); $this->reset_request_settings($options); $this->prepare_method(); $this->prepare_url(); $this->prepare_params(); return $this->curlit(); } /** * Signs the request and adds the OAuth signature. This runs all the request * parameter preparation methods. * * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc * @param string $url the request URL without query string parameters * @param array $params the request parameters as an array of key=value pairs * @param boolean $with_user whether to include the user credentials when making the request. * @return void */ private function oauth1_request() { $this->prepare_oauth1_params(); $this->prepare_method(); $this->prepare_url(); $this->prepare_params(); $this->prepare_base_string(); $this->prepare_signing_key(); $this->prepare_oauth_signature(); $this->prepare_auth_header(); return $this->curlit(); } private function default_options() { return array( 'method' => 'GET', 'params' => array(), 'with_user' => true, 'multipart' => false, 'headers' => array(), 'without_bearer' => false, ); } /** * Make a long poll HTTP request using this library. This method is * different to the other request methods as it isn't supposed to disconnect * * Using this method expects a callback which will receive the streaming * responses. * * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc * @param string $url the request URL without query string parameters * @param array $params the request parameters as an array of key=value pairs * @param string $callback the callback function to stream the buffer to. * @return void */ public function streaming_request($method, $url, $params=array(), $callback='') { if ( ! empty($callback) ) { if ( ! is_callable($callback) ) { return false; } $this->config['streaming_callback'] = $callback; } $this->metrics['start'] = time(); $this->metrics['interval_start'] = $this->metrics['start']; $this->metrics['messages'] = 0; $this->metrics['last_messages'] = 0; $this->metrics['bytes'] = 0; $this->metrics['last_bytes'] = 0; $this->config['is_streaming'] = true; $this->request($method, $url, $params); } /** * Handles the updating of the current Streaming API metrics. * * @return array the metrics for the streaming api connection */ private function update_metrics() { $now = time(); if (($this->metrics['interval_start'] + $this->config['streaming_metrics_interval']) > $now) return null; $this->metrics['mps'] = round( ($this->metrics['messages'] - $this->metrics['last_messages']) / $this->config['streaming_metrics_interval'], 2); $this->metrics['bps'] = round( ($this->metrics['bytes'] - $this->metrics['last_bytes']) / $this->config['streaming_metrics_interval'], 2); $this->metrics['last_bytes'] = $this->metrics['bytes']; $this->metrics['last_messages'] = $this->metrics['messages']; $this->metrics['interval_start'] = $now; return $this->metrics; } /** * Utility function to create the request URL in the requested format. * If a fully-qualified URI is provided, it will be returned. * Any multi-slashes (except for the protocol) will be replaced with a single slash. * * * @param string $request the API method without extension * @param string $extension the format of the response. Default json. Set to an empty string to exclude the format * @return string the concatenation of the host, API version, API method and format, or $request if it begins with http */ public function url($request, $extension='json') { // remove multi-slashes $request = preg_replace('$([^:])//+$', '$1/', $request); if (stripos($request, 'http') === 0 || stripos($request, '//') === 0) { return $request; } $extension = strlen($extension) > 0 ? ".$extension" : ''; $proto = $this->config['use_ssl'] ? 'https:/' : 'http:/'; // trim trailing slash $request = ltrim($request, '/'); $pos = strlen($request) - strlen($extension); if (substr($request, $pos) === $extension) $request = substr_replace($request, '', $pos); return implode('/', array( $proto, $this->config['host'], $request . $extension )); } /** * Public access to the private safe decode/encode methods * * @param string $text the text to transform * @param string $mode the transformation mode. either encode or decode * @return string $text transformed by the given $mode */ public function transformText($text, $mode='encode') { return $this->{"safe_$mode"}($text); } /** * Utility function to parse the returned curl headers and store them in the * class array variable. * * @param object $ch curl handle * @param string $header the response headers * @return string the length of the header */ private function curlHeader($ch, $header) { $this->response['raw'] .= $header; list($key, $value) = array_pad(explode(':', $header, 2), 2, null); $key = trim($key); $value = trim($value); if ( ! isset($this->response['headers'][$key])) { $this->response['headers'][$key] = $value; } else { if (!is_array($this->response['headers'][$key])) { $this->response['headers'][$key] = array($this->response['headers'][$key]); } $this->response['headers'][$key][] = $value; } return strlen($header); } /** * Utility function to parse the returned curl buffer and store them until * an EOL is found. The buffer for curl is an undefined size so we need * to collect the content until an EOL is found. * * This function calls the previously defined streaming callback method. * * @param object $ch curl handle * @param string $data the current curl buffer * @return int the length of the data string processed in this function */ private function curlWrite($ch, $data) { $l = strlen($data); if (strpos($data, $this->config['streaming_eol']) === false) { $this->buffer .= $data; return $l; } $buffered = explode($this->config['streaming_eol'], $data); $content = $this->buffer . $buffered[0]; $this->metrics['messages']++; $this->metrics['bytes'] += strlen($content); if ( ! is_callable($this->config['streaming_callback'])) return 0; $metrics = $this->update_metrics(); $stop = call_user_func( $this->config['streaming_callback'], $content, strlen($content), $metrics ); $this->buffer = $buffered[1]; if ($stop) return 0; return $l; } /** * Makes a curl request. Takes no parameters as all should have been prepared * by the request method * * the response data is stored in the class variable 'response' * * @return int the http response code for the request. 0 is returned if a connection could not be made */ private function curlit() { $this->response = array( 'raw' => '' ); // configure curl $c = curl_init(); if ($this->request_settings['method'] == 'GET' && isset($this->request_settings['querystring'])) { $this->request_settings['url'] = $this->request_settings['url'] . '?' . $this->request_settings['querystring']; } elseif ($this->request_settings['method'] == 'POST' || $this->request_settings['method'] == 'PUT') { $postfields = array(); if (isset($this->request_settings['postfields'])) $postfields = $this->request_settings['postfields']; curl_setopt($c, CURLOPT_POSTFIELDS, $postfields); } curl_setopt($c, CURLOPT_CUSTOMREQUEST, $this->request_settings['method']); curl_setopt_array($c, array( CURLOPT_HTTP_VERSION => $this->config['curl_http_version'], CURLOPT_USERAGENT => $this->config['user_agent'], CURLOPT_CONNECTTIMEOUT => $this->config['curl_connecttimeout'], CURLOPT_TIMEOUT => $this->config['curl_timeout'], CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => $this->config['curl_ssl_verifypeer'], CURLOPT_SSL_VERIFYHOST => $this->config['curl_ssl_verifyhost'], CURLOPT_FOLLOWLOCATION => $this->config['curl_followlocation'], CURLOPT_PROXY => $this->config['curl_proxy'], CURLOPT_ENCODING => $this->config['curl_encoding'], CURLOPT_URL => $this->request_settings['url'], // process the headers CURLOPT_HEADERFUNCTION => array($this, 'curlHeader'), CURLOPT_HEADER => false, CURLINFO_HEADER_OUT => true, )); if ($this->config['curl_cainfo'] !== false) curl_setopt($c, CURLOPT_CAINFO, $this->config['curl_cainfo']); if ($this->config['curl_capath'] !== false) curl_setopt($c, CURLOPT_CAPATH, $this->config['curl_capath']); if ($this->config['curl_proxyuserpwd'] !== false) curl_setopt($c, CURLOPT_PROXYUSERPWD, $this->config['curl_proxyuserpwd']); if ($this->config['curl_sslversion'] !== false) curl_setopt($c, CURLOPT_SSLVERSION, $this->config['curl_sslversion']); if ($this->config['is_streaming']) { // process the body $this->response['content-length'] = 0; curl_setopt($c, CURLOPT_TIMEOUT, 0); curl_setopt($c, CURLOPT_WRITEFUNCTION, array($this, 'curlWrite')); } if ( ! empty($this->request_settings['headers'])) { foreach ($this->request_settings['headers'] as $k => $v) { $headers[] = trim($k . ': ' . $v); } curl_setopt($c, CURLOPT_HTTPHEADER, $headers); } if (isset($this->config['block']) && (true === $this->config['block'])) return 0; // do it! $response = curl_exec($c); $code = curl_getinfo($c, CURLINFO_HTTP_CODE); $info = curl_getinfo($c); $error = curl_error($c); $errno = curl_errno($c); curl_close($c); // store the response $this->response['code'] = $code; $this->response['response'] = $response; $this->response['info'] = $info; $this->response['error'] = $error; $this->response['errno'] = $errno; if (!isset($this->response['raw'])) { $this->response['raw'] = ''; } $this->response['raw'] .= $response; return $code; } } /* * Configuration - Replace these values with your actual credentials */ $config = [ 'server_host' => 'sandbox.payneteasy.com', 'endpoint_id' => 1, // Your endpoint ID 'merchant_login' => 'your-merchant-login', 'private_key_file' => 'test-private-key.pem', // Your private key file 'test_mode' => true // Set to false for production ]; /* * API Request Function */ function makeApiRequest($endpoint, $params, $config) { // Load private key $private_key_pem = file_get_contents($config['private_key_file']); if (!$private_key_pem) { throw new Exception("Could not load private key file"); } // Initialize OAuth $oauth = new tmhOAuth(); $oauth->reconfigure([ 'consumer_key' => $config['merchant_login'], 'oauth_signature_method' => 'RSA-SHA256', 'private_key_pem' => $private_key_pem, 'curl_ssl_verifypeer' => $config['test_mode'] ? false : true ]); // Make request $url = "https://{$config['server_host']}/paynet/api/v4/{$endpoint}/{$config['endpoint_id']}"; $status = $oauth->request('POST', $url, $params); // Handle response if ($status == 0) { throw new Exception('HTTP request failed'); } elseif ($status != 200) { throw new Exception("HTTP error: {$status}"); } return [ 'status' => $status, 'response' => $oauth->response['response'], 'headers' => $oauth->response['headers'] ]; } /* * Example Usage - Transfer by Reference */ if (isset($_POST['action']) && $_POST['action'] == 'transfer') { try { $params = [ 'client-order-id' => 'transfer-' . uniqid(), 'amount' => $_POST['amount'] ?? '100', 'currency' => $_POST['currency'] ?? 'RUB', 'order_desc' => $_POST['description'] ?? 'Consumer credit', 'destination-card-no' => $_POST['card_number'] ?? '4444333322221111' ]; $result = makeApiRequest('transfer-by-ref', $params, $config); $response = json_decode($result['response'], true); echo "
Success! Transaction ID: " . ($response['transaction-id'] ?? 'N/A') . "
"; } catch (Exception $e) { echo "
Error: " . $e->getMessage() . "
"; } } /* * Example Usage - Bank Payout */ if (isset($_POST['action']) && $_POST['action'] == 'payout') { try { $params = [ 'client-order-id' => 'payout-' . uniqid(), 'amount' => $_POST['amount'] ?? '100', 'currency' => $_POST['currency'] ?? 'USD', 'order_desc' => $_POST['description'] ?? 'Bank payout', 'account_number' => $_POST['account_number'] ?? '123412341234', 'bank_name' => $_POST['bank_name'] ?? 'Test Bank' ]; $result = makeApiRequest('payout', $params, $config); $response = json_decode($result['response'], true); echo "
Success! Payout ID: " . ($response['payout-id'] ?? 'N/A') . "
"; } catch (Exception $e) { echo "
Error: " . $e->getMessage() . "
"; } } ?> .. raw:: html