Comments (2)
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.
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)
- undefined script: HOT 1
- Role id HOT 1
- composer outdated
- Documentation Typo HOT 2
- Move document to a specific folder HOT 1
- $updateHttpMethod = Request::METHOD_PATCH HOT 1
- Add smart fields support HOT 1
- Signing Link Expiration does not accept null HOT 2
- Missing "secret_key" setter when creating event subscription
- unable to use file from aws s3
- Type error in SplFileInfoItem
- If you're constantly getting "invalid_client" as a response...
- Missing essential features like Create User, Get Document List for User HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from signnowphpsdk.