Skip to content
Open
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
6 changes: 6 additions & 0 deletions VERSION.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
4.6.0

## Added
- Add draft support methods for retentions: `updateDraft()`, `copyToDraft()`, and `stampDraft()`.
- Allow `Retentions::cancel()` to be called without query parameters for deleting draft retentions.

4.5.0

## Added
Expand Down
Binary file added retention.pdf
Binary file not shown.
83 changes: 81 additions & 2 deletions src/Resources/Retentions.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,12 @@ public function create($params): mixed
* Cancels a Retention
*
* @param string $id Retention ID.
* @param array $query URL query parameters.
* @param array|null $query URL query parameters.
* @return mixed JSON-decoded response.
*
* @throws FacturapiException
*/
public function cancel($id, $query): mixed
public function cancel($id, $query = null): mixed
{
try {
return json_decode($this->executeDeleteRequest($this->getRequestUrl($id, $query), null));
Expand All @@ -80,6 +80,85 @@ public function cancel($id, $query): mixed
}
}

/**
* Updates a Retention with "draft" status
*
* @param string $id Retention ID.
* @param array $body Draft payload.
* @return mixed JSON-decoded response.
*
* @throws FacturapiException
*/
public function updateDraft($id, $body): mixed
{
try {
return json_decode($this->executeJsonPutRequest($this->getRequestUrl($id), $body));
} catch (FacturapiException $e) {
throw $e;
}
}

/**
* @deprecated Use updateDraft() instead. Will be removed in v5.
*/
public function update_draft($id, $body): mixed
{
trigger_error('Retentions::update_draft() is deprecated and will be removed in v5. Use updateDraft() instead.', E_USER_DEPRECATED);
return $this->updateDraft($id, $body);
}

/**
* Creates a new draft Retention copying the information from the specified Retention
*
* @param string $id Retention ID.
* @return mixed JSON-decoded response.
*
* @throws FacturapiException
*/
public function copyToDraft($id): mixed
{
try {
return json_decode($this->executeJsonPostRequest($this->getRequestUrl($id . "/copy"), null));
} catch (FacturapiException $e) {
throw $e;
}
}

/**
* @deprecated Use copyToDraft() instead. Will be removed in v5.
*/
public function copy_to_draft($id): mixed
{
trigger_error('Retentions::copy_to_draft() is deprecated and will be removed in v5. Use copyToDraft() instead.', E_USER_DEPRECATED);
return $this->copyToDraft($id);
}

/**
* Stamps a draft Retention
*
* @param string $id Retention ID.
* @return mixed JSON-decoded response.
*
* @throws FacturapiException
*/
public function stampDraft($id): mixed
{
try {
return json_decode($this->executeJsonPostRequest($this->getRequestUrl($id . "/stamp"), null));
} catch (FacturapiException $e) {
throw $e;
}
}

/**
* @deprecated Use stampDraft() instead. Will be removed in v5.
*/
public function stamp_draft($id): mixed
{
trigger_error('Retentions::stamp_draft() is deprecated and will be removed in v5. Use stampDraft() instead.', E_USER_DEPRECATED);
return $this->stampDraft($id);
}

/**
* Sends the retention to the customer's email
*
Expand Down
102 changes: 102 additions & 0 deletions tests/Resources/RetentionsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);

namespace Facturapi\Tests\Resources;

use Facturapi\Resources\Retentions;
use Facturapi\Tests\Support\FakeHttpClient;
use GuzzleHttp\Psr7\Response;
use PHPUnit\Framework\TestCase;

final class RetentionsTest extends TestCase
{
public function testCreateCanSendDraftRetentionPayload(): void
{
$httpClient = new FakeHttpClient(new Response(200, [], '{"id":"ret_draft","status":"draft"}'));
$retentions = new Retentions('sk_test_abc123', ['httpClient' => $httpClient]);

$retentions->create([
'status' => 'draft',
'customer' => null,
]);

$request = $httpClient->requests()[0];
self::assertSame('POST', $request->getMethod());
self::assertSame('https://www.facturapi.io/v2/retentions', (string) $request->getUri());
self::assertJsonStringEqualsJsonString(
'{"status":"draft","customer":null}',
(string) $request->getBody()
);
}

public function testUpdateDraftUsesPutOnRetentionId(): void
{
$httpClient = new FakeHttpClient(new Response(200, [], '{"id":"ret_123"}'));
$retentions = new Retentions('sk_test_abc123', ['httpClient' => $httpClient]);

$retentions->updateDraft('ret_123', [
'folio_int' => 'R-2026-001',
]);

$request = $httpClient->requests()[0];
self::assertSame('PUT', $request->getMethod());
self::assertSame('https://www.facturapi.io/v2/retentions/ret_123', (string) $request->getUri());
self::assertJsonStringEqualsJsonString(
'{"folio_int":"R-2026-001"}',
(string) $request->getBody()
);
}

public function testCopyToDraftUsesCopyEndpoint(): void
{
$httpClient = new FakeHttpClient(new Response(200, [], '{"id":"ret_draft"}'));
$retentions = new Retentions('sk_test_abc123', ['httpClient' => $httpClient]);

$retentions->copyToDraft('ret_123');

$request = $httpClient->requests()[0];
self::assertSame('POST', $request->getMethod());
self::assertSame('https://www.facturapi.io/v2/retentions/ret_123/copy', (string) $request->getUri());
self::assertSame('', (string) $request->getBody());
}

public function testStampDraftUsesStampEndpoint(): void
{
$httpClient = new FakeHttpClient(new Response(200, [], '{"id":"ret_123","status":"valid"}'));
$retentions = new Retentions('sk_test_abc123', ['httpClient' => $httpClient]);

$retentions->stampDraft('ret_123');

$request = $httpClient->requests()[0];
self::assertSame('POST', $request->getMethod());
self::assertSame('https://www.facturapi.io/v2/retentions/ret_123/stamp', (string) $request->getUri());
self::assertSame('', (string) $request->getBody());
}

public function testCancelCanDeleteDraftWithoutQueryParameters(): void
{
$httpClient = new FakeHttpClient(new Response(200, [], '{"id":"ret_123","deleted":true}'));
$retentions = new Retentions('sk_test_abc123', ['httpClient' => $httpClient]);

$retentions->cancel('ret_123');

$request = $httpClient->requests()[0];
self::assertSame('DELETE', $request->getMethod());
self::assertSame('https://www.facturapi.io/v2/retentions/ret_123', (string) $request->getUri());
}

public function testAllCanFilterByDraftStatus(): void
{
$httpClient = new FakeHttpClient(new Response(200, [], '{"data":[]}'));
$retentions = new Retentions('sk_test_abc123', ['httpClient' => $httpClient]);

$retentions->all([
'status' => 'draft',
]);

$request = $httpClient->requests()[0];
self::assertSame('GET', $request->getMethod());
self::assertSame('https://www.facturapi.io/v2/retentions?status=draft', (string) $request->getUri());
}
}
Loading