Compare commits

...

14 Commits

22 changed files with 961 additions and 90 deletions

View File

@ -1,23 +1,23 @@
{
"name": "leenooks/intuit",
"description": "Intuit API",
"keywords": ["laravel","leenooks","intuit"],
"authors": [
{
"name": "Deon George",
"email": "deon@leenooks.net"
}
],
"require": {
"jenssegers/model": "^1.5"
},
"require-dev": {
},
"autoload": {
"psr-4": {
"Intuit\\": "src"
}
},
"name": "leenooks/intuit",
"description": "Intuit API",
"keywords": ["laravel","leenooks","intuit"],
"authors": [
{
"name": "Deon George",
"email": "deon@dege.au"
}
],
"require": {
"jenssegers/model": "^1.5"
},
"require-dev": {
},
"autoload": {
"psr-4": {
"Intuit\\": "src"
}
},
"extra": {
"laravel": {
"providers": [
@ -25,5 +25,5 @@
]
}
},
"minimum-stability": "dev"
}
"minimum-stability": "dev"
}

View File

@ -7,9 +7,9 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Intuit\Exceptions\{ConnectionIssueException,InvalidQueryResultException};
use Intuit\Models\{ProviderToken};
use Intuit\Response\{Customer,Invoice,ListList};
use Intuit\Exceptions\{ConnectionIssueException,InvalidQueryResultException,NotFoundException};
use Intuit\Models\{ProviderToken,Payment};
use Intuit\Response\{Customer,Invoice,ListList,Taxcode};
final class API
{
@ -25,10 +25,14 @@ final class API
private const CURLOPT_HEADER = FALSE;
private ProviderToken $token;
private string $url;
public function __construct(ProviderToken $token,bool $tryprod=FALSE)
{
$this->url = (config('app.env') == 'local' && ! $tryprod) ? 'https://sandbox-quickbooks.api.intuit.com' : 'https://quickbooks.api.intuit.com';
$this->url = ((config('app.env') === 'local') && (! $tryprod))
? 'https://sandbox-quickbooks.api.intuit.com'
: 'https://quickbooks.api.intuit.com';
$this->token = $token;
Log::debug(sprintf('%s:Intuit API for id [%s]',static::LOGKEY,$token->realm_id));
@ -36,9 +40,17 @@ final class API
/* STATIC */
/**
* URL to get log into Quickbooks
*
* @param bool $tryprod
* @return string
*/
public static function url(bool $tryprod=FALSE): string
{
return (config('app.env') == 'local' && ! $tryprod) ? 'https://app.sandbox.qbo.intuit.com/app' : 'https://app.qbo.intuit.com/app';
return ((config('app.env') === 'local') && (! $tryprod))
? 'https://app.sandbox.qbo.intuit.com/app'
: 'https://app.qbo.intuit.com/app';
}
/**
@ -50,7 +62,7 @@ final class API
private function convertHeaders(array $header): array
{
return collect($header)
->map(function($value,$key) { return sprintf('%s:%s',$key,$value); })
->map(fn($value,$key)=>sprintf('%s:%s',$key,$value))
->values()
->toArray();
}
@ -63,7 +75,7 @@ final class API
* @return object|array
* @throws \Exception
*/
private function execute(string $path,array $parameters=[])
private function execute(string $path,array $parameters=[],string $query=NULL): mixed
{
$url = sprintf('%s/%s/company/%s/%s',$this->url,self::VERSION,$this->token->realm_id,$path);
@ -163,9 +175,41 @@ final class API
}
});
switch ($path) {
case 'query':
if (! $result->QueryResponse) {
Log::debug(sprintf('%s:Query response malformed',self::LOGKEY),['parameters'=>$parameters,'x'=>$x]);
throw new InvalidQueryResultException(sprintf('%s:Query response malformed',self::LOGKEY));
}
if (! object_get($result->QueryResponse,$query))
throw new NotFoundException(sprintf('%s:Query returned no results',self::LOGKEY));
return $result->QueryResponse;
}
return $result;
}
/**
* Find a customer by their email address
*
* @param string $id
* @param array $parameters
* @return Customer
* @throws InvalidQueryResultException
*/
public function getAccountQuery(string $id,array $parameters=[]): Customer
{
Log::debug(sprintf('%s:Get a specific account [%s]',static::LOGKEY,$id));
$table = 'Customer';
$parameters['query'] = sprintf("select * from %s where Id='%s'",$table,$id);
$result = object_get($this->execute('query',$parameters,$table),$table);
return new Customer(array_pop($result));
}
/**
* Get a list of our classes
*
@ -182,28 +226,6 @@ final class API
return new ListList($this->execute('query',$parameters),$key);
}
/**
* Find a customer by their email address
*
* @param string $id
* @param array $parameters
* @return Customer
* @throws InvalidQueryResultException
*/
public function getAccountQuery(string $id,array $parameters=[]): Customer
{
Log::debug(sprintf('%s:Get a specific account [%s]',static::LOGKEY,$id));
$parameters['query'] = sprintf("select * from Customer where PrimaryEmailAddr='%s'",$id);
$x = $this->execute('query',$parameters);
if ((! $x->QueryResponse) || (! $x->QueryResponse->Customer) || (count($x->QueryResponse->Customer) !== 1))
throw new InvalidQueryResultException(sprintf('%s:Query response malformed',self::LOGKEY));
return new Customer($x->QueryResponse);
}
/**
* Get a specific customer record
*
@ -247,14 +269,11 @@ final class API
{
Log::debug(sprintf('%s:Get a specific invoice [%s]',static::LOGKEY,$id));
$parameters['query'] = sprintf("select * from Invoice where DocNumber='%s'",$id);
$table = 'Invoice';
$parameters['query'] = sprintf("select * from %s where DocNumber='%s'",$table,$id);
$result = object_get($this->execute('query',$parameters,$table),$table);
$x = $this->execute('query',$parameters);
if ((! $x->QueryResponse) || (! $x->QueryResponse->Invoice) || (count($x->QueryResponse->Invoice) !== 1))
throw new InvalidQueryResultException(sprintf('%s:Query response malformed',self::LOGKEY));
return new Invoice($x->QueryResponse);
return new Invoice(array_pop($result));
}
/**
@ -286,11 +305,66 @@ final class API
$key = 'Item';
$parameters['query'] = 'select * from Item';
return new ListList($this->execute('query',$parameters,$key),$key);
}
/**
* Get a specific payment record
*
* @param int $id
* @param array $parameters
* @return Payment
* @throws \Exception
*/
public function getPayment(int $id,array $parameters=[]): Payment
{
Log::debug(sprintf('%s:Get a specific payment [%d]',static::LOGKEY,$id));
$x = $this->execute('payment/'.$id,$parameters);
if (! $x->Payment)
throw new InvalidQueryResultException(sprintf('%s:Query response malformed',self::LOGKEY));
return new Payment((array)$x->Payment);
}
/**
* Get a list of our payments
*
* @param array $parameters
* @return ListList
* @throws \Exception
*/
public function getPayments(array $parameters=[]): ListList
{
Log::debug(sprintf('%s:Get a list of payments',static::LOGKEY));
$key = 'Payment';
$parameters['query'] = 'select * from Payment';
return new ListList($this->execute('query',$parameters),$key);
}
/**
* Get a list of our invoices
* Find an taxcode by its ID
*
* @param string $id
* @param array $parameters
* @return Taxcode
* @throws InvalidQueryResultException
*/
public function getTaxCodeQuery(string $id,array $parameters=[]): Taxcode
{
Log::debug(sprintf('%s:Get a specific invoice [%s]',static::LOGKEY,$id));
$table = 'TaxCode';
$parameters['query'] = sprintf("select * from %s where Id='%s'",$table,$id);
$result = object_get($this->execute('query',$parameters,$table),$table);
return new Taxcode(array_pop($result));
}
/**
* Get a list of our tax codes
*
* @param array $parameters
* @return ListList
@ -300,9 +374,9 @@ final class API
{
Log::debug(sprintf('%s:Get a list of taxes',static::LOGKEY));
$key = 'TaxCode';
$parameters['query'] = 'select * from Taxcode';
$parameters['query'] = 'select * from TaxCode';
return new ListList($this->execute('query',$parameters),$key);
return new ListList($this->execute('query',$parameters,$key),$key);
}
/**
@ -354,7 +428,9 @@ final class API
{
Log::debug(sprintf('%s:Update invoice',static::LOGKEY),['params'=>$parameters]);
return new Invoice($this->execute('invoice',array_merge($parameters,['method'=>'POST'])));
$result = $this->execute('invoice',array_merge($parameters,['method'=>'POST']));
return new Invoice($result->Invoice);
}
/**
@ -368,6 +444,8 @@ final class API
{
Log::debug(sprintf('%s:Update customer',static::LOGKEY),['params'=>$parameters]);
return new Customer($this->execute('customer',array_merge($parameters,['method'=>'POST'])));
$result = $this->execute('customer',array_merge($parameters,['method'=>'POST']));
return new Customer($result->Customer);
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace Intuit\Commands;
use GuzzleHttp\Exception\ConnectException;
use Illuminate\Console\Command;
use Intuit\Exceptions\ConnectionIssueException;
use Intuit\Exceptions\NotFoundException;
use Intuit\Traits\ProviderTokenTrait;
class AccountGet extends Command
{
use ProviderTokenTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'intuit:account:get'
.' {id : Account ID}'
.' {user? : User Email}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Get an account from quickbooks';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$to = $this->providerToken($this->argument('user'));
try {
dump($to->API()->getAccountQuery($this->argument('id')));
} catch (ConnectException|ConnectionIssueException $e) {
$this->error($e->getMessage());
return self::FAILURE;
} catch (NotFoundException $e) {
$this->error('Nothing found');
return self::FAILURE;
}
return self::SUCCESS;
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace Intuit\Commands;
use GuzzleHttp\Exception\ConnectException;
use Illuminate\Console\Command;
use Intuit\Exceptions\ConnectionIssueException;
use Intuit\Exceptions\NotFoundException;
use Intuit\Traits\ProviderTokenTrait;
class InvoiceGet extends Command
{
use ProviderTokenTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'intuit:invoice:get'
.' {id : Invoice ID}'
.' {user? : User Email}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Get an invoice from the quickbooks';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$to = $this->providerToken($this->argument('user'));
try {
dump($to->API()->getInvoiceQuery($this->argument('id')));
} catch (ConnectException|ConnectionIssueException $e) {
$this->error($e->getMessage());
return self::FAILURE;
} catch (NotFoundException $e) {
$this->error('Nothing found');
return self::FAILURE;
}
return self::SUCCESS;
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace Intuit\Commands;
use GuzzleHttp\Exception\ConnectException;
use Illuminate\Console\Command;
use Intuit\Exceptions\ConnectionIssueException;
use Intuit\Exceptions\NotFoundException;
use Intuit\Traits\ProviderTokenTrait;
use App\Jobs\AccountingPaymentSync as Job;
class PaymentGet extends Command
{
use ProviderTokenTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'intuit:payment:get'
.' {id : Payment ID}'
.' {user? : User Email}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Get a payment from the accounting provider';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$to = $this->providerToken($this->argument('user'));
try {
dd($to->API()->getPayment($this->argument('id')));
} catch (ConnectException|ConnectionIssueException $e) {
$this->error($e->getMessage());
return self::FAILURE;
} catch (NotFoundException $e) {
$this->error('Nothing found');
return self::FAILURE;
}
if ($acc)
Job::dispatchSync($to,$acc);
return self::SUCCESS;
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace Intuit\Commands;
use GuzzleHttp\Exception\ConnectException;
use Illuminate\Console\Command;
use Intuit\Exceptions\ConnectionIssueException;
use Intuit\Exceptions\NotFoundException;
use Intuit\Traits\ProviderTokenTrait;
class TaxCodeGet extends Command
{
use ProviderTokenTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'intuit:taxcode:get'
.' {id : Taxcode ID}'
.' {user? : User Email}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Get the defined tax codes from the accounting provider';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$to = $this->providerToken($this->argument('user'));
try {
$result = $to->API()->getTaxCodeQuery($this->argument('id'));
dd($result,$result->getTaxRateRef());
} catch (ConnectException|ConnectionIssueException $e) {
$this->error($e->getMessage());
return self::FAILURE;
} catch (NotFoundException $e) {
$this->error('Nothing found');
return self::FAILURE;
}
return self::SUCCESS;
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace Intuit\Commands;
use Illuminate\Console\Command;
use Intuit\Traits\ProviderTokenTrait;
/**
* Refresh the Intuit tokens
*/
class TokenRefresh extends Command
{
use ProviderTokenTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'intuit:token:refresh'
.' {user? : User Email}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Refresh an access token';
/**
* Execute the console command.
*
* @return int
* @throws \Intuit\Exceptions\NotTokenException
*/
public function handle()
{
$to = $this->providerToken($this->argument('user'));
$old = $to->refresh_token;
$x = $to->refreshToken();
dump([
'access'=>$to->access_token,
'old'=>$old,
'refresh'=>$to->refresh_token,
'expires'=>(string)$to->refresh_token_expires_at,
'x'=>$x,
]);
return self::SUCCESS;
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace Intuit\Controllers;
use App\Events\ProviderPaymentCreated;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Log;
use Intuit\Response\Webhook as Payload;
class Webhook extends BaseController
{
use DispatchesJobs;
private const name = 'intuit';
public function webhook(Request $request)
{
$signature = $request->header('Intuit-Signature');
// Load the secret, you also can load it from env(YOUR_OWN_SLACK_SECRET)
$secret = config(sprintf('services.provider.%s.verifytoken',self::name));
Log::channel('webhook')->debug('Payload: '.$request->getContent());
Log::channel('webhook')->debug('Signature: '.$signature);
$payloadHash = hash_hmac('sha256',$request->getContent(),$secret);
$singatureHash = bin2hex(base64_decode($signature));
if ($payloadHash == $singatureHash) {
Log::channel('webhook')->debug('Signature OK');
} else {
Log::channel('webhook')->alert('Signature NOT ok.');
}
$payload = new Payload(json_decode($request->getContent(),TRUE));
foreach ($payload->types() as $type) {
switch ($type) {
case 'eventNotifications':
foreach ($payload->event($type) as $dataObject) {
Log::info('Event for realm: '.$dataObject['realmId'],['data'=>$dataObject]);
foreach ($dataObject['dataChangeEvent'] as $object => $data) {
switch ($object) {
case 'entities':
foreach ($data as $eventData) {
switch ($x=$eventData['name']) {
case 'Payment':
Log::alert(sprintf('We got a payment event [%s:%s]',$object,$x),['data'=>$eventData]);
event(new ProviderPaymentCreated('intuit',$eventData));
break;
default:
Log::alert(sprintf('We dont know how to handle [%s:%s] yet',$object,$x),['data'=>$eventData]);
}
}
break;
default:
Log::error(sprintf('Unknown object: %s in %s',$object,$type),['data'=>$data]);
}
}
}
break;
default:
Log::error('Unknown webhook payload type: '.$type,['data'=>$data]);
}
}
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace Intuit\Exceptions;
use Exception;
class NotFoundException extends Exception
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Intuit\Exceptions;
use Exception;
class NotTokenException extends Exception
{
}

183
src/Models/Payment.php Normal file
View File

@ -0,0 +1,183 @@
<?php
namespace Intuit\Models;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Jenssegers\Model\Model;
use Intuit\Traits\CompareAttributes;
/*
{#5731
+"QueryResponse": {#5730
+"Payment": array:1 [
0 => {#5729
+"CustomerRef": {#5728
+"value": "70"
+"name": "Customer Name"
}
+"DepositToAccountRef": {#5727
+"value": "135"
}
+"TotalAmt": 133.0
+"UnappliedAmt": 0
+"ProcessPayment": false
+"domain": "QBO"
+"sparse": false
+"Id": "208"
+"SyncToken": "0"
+"MetaData": {#5726
+"CreateTime": "2023-05-12T22:19:55-07:00"
+"LastUpdatedTime": "2023-05-12T22:19:55-07:00"
}
+"TxnDate": "2023-05-13"
+"CurrencyRef": {#5725
+"value": "AUD"
+"name": "Australian Dollar"
}
+"Line": array:2 [
0 => {#5724
+"Amount": 1.0
+"LinkedTxn": array:1 [
0 => {#5723
+"TxnId": "202"
+"TxnType": "Invoice"
}
]
+"LineEx": {#5722
+"any": array:3 [
0 => {#5721
+"name": "{http://schema.intuit.com/finance/v3}NameValue"
+"declaredType": "com.intuit.schema.finance.v3.NameValue"
+"scope": "javax.xml.bind.JAXBElement$GlobalScope"
+"value": {#5720
+"Name": "txnId"
+"Value": "202"
}
+"nil": false
+"globalScope": true
+"typeSubstituted": false
}
1 => {#5719
+"name": "{http://schema.intuit.com/finance/v3}NameValue"
+"declaredType": "com.intuit.schema.finance.v3.NameValue"
+"scope": "javax.xml.bind.JAXBElement$GlobalScope"
+"value": {#5718
+"Name": "txnOpenBalance"
+"Value": "132.00"
}
+"nil": false
+"globalScope": true
+"typeSubstituted": false
}
2 => {#5717
+"name": "{http://schema.intuit.com/finance/v3}NameValue"
+"declaredType": "com.intuit.schema.finance.v3.NameValue"
+"scope": "javax.xml.bind.JAXBElement$GlobalScope"
+"value": {#5716
+"Name": "txnReferenceNumber"
+"Value": "006825.4"
}
+"nil": false
+"globalScope": true
+"typeSubstituted": false
}
]
}
}
1 => {#5715
+"Amount": 132.0
+"LinkedTxn": array:1 [
0 => {#5714
+"TxnId": "206"
+"TxnType": "Invoice"
}
]
+"LineEx": {#5713
+"any": array:3 [
0 => {#5712
+"name": "{http://schema.intuit.com/finance/v3}NameValue"
+"declaredType": "com.intuit.schema.finance.v3.NameValue"
+"scope": "javax.xml.bind.JAXBElement$GlobalScope"
+"value": {#5711
+"Name": "txnId"
+"Value": "206"
}
+"nil": false
+"globalScope": true
+"typeSubstituted": false
}
1 => {#5710
+"name": "{http://schema.intuit.com/finance/v3}NameValue"
+"declaredType": "com.intuit.schema.finance.v3.NameValue"
+"scope": "javax.xml.bind.JAXBElement$GlobalScope"
+"value": {#5709
+"Name": "txnOpenBalance"
+"Value": "132.00"
}
+"nil": false
+"globalScope": true
+"typeSubstituted": false
}
2 => {#5708
+"name": "{http://schema.intuit.com/finance/v3}NameValue"
+"declaredType": "com.intuit.schema.finance.v3.NameValue"
+"scope": "javax.xml.bind.JAXBElement$GlobalScope"
+"value": {#5707
+"Name": "txnReferenceNumber"
+"Value": "006813"
}
+"nil": false
+"globalScope": true
+"typeSubstituted": false
}
]
}
}
]
}
]
+"startPosition": 1
+"maxResults": 1
}
+"time": "2023-05-12T22:20:40.937-07:00"
}
*/
final class Payment extends Model
{
use CompareAttributes;
public function __get($key) {
$keymap = [
'id' => 'Id',
'synctoken' => 'SyncToken',
'date_paid' => 'TxnDate',
'total_amt' => 'TotalAmt',
'unapplied' => 'UnappliedAmt',
];
switch ($key) {
case 'created_at':
return object_get($this->getAttribute('MetaData'),'CreateTime');
case 'updated_at':
return object_get($this->getAttribute('MetaData'),'LastUpdatedTime');
case 'account_ref':
return object_get($this->getAttribute('CustomerRef'),'value');
default:
return parent::__get(Arr::get($keymap,$key,$key));
}
}
public function lines(): Collection
{
$result = collect($this->getAttribute('Line'))->transform(function($item) {
return $item->LinkedTxn[0]->TxnType === 'Invoice' ? [ $item->LinkedTxn[0]->TxnId => $item->Amount] : [];
});
return collect(array_replace(...$result));
}
}

View File

@ -3,18 +3,21 @@
namespace Intuit\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
use Laravel\Socialite\Facades\Socialite;
class ProviderToken extends Model
{
protected $dates = [
'access_token_expires_at',
'refresh_token_expires_at',
private const LOGKEY = 'IPT';
protected $casts = [
'access_token_expires_at' => 'datetime:Y-m-d H:i:s',
'refresh_token_expires_at' => 'datetime:Y-m-d H:i:s',
];
/* ATTRIBUTES */
public function getAccessTokenAttribute($value): ?string
public function getAccessTokenAttribute(string $value): ?string
{
if (! $this->hasAccessTokenExpired())
return $value;
@ -29,11 +32,16 @@ class ProviderToken extends Model
public function hasAccessTokenExpired(): bool
{
return $this->access_token_expires_at->isPast();
return $this
->access_token_expires_at
->isPast();
}
public function refreshToken(): bool
{
return Socialite::with($this->provider->name)->refreshtoken($this);
Log::debug(sprintf('%s:= Refreshing token for [%s]',self::LOGKEY,$this->provider->name));
return Socialite::with($this->provider->name)
->refreshToken($this);
}
}

View File

@ -4,6 +4,7 @@ namespace Intuit\Providers;
use Illuminate\Routing\Router;
use Illuminate\Support\ServiceProvider;
use Intuit\Commands\{AccountGet,InvoiceGet,PaymentGet,TaxCodeGet,TokenRefresh};
/**
* Class IntuitServiceProvider.
@ -20,6 +21,14 @@ class IntuitServiceProvider extends ServiceProvider
public function boot(Router $router)
{
$this->mergeConfigFrom($this->_path.'/config/intuit.php','intuit');
$this->commands([
AccountGet::class,
InvoiceGet::class,
PaymentGet::class,
TaxCodeGet::class,
TokenRefresh::class,
]);
}
/**
@ -29,8 +38,7 @@ class IntuitServiceProvider extends ServiceProvider
*/
public function register()
{
if (! $this->_path) {
if (! $this->_path)
$this->_path = realpath(__DIR__.'/../../src');
}
}
}

View File

@ -6,6 +6,7 @@ use Carbon\Carbon;
use GuzzleHttp\RequestOptions;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Laravel\Passport\Exceptions\InvalidAuthTokenException;
use Laravel\Socialite\Two\InvalidStateException;
use Laravel\Socialite\Two\ProviderInterface;
@ -16,6 +17,8 @@ use App\Models\{ProviderOauth,ProviderToken};
class IntuitProvider extends AbstractProvider implements ProviderInterface
{
private const LOGKEY = 'SIP';
private const hosts = [
'authorise' => 'https://appcenter.intuit.com/connect/oauth2',
'tokenendpoint' => 'https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer',
@ -104,12 +107,16 @@ class IntuitProvider extends AbstractProvider implements ProviderInterface
];
}
public function refreshtoken(ProviderToken $to): bool
public function refreshToken($to)
{
$response = $this->getHttpClient()->post($this->getTokenUrl(), [
RequestOptions::HEADERS => $this->getAuthorisationHeader($to->provider),
RequestOptions::FORM_PARAMS => $this->getRefreshTokenFields($to->refresh_token),
]);
Log::debug(sprintf('%s:= Refreshing token for [%d]',self::LOGKEY,$to->id));
$response = $this
->getHttpClient()
->post($this->getTokenUrl(),[
RequestOptions::HEADERS => $this->getAuthorisationHeader($to->provider),
RequestOptions::FORM_PARAMS => $this->getRefreshTokenFields($to->refresh_token),
]);
switch ($response->getStatusCode()) {
case '200':
@ -125,10 +132,11 @@ class IntuitProvider extends AbstractProvider implements ProviderInterface
if (($x=Arr::get($body,'refresh_token')) !== $to->refresh_token) {
$to->refresh_token = $x;
$to->refresh_token_expires_at = Carbon::now()->addSeconds(Arr::get($body,'x_refresh_token_expires_in'));
}
$to->save();
return TRUE;
Log::debug(sprintf('%s:= Refresh token updated.',self::LOGKEY));
}
return $to->save();
default:
throw new InvalidAuthTokenException(sprintf('Invalid response [%d] refreshing token for [%s] (%s)',$response->getStatusCode(),$to->user->email,$response->getBody()));

View File

@ -55,8 +55,8 @@ abstract class Base implements \JsonSerializable
* @param mixed $value
* @return mixed
*/
public function search(string $key, mixed $value): mixed
public function search(string $key,mixed $value): mixed
{
return $this->_data->search(function($item) use ($key,$value) { return $item->{$key} == $value; });
return $this->_data->search(fn($item)=>$item->{$key}===$value);
}
}

View File

@ -18,6 +18,6 @@ class Customer extends Base
if (object_get($response,'time'))
unset($response->time);
$this->_model = new CustomerModel((array)$response->Customer);
$this->_model = new CustomerModel((array)$response);
}
}

View File

@ -18,6 +18,6 @@ class Invoice extends Base
if (object_get($response,'time'))
unset($response->time);
$this->_model = new InvoiceModel((array)$response->Invoice);
$this->_model = new InvoiceModel((array)$response);
}
}

View File

@ -7,7 +7,7 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
use Intuit\Models\{Category,Customer,Invoice,Item,Taxcode};
use Intuit\Models\{Category,Customer,Invoice,Item,Payment,Taxcode};
/**
* This is a Generic Intuit Response to API calls that produces a list of objects
@ -27,6 +27,7 @@ class ListList extends Base implements \Countable, \ArrayAccess, \Iterator
'Customer' => Customer::class,
'Invoice' => Invoice::class,
'Item' => Item::class,
'Payment' => Payment::class,
'TaxCode' => Taxcode::class,
];
@ -143,9 +144,7 @@ class ListList extends Base implements \Countable, \ArrayAccess, \Iterator
*/
private function data(object $response,string $type): Collection
{
if (! ($x=object_get($response->QueryResponse,$type)))
return collect();
$x = $response->{$type};
switch (Arr::get(self::TYPES,$type)) {
case Category::class:
$data = collect(Category::hydrate($x));
@ -163,6 +162,10 @@ class ListList extends Base implements \Countable, \ArrayAccess, \Iterator
$data = collect(Item::hydrate($x));
break;
case Payment::class:
$data = collect(Payment::hydrate($x));
break;
case Taxcode::class:
$data = collect(Taxcode::hydrate($x));
break;
@ -172,8 +175,8 @@ class ListList extends Base implements \Countable, \ArrayAccess, \Iterator
throw new \Exception(sprintf('%s:Unknown object type: %s',self::LOGKEY,$type));
}
$this->startPosition = $response->QueryResponse->startPosition;
$this->maxResults = $response->QueryResponse->maxResults;
$this->startPosition = $response->startPosition;
$this->maxResults = $response->maxResults;
return $data;
}

23
src/Response/Payment.php Normal file
View File

@ -0,0 +1,23 @@
<?php
namespace Intuit\Response;
use Intuit\Models\Payment as PaymentModel;
/**
* This is an Invoice Intuit Response to API calls
*/
class Payment extends Base
{
protected const LOGKEY = 'RIP';
public function __construct(object $response)
{
parent::__construct($response);
if (object_get($response,'time'))
unset($response->time);
$this->_model = new PaymentModel((array)$response->Payment);
}
}

View File

@ -2,7 +2,8 @@
namespace Intuit\Response;
use Intuit\Models\Tax as TaxModel;
use Illuminate\Support\Collection;
use Intuit\Models\Taxcode as TaxcodeModel;
/**
* This is an Invoice Intuit Response to API calls
@ -18,6 +19,12 @@ class Taxcode extends Base
if (object_get($response,'time'))
unset($response->time);
$this->_model = new TaxModel((array)$response->Tax);
$this->_model = new TaxcodeModel((array)$response);
}
public function getTaxRateRef(): Collection
{
return collect($this->SalesTaxRateList->TaxRateDetail)
->pluck('TaxRateRef.value');
}
}

143
src/Response/Webhook.php Normal file
View File

@ -0,0 +1,143 @@
<?php
namespace Intuit\Response;
use Illuminate\Support\Collection;
/**
* Stores incoming or outgoing message data for a Slack API call.
*/
class Webhook implements \ArrayAccess, \JsonSerializable
{
/**
* @var Collection The response data.
*/
protected Collection $data;
/**
* Creates a new payload object.
*
* @param array $data The payload data.
*/
public function __construct(array $data,bool $key=FALSE)
{
$this->data = collect($key ? ['payload'=>$data ] : $data);
}
/**
* @return string
*/
public function __toString()
{
return $this->toJson();
}
/**
* Creates a response object from a JSON message.
*
* @param string $json A JSON string.
* @return Webhook The parsed response.
*/
public static function fromJson($json): self
{
$data = json_decode((string)$json,true);
if (json_last_error() !== JSON_ERROR_NONE || (! is_array($data))) {
throw new \UnexpectedValueException('Invalid JSON message:'.serialize($data));
}
return new static($data);
}
/* INTERFACES */
/**
* @param mixed $offset
* @param mixed $value
*/
public function offsetSet($offset, $value)
{
if (is_null($offset)) {
$this->data[] = $value;
} else {
$this->data[$offset] = $value;
}
}
/**
* @param mixed $offset
* @return bool
*/
public function offsetExists($offset)
{
return isset($this->data[$offset]);
}
/**
* @param mixed $offset
*/
public function offsetUnset($offset)
{
unset($this->data[$offset]);
}
/**
* @param mixed $offset
* @return null
*/
public function offsetGet($offset)
{
return $this->data[$offset] ?? NULL;
}
/**
* @return array
*/
public function jsonSerialize()
{
return $this->data;
}
/* METHODS */
/**
* Return the events of a specific type
* @param string $type
* @return Collection
*/
public function event(string $type): Collection
{
return collect($this->data->get($type));
}
/**
* Gets the payload data.
*
* @return array The payload data.
*/
public function getData()
{
return $this->data;
}
/**
* Serializes the payload to a JSON message.
*
* @return string A JSON message.
*/
public function toJson(): string
{
return json_encode($this->data,true);
}
/**
* Return the event types
*
* @return Collection
*/
public function types(): Collection
{
return $this->data->keys();
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Intuit\Traits;
use App\Models\{ProviderOauth,ProviderToken,User};
use Intuit\Exceptions\NotTokenException;
/**
* Return a provider token for API calls
*/
trait ProviderTokenTrait
{
private const provider = 'intuit';
/**
* Return a provider token to use for an API call
*
* @return ProviderToken
*/
protected function providerToken(string $user=NULL): ProviderToken
{
$uo = User::where('email',$user ?: config('osb.admin'))
->sole();
$so = ProviderOauth::where('name',self::provider)
->sole();
if (! ($to=$so->token($uo)))
throw new NotTokenException(sprintf('Unknown Tokens for [%s]',$uo->email));
return $to;
}
}