Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,67 @@ $packet = (new Packet())
$packetId = $client->sendPack($packet);
```

### Платёжное поручение

```php
use Ramsey\Uuid\Uuid;
use TTBooking\DirectBank\Dictionary\DocKind;
use TTBooking\DirectBank\Objects\{
BankPartyType, BankType, CustomerDetailsType, CustomerPartyType, DocumentType,
Packet, ParticipantType, PayDocRu, PayDocRuApp
};

$customer = (new CustomerPartyType())->setId('2806')->setInn('7705260699');
$bank = (new BankPartyType())->setBic('044525888');

$payDocRu = (new PayDocRu())
->setId((string) Uuid::uuid4())
->setCreationDate((new DateTimeImmutable())->format(DATE_ATOM))
->setSender($customer)
->setRecipient($bank)
->setData(
(new PayDocRuApp())
->setDocNo('14')
->setDocDate('2026-09-22')
->setSum(1234.56)
->setPayer(
(new CustomerDetailsType())
->setName('ООО "Ромашка"')->setINN('7705260699')->setKPP('770501001')
->setAccount('40702810813123123222')
->setBank((new BankType())->setBic('044525888'))
)
->setPayee(
(new CustomerDetailsType())
->setName('ООО "Лютик"')->setINN('7704596181')
->setAccount('40702810401200000035')
->setBank((new BankType())->setBic('044525999'))
)
->setTransitionKind('01')
->setPriority('5')
->setPurpose('Оплата по счёту № 15')
// ->setBudgetPaymentInfo(...) — реквизиты бюджетного платежа
);
// ->setDigest(new DigestType($digest, $algorithmVersion)) — если банк требует дайджест

$packet = (new Packet())
->setId((string) Uuid::uuid4())
->setCreationDate((new DateTimeImmutable())->format(DATE_ATOM))
->setSender((new ParticipantType())->setCustomer($customer))
->setRecipient((new ParticipantType())->setBank($bank))
->setDocument(
(new DocumentType())
->setId($payDocRu->getId())
->setDockind(DocKind::PAY_DOC_RU)
->setData(base64_encode((string) $payDocRu))
);

$client->sendPack($packet);
```

Платёжное требование собирается так же: `PayRequest` с данными `PayRequestApp` и видом `DocKind::PAY_REQUEST`.
Исходящие документы собираются в XML через `TTBooking\DirectBank\Mapper\XmlMapper`: поля базовых
типов идут раньше полей наследников, как требует XSD.

### Получение ответов банка

```php
Expand Down Expand Up @@ -231,8 +292,12 @@ foreach ($client->getPackList() ?? [] as $id) {
| Документ | Класс | Вид ЭД |
|---|---|---|
| Запрос о состоянии электронного документа | `StatusRequest` (`setExtID()` — ИД документа) | `DocKind::STATUS_REQUEST` |
| Запрос об отзыве электронного документа | `CancelationRequest` (`setExtID()`, `setReason()`) | `DocKind::CANCELATION_REQUEST` |
| Запрос-зонд | `Probe` | `DocKind::PROBE` |

Все исходящие документы принимают необязательный дайджест: `setDigest(new DigestType($data, $algorithmVersion))`.
Как его формировать, стандарт не описывает — это делает внешняя компонента банка.

Входящие: `StatusPacketNotice` (`01`), `StatusDocNotice` (`02`), `Settings` (`06`), `Statement` (`15`).

## Справочники
Expand Down
18 changes: 18 additions & 0 deletions src/Mapper/ConvertibleTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);

namespace TTBooking\DirectBank\Mapper;


/**
* Traits\ConvertibleTrait, в котором toXml() собирает XML через XmlMapper
*/
trait ConvertibleTrait
{
use \Traits\ConvertibleTrait;

public function toXml()
{
return (new XmlMapper())->unmap($this);
}
}
79 changes: 79 additions & 0 deletions src/Mapper/XmlMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);

namespace TTBooking\DirectBank\Mapper;


use Common\ModelReflection\Enum\AnnotationEnum;
use Common\ModelReflection\ModelClass;
use Common\ModelReflection\ModelProperty;
use Common\Util\Validation;
use Mapper\XmlModelMapper;

/**
* XmlModelMapper, который при сборке XML выводит свойства от базового класса к наследнику.
*
* Reflection отдаёт сначала свойства самого класса, потом унаследованные, а в типах-расширениях
* XSD (xsd:extension) элементы базового типа идут раньше добавленных.
*/
class XmlMapper extends XmlModelMapper
{
/**
* Повторяет XmlModelMapper::unmapModel(), меняется только порядок свойств
*/
protected function unmapModel(object $model): \stdClass
{
if (Validation::isEmpty($model)) {
throw new \InvalidArgumentException('Model must be an object with properties.');
}

$unmappedObject = new \stdClass();
foreach (self::orderedProperties(new ModelClass($model)) as $property) {
$propertyKey = $property->getName();
$propertyValue = $property->getPropertyValue();
if (Validation::isEmpty($propertyValue)) {
continue;
}

if ($property->getDocBlock()->hasAnnotation(AnnotationEnum::XML_ATTRIBUTE)) {
$attributeKey = self::ATTR_KEY;
$unmappedObject->{$attributeKey}[$propertyKey] = $propertyValue;
continue;
}

if ($property->getDocBlock()->hasAnnotation(AnnotationEnum::XML_NODE_VALUE)) {
$valueKey = self::VALUE_KEY;
$unmappedObject->$valueKey = $propertyValue;
continue;
}

$unmappedObject->$propertyKey = $this->unmapValueByType($property->getType(), $propertyValue);
}

return $unmappedObject;
}

/**
* Свойства от самого базового класса к наследнику, внутри класса — в порядке объявления
*
* @return ModelProperty[]
*/
protected static function orderedProperties(ModelClass $modelClass): array
{
$properties = $modelClass->getProperties();

$depth = function (ModelProperty $property): int {
$depth = 0;
for ($class = $property->getProperty()->getDeclaringClass(); $class = $class->getParentClass(); ) {
$depth++;
}

return $depth;
};

// usort стабилен с PHP 8.0: порядок объявления внутри класса сохраняется
usort($properties, fn(ModelProperty $a, ModelProperty $b) => $depth($a) <=> $depth($b));

return $properties;
}
}
50 changes: 49 additions & 1 deletion src/Objects/BudgetPaymentInfoType.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,52 @@ public function getPayType(): ?string
{
return $this->PayType;
}
}

public function setDrawerStatus(?string $drawerStatus): static
{
$this->DrawerStatus = $drawerStatus;
return $this;
}

public function setCBC(?string $cbc): static
{
$this->CBC = $cbc;
return $this;
}

public function setOKTMO(?string $oktmo): static
{
$this->OKTMO = $oktmo;
return $this;
}

public function setReason(?string $reason): static
{
$this->Reason = $reason;
return $this;
}

public function setTaxPeriod(?string $taxPeriod): static
{
$this->TaxPeriod = $taxPeriod;
return $this;
}

public function setDocNo(?string $docNo): static
{
$this->DocNo = $docNo;
return $this;
}

public function setDocDate(?string $docDate): static
{
$this->DocDate = $docDate;
return $this;
}

public function setPayType(?string $payType): static
{
$this->PayType = $payType;
return $this;
}
}
Loading
Loading