mervit/idoklad-v3

PHP 类,用于简化对 iDoklad api v3 的请求。

v1.1.0 2024-05-20 12:11 UTC

This package is auto-updated.

Last update: 2024-09-20 13:05:51 UTC


README

Open Source Love

iDoklad v3

用于发送对 iDoklad api v3 请求的 PHP 类。

iDoklad api v2 文档

iDoklad api v3 文档

感谢 @malcanek 创建了 v2 版本,我已经将其升级到 v3 并将其提供给其他感兴趣的人。

与 v2 版本相比的代码变更

  • 主要变化在于响应,将 TotalItems 和 TotalPages 移动到 data 参数下。因此,列表项不能通过 getData 方法读取,而需要使用新创建的 getItems 方法。因此,在从 v2 版本升级到 v3 版本时,需要修改所有返回列表结果的调用。
  • 过滤器已重做,现在允许对条件和组合使用逻辑运算符(或和与)进行分组和嵌套。
  • GET 请求现在支持 select 参数,用于限制结果中的数据。
  • GET 请求现在还支持 include 参数,允许在响应中包含关联实体的数据。

将库添加到项目中

我们将通过包含 src/iDoklad.php 文件或将库添加到 composer 中来将库添加到项目中。之后,我们使用 use 关键字来引用库。

composer require mervit/idoklad-v3

我们输入我们的 client ID、client secret,如果需要使用 OAuth2 认证,还要输入 redirect URI。最后,我们调用 iDoklad 对象,它负责所有通信。

include_once 'src/iDoklad.php';
            
use mervit\iDoklad\iDoklad;
use mervit\iDoklad\auth\iDokladCredentials;
use mervit\iDoklad\iDokladException;

$clientId = 'Your client ID';
$clientSecret = 'Your client secret';
$redirectUri = 'Your redirect URI for OAuth2';

$iDoklad = new iDoklad($clientId, $clientSecret, $redirectUri);

OAuth2 - 授权代码流

OAuth 认证分几个步骤。我们使用从开发门户获取的 client ID 和 client secret 作为 client ID 和 client secret。

首先,我们提供用户输入其登录凭据的 URL。我们可以使用以下方法获取该 URL

echo '<a href="'.$iDoklad->getAuthenticationUrl().'">Odkaz</a>';

用户输入登录凭据后,用户将被重定向到指定的 redirect URI,并附带代码。我们可以使用以下方法处理代码

$iDoklad->requestCredentials($_GET['code']);

现在,实例对象中已经有了凭证,我们可以向 iDoklad api 发送查询。我们可以通过两种方式获取凭证。从对象中直接获取凭证

$credentials = $iDoklad->getCredentials();
echo $credentials->toJson();

使用 credentials callback 处理凭证

Callback 的工作方式是库在凭证更改时调用 callback 函数。这很有用,因为当 token 过期时,它会自动刷新 token。

$iDoklad->setCredentialsCallback(function($credentials){
    file_put_contents('credentials.json', $credentials->toJson());
});

将凭证上传到 iDoklad 对象

使用现有的凭证创建对象

$iDoklad = new iDoklad($clientId, $clientSecret, $redirectUri, $credentials);

将凭证添加到现有的对象中

$iDoklad->setCredentials($credentials);

一旦对象包含凭证,就可以向 iDoklad api 发送查询。

OAuth2 - 客户端凭证流

这种方法更简单。我们将根据从用户账户设置中获取的 client ID 和 client secret 获取凭证。对象创建后,我们只需调用

$iDoklad->authCCF();

与 OAuth2 - 授权代码流一样,这里也使用了 credentials callback。

发送对 iDoklad api 的请求

用于发送 api 请求的是 iDokladRequest 对象。它可以用最简单的方式创建,只需一个参数,它根据文档指定操作,然后直接发送到 api。

$request = new iDokladRequest('IssuedInvoices');
$response = $iDoklad->sendRequest($request);

从 api 获取数据

如果请求成功,则作为 iDokladResponse 对象返回数据。首先,我们检查请求是否成功(返回值应该是 200)

$response->getCode();

然后我们可以从数组中获取实际数据

$response->getData();

错误捕获

该类抛出 iDokladException 类型的异常。

创建新的发票

