-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit ce7ccc8
Showing
4 changed files
with
255 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2017 FunkyTime | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# funkytime/yuki | ||
A php connector for Yuki's Sales API (subset), intended to create Sales Invoices. | ||
|
||
```php | ||
$invoice = [ | ||
'Reference' => '', | ||
// ... | ||
'Contact' => [ | ||
'ContactCode' => '', | ||
// ... | ||
] | ||
'ContactPerson' => ['FullName' => ''], | ||
'InvoiceLines' => [ | ||
'InvoiceLine' => [ | ||
'ProductQuantity' => '', | ||
'Product' => [ | ||
'Description' => '', | ||
// ... | ||
] | ||
] | ||
] | ||
]; | ||
]; | ||
new \FunkyTime\Yuki($api_key)->ProcessInvoice($invoice); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"name": "funkytime/yuki", | ||
"description": "A php connector for Yuki's Sales API (subset), intended to create Sales Invoices.", | ||
"license": "MIT", | ||
"authors": [ | ||
{ | ||
"name": "FunkyTime", | ||
"homepage": "https://funkytime.com" | ||
} | ||
], | ||
"require": { | ||
"php": "^5.3.3 || ^7.0" | ||
}, | ||
"extra": { | ||
"branch-alias": { | ||
"dev-master": "0.1.x-dev" | ||
} | ||
}, | ||
"autoload": { | ||
"psr-4": { | ||
"FunkyTime\\": "src/" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
<?php | ||
|
||
namespace FunkyTime; | ||
|
||
use Exception, SoapClient, SoapVar; | ||
|
||
/** | ||
* Connector for Yuki's Sales SOAP Webservice (subset), intended to create Sales Invoices. | ||
* | ||
* by FunkyTime.com | ||
* | ||
* Magic methods (passed to SOAP call): | ||
* @method array Authenticate(array $params) | ||
* @method array Administrations(array $params) | ||
* @method array ProcessSalesInvoices(array $params) | ||
*/ | ||
class Yuki | ||
{ | ||
const SALES_WSDL = 'https://api.yukiworks.nl/ws/Sales.asmx?WSDL'; | ||
|
||
private $soap, // the SOAP client | ||
// the currently active identifiers for the logged in Yuki user: | ||
$sid, // SessionID | ||
$aid; // AdministrationID | ||
|
||
/** | ||
* Wrapper for ProcessSalesInvoices. Will throw an Exception if it did not succeed (e.g. duplicate invoice number). | ||
* Format the parameter like this: | ||
* $invoice = [ | ||
* 'Reference' => '', | ||
* ... | ||
* 'Contact' => [ | ||
* 'ContactCode' => '', | ||
* ... | ||
* ] | ||
* 'ContactPerson' => ['FullName' => ''], | ||
* 'InvoiceLines' => [ | ||
* 'InvoiceLine' => [ | ||
* 'ProductQuantity' => '', | ||
* 'Product' => [ | ||
* 'Description' => '', | ||
* ... | ||
* ] | ||
* ] | ||
* ] | ||
* ]; | ||
* See http://www.yukiworks.nl/schemas/SalesInvoices.xsd, but note that only a subset is supported here (see code) | ||
* | ||
* @param array $invoice Invoice data in associative array, with matching Yuki keys | ||
* @param bool $escaped Whether the data still needs to be trimmed and escaped for inclusion in XML tags | ||
* @return mixed | ||
* @throws Exception | ||
*/ | ||
public function ProcessInvoice($invoice, $escaped = false) { | ||
// This currently assumes that all array values are properly escaped | ||
if(!$escaped) { | ||
// todo | ||
} | ||
|
||
// General fields | ||
$SalesInvoice = ''; | ||
foreach(['Reference', 'Subject', 'PaymentMethod', 'Date', 'DueDate', 'Currency', 'ProjectCode', 'Remarks'] as $k) { | ||
if(!empty($invoice[$k])) $SalesInvoice .= "<$k>$invoice[$k]</$k>"; | ||
if($k === 'PaymentMethod') $SalesInvoice .= '<Process>true</Process>'; // invoice is marked as fully prepared | ||
} | ||
|
||
// Client (company) | ||
$Contact = ''; | ||
foreach(['ContactCode', 'FullName', 'CountryCode', 'City', 'Zipcode', 'AddressLine_1', 'AddressLine_2', 'EmailAddress', 'VATNumber', 'ContactType'] as $k) { | ||
if(!empty($invoice['Contact'][$k])) $Contact .= "<$k>{$invoice['Contact'][$k]}</$k>"; | ||
} | ||
$SalesInvoice .= "<Contact>$Contact</Contact>"; | ||
|
||
if(!empty($invoice['ContactPerson']) && !empty($invoice['ContactPerson']['FullName'])) { | ||
$SalesInvoice .= "<ContactPerson><FullName>{$invoice['ContactPerson']['FullName']}</FullName></ContactPerson>"; | ||
} | ||
|
||
// Invoice lines | ||
if(!empty($invoice['InvoiceLines'])) { | ||
$InvoiceLines = []; | ||
foreach($invoice['InvoiceLines'] as $InvoiceLine) { | ||
$Product = ''; | ||
foreach(['Description', 'SalesPrice', 'VATPercentage', 'VATType', 'GLAccountCode', 'Remarks'] as $k) { | ||
if(!empty($InvoiceLine['Product'][$k])) $Product .= "<$k>{$InvoiceLine['Product'][$k]}</$k>"; | ||
} | ||
$InvoiceLines[] = "<InvoiceLine><ProductQuantity>$InvoiceLine[ProductQuantity]</ProductQuantity><Product>$Product</Product></InvoiceLine>"; | ||
} | ||
$SalesInvoice .= '<InvoiceLines>'. implode($InvoiceLines) .'</InvoiceLines>'; | ||
} | ||
// die($SalesInvoice); | ||
// XML doc, and send it | ||
$xmlvar = new SoapVar('<ns1:xmlDoc><SalesInvoices xmlns="urn:xmlns:http://www.theyukicompany.com:salesinvoices" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><SalesInvoice>'. | ||
$SalesInvoice. | ||
'</SalesInvoice></SalesInvoices></ns1:xmlDoc>', XSD_ANYXML); | ||
|
||
$return = $this->ProcessSalesInvoices(['sessionId' => $this->sid, 'administrationId' => $this->aid, 'xmlDoc' => $xmlvar]); | ||
|
||
// Check the result to see whether it was succesful | ||
$result_xml = simplexml_load_string(current($return->ProcessSalesInvoicesResult)); | ||
if(!$result_xml->TotalSucceeded->__toString()) { | ||
// None succeeded, so throw the error message | ||
throw new Exception($result_xml->Invoice->Message); | ||
} | ||
return true; // success | ||
} | ||
|
||
/** | ||
* Also called from constructor, if constructed with api key | ||
* @param string $api_key Yuki API key | ||
* @throws Exception If api key is not set | ||
*/ | ||
public function login($api_key) { | ||
if (!$api_key) { | ||
throw new Exception('Yuki API key not set. Please check your company\'s settings. You can find or create a Yuki API key (of type Administration) under Settings > Webservices in Yuki.'); | ||
} | ||
|
||
$this->sid = $this->sid($api_key); | ||
$this->aid = $this->aid(); | ||
} | ||
|
||
/** | ||
* Get the AdministrationID | ||
* @return string AdministrationID | ||
* @throws Exception | ||
*/ | ||
private function aid() { | ||
// could maybe be saved to DB the first time, but an API key might later get attached to a different Administration... | ||
$result = $this->Administrations(['sessionID' => $this->sid]); | ||
|
||
// Save and return the result | ||
try { | ||
$xml = simplexml_load_string(current($result->AdministrationsResult)); | ||
return $this->aid = $xml->Administration->attributes()['ID']; | ||
} | ||
catch(Exception $e) { | ||
throw new Exception('Yuki authentication failed. The API key works, but it does not seem to have access to any Administration.'); | ||
} | ||
} | ||
|
||
/** | ||
* Get a sessionID | ||
* @param string $api_key Yuki accessKey | ||
* @return string sessionID | ||
* @throws Exception | ||
*/ | ||
private function sid($api_key) { | ||
$result = $this->Authenticate(['accessKey' => $api_key]); | ||
|
||
// Save and return the result | ||
if ($result && !empty($result->AuthenticateResult)) { | ||
return $this->sid = $result->AuthenticateResult; | ||
} | ||
else throw new Exception('Authentication failed. Please check your company\'s Yuki accessKey.'); | ||
} | ||
|
||
|
||
/** | ||
* Generic call method (it just passes it on to the SoapClient) | ||
* @param string $method | ||
* @param array $params | ||
* @return object Response | ||
* @throws Exception | ||
*/ | ||
public function __call($method, $params) { | ||
try{ | ||
$result = $this->soap->__soapCall($method, $params); | ||
return $result; | ||
} | ||
catch(Exception $e) { | ||
// rethrow with a little more information | ||
throw new Exception("$method failed: [".$e->getCode().'] '.$e->getMessage(), $e->getCode()); | ||
} | ||
} | ||
|
||
/** | ||
* Yuki constructor (creates the SOAP client). | ||
* | ||
* @param null|string $apikey If provided, will immediately connect | ||
* @throws Exception If the SOAP client could not be instantiated, or the login failed | ||
*/ | ||
public function __construct($apikey = null) { | ||
$this->soap = new SoapClient(self::SALES_WSDL); | ||
if($apikey) $this->login($apikey); | ||
} | ||
} |