Git Product home page Git Product logo

Comments (2)

lubosdz avatar lubosdz commented on May 25, 2024

Looks like uploading document does not work at all. Following is standalone code that fails uploading PDF as descripbed in API documentation:

// verify => false ... bypass error in devel "self signed certificate in certificate chain"
$client = new \GuzzleHttp\Client(['verify' => false]); 

$response = $client->request('POST', 'https://api-eval.signnow.com/document', [
  'multipart' => [
	[
		'name' => 'file',
		'contents' => file_get_contents($pathToPdfFile),
	]
  ],
  'headers' => [
	'Accept' => 'application/json',
	'Authorization' => 'Bearer '.$bearerAccessToken,
  ],
]);

$resp = $response->getBody(); // false

$resp holds false and in dashboard API logs is {"errors":[{"code":65579,"message":"Must upload one file"}]} .

I tried few other clients - cURL, multipart file_get_contents - same error.

Seems related - signnow/SignNowNodeSDK#1

EDIT:
Figured out reason - online web documentation is not quite complete, additional attribute must be included in order to allow detecting file type (pdf, docx, ..) as well as stored file name - following works:

// verify => false ... bypass error in devel "self signed certificate in certificate chain"
$client = new \GuzzleHttp\Client(['verify' => false]); 

$response = $client->request('POST', 'https://api-eval.signnow.com/document', [
  'multipart' => [
	[
		'name' => 'file',
		'filename' => basename($path), // <-- (!) improtant, tell file extension and stored filename
		'contents' => file_get_contents($pathToPdfFile),
	]
  ],
  'headers' => [
	'Accept' => 'application/json',
	'Authorization' => 'Bearer '.$bearerAccessToken,
  ],
]);

$resp = $response->getBody(); // OK

from signnowphpsdk.

lubosdz avatar lubosdz commented on May 25, 2024

Here is working PHP wrapper draft around Signnow API - gets bearer token & uploads file. Usage:

// init class
$signnow = new Signnow("https://api-eval.signnow.com", "your@email", "login.PWD!", "abc123-YOUR-API-TOKEN***");

// get bearer token
$accessToken = $signnow->getAccessToken();

// upload file
$path = '/var/test/sample-contract.pdf';
$resp = $signnow->uploadFile($path); // e.g. {'id' => 'abc123*****'}

PHP wrapper:

/**
* Standalone PHP wrapper around Signnow API - basic working draft, add more methods as needed ..
* (no dependencies, no guzzle, ..)
*/
class Signnow
{
	protected
		/** @var string API URL */
		$urlEndpoint = 'https://api-eval.signnow.com',

		/** @var string (Static) auth token under user's account needed to obtain access/bearer token */
		$basicAuthToken = 'XD8ODNmNTU6NWNmN....',

		/** @var string Signnow account user creadentials */
		$loginEmail = '[email protected]',
		$loginPassword = 'Your.pwd!',

		/** @var string (Dynamic) access (bearer) token used across most of requests */
		$accessToken,

		/** @var string Cache key specific to credentials */
		$cacheKey;

	/**
	* Constructor - set user credentials for API requests
	* @param string $url
	* @param string $email
	* @param string $pwd
	* @param string $basicToken
	*/
	public function __construct($url, $email, $pwd, $basicToken)
	{
		$this->urlEndpoint = trim($url);
		$this->loginEmail = trim($email);
		$this->loginPassword = trim($pwd);
		$this->basicAuthToken = trim($basicToken);
		$this->cacheKey = substr(md5($url.$email.$basicToken), 0, 10);
		if(!$this->cacheKey){
			throw new \Exception('Cache key may not be empty.');
		}
		$this->cacheKey = "signnow.bearer.resp.{$this->cacheKey}";
	}

	/**
	* Return bearer (access) token
	* Either load valid from cache or obtain a new token
	*/
	public function getAccessToken($forceNew = false, $refresh = false) : string
	{
		if($this->accessToken && !$forceNew && !$refresh){
			return $this->accessToken;
		}

		// optional - set your cache handler, this is just example for Yii2 framework
		//$cache = Yii::$app->cache;
		$cache = null;
		$resp = $forceNew || !$cache? '' : $cache->get($this->cacheKey);

		if(!$resp || $forceNew){
			// generate bearer token
			$data = [
				// password|refresh_token|authorization_code
				'grant_type' => 'password',
				'username' => $this->loginEmail,
				'password' => $this->loginPassword,
			];

			$options = [
				'headers' => [
					'Authorization: Basic '.$this->basicAuthToken,
					'Content-type: application/x-www-form-urlencoded',
			]];

			$resp = $this->doHttpRequest('/oauth2/token', $data, $options);

			if(!empty($resp['access_token']) && !empty($resp['expires_in']) && $cache){
				// store response to cache
				$cache->set($this->cacheKey, $resp);
			}
		}

		// should be valid, default expiration is 30 days - 2592000 secs
		$this->accessToken = empty($resp['access_token']) ? '' : $resp['access_token'];

		if($this->accessToken && $refresh && !$forceNew && !empty($resp['refresh_token'])){
			// refresh but only if this is not just forced new token, makes no sense to refresh new token
			$data = [
				'grant_type' => 'refresh_token',
				'refresh_token' => $resp['refresh_token'],
			];
			$options = [
				'headers' => [
					'Authorization: Basic '.$this->basicAuthToken,
					'Content-type: application/x-www-form-urlencoded',
			]];
			$resp = $this->doHttpRequest('/oauth2/token', $data, $options);
			if(!empty($resp['access_token']) && !empty($resp['expires_in']) && $cache){
				$cache->set($this->cacheKey, $resp);
				$this->accessToken = $resp['access_token'];
			}
		}

		return $this->accessToken;
	}