$request->addMethodType('POST');
$data = array(
    'PurchaserId' => 3739927,
    'IssuedInvoiceItems' => [array(
        'Name' => 'Testovaci polozka',
        'UnitPrice' => 100,
        'Amount' => 1
    )]
);
$request->addPostParameters($data);

或者我们可以通过 fce 设置 method type,这将看起来像这样

$data = array(
    'PurchaserId' => 3739927,
    'IssuedInvoiceItems' => [array(
        'Name' => 'Testovaci polozka',
        'UnitPrice' => 100,
        'Amount' => 1
    )]
);
$request->post()->addPostParameters($data);

使用过滤和排序

为了使用过滤器,我们将使用 iDokladFilter 类。参数可以在创建类时立即设置,第一个参数是要过滤的字段名称,第二个参数是操作符,最后一个参数是值。

$filter = new iDokladFilter('DocumentNumber', '==', '20170013');
$request->addFilter($filter);

我们可以通过 setFilterType 方法定义各个过滤器之间的关系。

$request->setFilterType('and');

我们可以添加多个过滤器,并使用 filterGroup 类将它们分组。

$filterGroup = new \mervit\iDoklad\request\iDokladFilterGroup('or');
$filter2 = new \mervit\iDoklad\request\iDokladFilter('Id', '!eq', '123');
$filterGroup->addFilter($filter2);
$filter3 = new \mervit\iDoklad\request\iDokladFilter('Id', 'eq', '456');
$filterGroup->addFilter($filter3);
$request->addFilter($filterGroup);

为了使用排序,我们将使用 iDokladSort 类。同样,我们可以立即添加参数,其中第一个参数是字段名称,第二个参数是可选的,可以指定是升序(asc)还是降序(desc)。

$sort = new iDokladSort('DocumentNumber', 'desc');
$request->addSort($sort);

分页和返回的项目数量

$request->setPage(2);
$request->setPageSize(5);

限制结果

通过 addSelect 方法,我们可以限制 API 返回的数据,从而显著减少请求处理时间。

$request->addSelect('Id');
$request->addSelect('CurrencyId');

嵌套数据用点分隔

$request->addSelect('DeliveryAddress.Name');

我们还可以添加多个用逗号分隔的变量

$request->addSelect('Items.Id,Items.Name');

加载附加实体

通过 addInclude 方法,我们可以扩展返回的数据,包括附加实体的详细信息。有了这个功能,我们就不必单独调用附加实体的详细信息。

$request->addInclude('Currency');

嵌套数据用点分隔

$request->addInclude('Items.PriceListItem.Currency');

我们还可以添加多个用逗号分隔的变量

$request->addInclude('Currency,Items.PriceListItem.Currency');

返回代码高于或等于 400 时抛出异常

如果我们要在 HTTP 返回代码高于或等于 400 时抛出异常,我们只需要调用该函数即可。

$iDoklad->httpExceptionsOn()

上传附件

如果我们要上传附件,我们只需要在请求对象上使用 addFile 方法。

$request = new \mervit\iDoklad\request\iDokladRequest('Attachments/{documentId}/{documentType}');
$request->addFile(new CURLFile(path_to_your_file));
$response = $iDoklad->sendRequest($request);

其他修改

如果我们需要使用 POST、PUT、PATCH、DELETE 方法,我们将使用 iDokladRequest 对象上的 addMethodType 方法。

示例

示例使用可以在 acf.php 和 ccf.php 文件中看到。acf.php 包含了 authorization code flow 的使用示例,ccf 包含了 client credentials flow 的示例。只需要添加自己的 client ID、client secret 和 redirect URI。

创建发票的示例

// Generate company name
        $companyName = $order->getFirstname() . ' ' . $order->getLastname();
        if($order->getCompanyName()){
            $companyName = $order->getCompanyName();
        }

        // Try to find existing company in address book
        $filter = new iDokladFilter('CompanyName', '==', $companyName);
        $contactRequest = new iDokladRequest('Contacts');
        $contactRequest->addFilter($filter);
        $contactResponse = $this->sendRequest($contactRequest);
        if($contactResponse->getTotalItems() > 0){
            $contactId = $contactResponse->getItems()[0]["Id"];
        }

        // Create or update company in address book
        $contactRequest = new iDokladRequest('Contacts');
        $contactRequestPostParameters = [];
        $contactRequestPostParameters['CountryId'] = 1;
        $contactRequestPostParameters['Email'] = $order->getEmail();
        $contactRequestPostParameters['Mobile'] = $order->getPhone();
        $contactRequestPostParameters['Firstname'] = $order->getFirstname();
        $contactRequestPostParameters['Surname'] = $order->getLastname();
        $contactRequestPostParameters['CompanyName'] = $companyName;
        if($order->getCin()) {
            $contactRequestPostParameters['IdentificationNumber'] = $order->getCin();
        }
        if($order->getVat()){
            $contactRequestPostParameters['VatIdentificationNumber'] = $order->getVat();
        }
        if($order->getAddress()){
            $contactRequestPostParameters['Street'] = $order->getAddress();
        }
        if(isset($contactId)){
            $contactRequestPostParameters['Id'] = $contactId;
            $contactRequest->addMethodType('PATCH');
        } else {
            $contactRequest->addMethodType('POST');
        }
        $contactRequest->addPostParameters($contactRequestPostParameters);

        $contactResponse = $this->sendRequest($contactRequest);
        $contact = $contactResponse->getData();

        // Get default numeric sequence
        $numericSequenceRequest = new iDokladRequest('NumericSequences');
        $numericSequenceRequest->addFilter(new iDokladFilter('IsDefault', '==', 'true'));
        $numericSequenceRequest->addFilter(new iDokladFilter('DocumentType', '==', '0')); // 0 = IssuedInvoices
        $numericSequenceResponse = $this->sendRequest($numericSequenceRequest);
        $numericSequences = $numericSequenceResponse->getItems();
        $defaultNumericSequenceId = $numericSequences[0]['Id'];
        $lastDocumentSerialNumber = $numericSequences[0]['LastNumber'];

        // Get default payment option
        $paymentOptionRequest = new iDokladRequest('PaymentOptions');
        $paymentOptionResponse = $this->sendRequest($paymentOptionRequest);
        $paymentOptions = $paymentOptionResponse->getItems();
        $paymentOptionId = null;
        foreach($paymentOptions as $po){
            if($po['IsDefault'] == 'true'){
                $paymentOptionId = $po['Id'];
                break;
            }
        }

        // Create Issued Invoice
        $dateOfIssue = new \DateTime();
        $dateOfMaturity = clone $dateOfIssue;
        $dateOfMaturity->modify('+14 days');
        $invoicePostParameters = [];
        $invoicePostParameters['PartnerId'] = $contact['Id'];
        $invoicePostParameters['CurrencyId'] = 1;
        $invoicePostParameters['Description'] = 'Nákup v eshopu';
        $invoicePostParameters['DateOfIssue'] = $dateOfIssue->format('Y-m-d');
        $invoicePostParameters['DateOfMaturity'] = $dateOfMaturity->format('Y-m-d');
        $invoicePostParameters['DateOfTaxing'] = $dateOfIssue->format('Y-m-d');
        $invoicePostParameters['DocumentSerialNumber'] = (string)((int) $lastDocumentSerialNumber + 1);
        $invoicePostParameters['IsEet'] = false;
        $invoicePostParameters['IsIncomeTax'] = true;
        $invoicePostParameters['NumericSequenceId'] = $defaultNumericSequenceId;
        $invoicePostParameters['PaymentOptionId'] = $paymentOptionId;
        $invoicePostParameters['Items'] = [];

        foreach ($order->getItems() as $item) {
            $invoiceItem = [];
            $invoiceItem['Amount'] = $item->getQuantity();
            $invoiceItem['PriceType'] = 0; // Cena s daní
            $invoiceItem['VatRateType'] = 1; // Základní sazba DPH
            $invoiceItem['UnitPrice'] = $item->getPrice();
            $invoiceItem['Name'] = $item->getName();
            $invoiceItem['DiscountPercentage'] = 0;
            $invoiceItem['IsTaxMovement'] = false;
            $invoicePostParameters['Items'][] = $invoiceItem;
        }

        $invoiceRequest = new iDokladRequest('IssuedInvoices');
        $invoiceRequest->addMethodType('POST');
        $invoiceRequest->addPostParameters($invoicePostParameters);
        return $this->sendRequest($invoiceRequest);