	/**
	* Return info about bearer token validity - expires_in [secs], token_type, ..
	*/
	public function verifyToken()
	{
		$info = [];
		$accessToken = $this->getAccessToken();

		if($accessToken){
			$options = [
				'method' => 'GET', // (!) may not be POST, or returns "invalid_client"
				'headers' => [
					'Authorization: Bearer '.$accessToken,
					'Content-type: application/x-www-form-urlencoded',
			]];
			$info = $this->doHttpRequest('/oauth2/token', [], $options);
		}

		return $info;
	}

	/**
	* Return user account info - id, primary_email, emails[0] .., first_name, last_name, ..
	*/
	public function getUserInfo()
	{
		$info = [];
		$accessToken = $this->getAccessToken();

		if($accessToken){
			$options = [
				'method' => 'GET', // (!) may not be POST, or returns "invalid_client"
				'headers' => [
					'Authorization: Bearer '.$accessToken,
					'Content-type: application/json',
			]];
			$info = $this->doHttpRequest('/user', [], $options);
		}

		return $info;
	}

	/**
	* Uploads a file to user's account and returns unique id of the uploaded document.
	* Accepts .doc, .docx, .pdf, .xls, .xlsx, .ppt, .pptx and .png file types
	* @param string $path Abs. path to file
	*/
	public function uploadFile($path)
	{
		if(!is_file($path)){
			throw new \Exception("File not found in [{$path}].");
		}

		if(!$this->getAccessToken()){
			throw new \Exception("Invalid access token.");
		}

		// based on https://stackoverflow.com/questions/4003989/upload-a-file-using-file-get-contents
		$boundary = '-----------------------'.microtime(true);
		$ext = strtolower( pathinfo($path, PATHINFO_EXTENSION) );
		$file_contents = file_get_contents($path);

		if(!in_array($ext, ['pdf', 'docx', 'xlsx', 'png'])){
			throw new \Exception("File type [{$ext}] not allowed for uploading.");
		}

		// build multipart stream
		$data =  "--{$boundary}\r\n"
		// (!) important - must include filename=\"".basename($path)."\" - to detect file type
		// also will store file name in dashboard/documents
		  ."Content-Disposition: form-data; name=\"file\"; filename=\"".basename($path)."\"; \r\n\r\n"
		  .$file_contents."\r\n"
		  ."--{$boundary}--\r\n";

		$options = [
			'headers' => [
				'Authorization: Bearer '.$this->accessToken,
				'Content-type: multipart/form-data; boundary='.$boundary,
		]];

		// resp e.g. {'id' => "3b323840975b9a*********"}
		return $this->doHttpRequest('/document', $data, $options);
	}

	/**
	* HTTP/HTTPS request without SSL verification and without any dependency
	* https://stackoverflow.com/questions/11319520/php-posting-json-via-file-get-contents
	* @param string $url
	* @param string|array $data
	* @param string $options e.g. timeout => 5, method => 'GET', content, header, ..
	* @param bool $tryJson If TRUE, try to convert response string into JSON
	*/
	protected function doHttpRequest($url, $data = null, array $options = [], $tryJson = true)
	{
		$options = array_change_key_case($options, CASE_LOWER);

		// default Signnow API headers
		$headers = [
			'Accept: application/json',
		];

		if(!empty($options['headers'])){
			$headers = array_merge($headers, $options['headers']);
			unset($options['headers']);
		}

		$http = [
			'timeout' => 5, // 5 secs
			'method' => 'POST',
			'header' => $headers,
		];

		if($data){
			$http['content'] = $data;
		}

		// explicitly defined HTTP section
		if(!empty($options['http'])){
			$http = $options['http'] + $http;
			unset($options['http']);
		}

		$ssl = [
			// disable certificate check if needed in sandbox
			//'verify_peer' => DEBUG_MODE ? false : true,
			//'verify_peer_name' => DEBUG_MODE ? false : true,
		];

		// explicitly defined SSL section
		if(!empty($options['ssl'])){
			$ssl = $options['ssl'] + $ssl;
			unset($options['ssl']);
		}

		// merge remaining HTTP options
		if(!empty($options)){
			$http = $options + $http;
		}

		// build e.g. POST FORM data - must be "Content-type: application/x-www-form-urlencoded"
		if(!empty($http['content']) && is_array($http['content'])){
			$http['content'] = http_build_query($http['content']);
		}

		$ctx = stream_context_create([
			'http' => $http,
			'ssl' => $ssl,
		]);

		// convert relative URL to absolute
		if(false === stripos($url, '://')){
			$url = rtrim($this->urlEndpoint, ' \/') .'/'. ltrim($url, ' \/');
		}

		$response = file_get_contents($url, false, $ctx);

		// attempt converting to JSON
		if($tryJson && $response && is_string($response) && !!($tmp = json_decode($response, true))){
			$response = $tmp;
		}

		return $response;
	}
}

from signnowphpsdk.

Related Issues (14)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.