diff --git a/config/telegraph.php b/config/telegraph.php index ca6f73bd..7da4536e 100644 --- a/config/telegraph.php +++ b/config/telegraph.php @@ -180,4 +180,8 @@ 'max_size_mb' => 50, ], ], + + 'payments' => [ + 'provider_token' => env('TELEGRAPH_PAYMENT_PROVIDER_TOKEN', ''), + ], ]; diff --git a/docs/12.features/7.attachments.md b/docs/12.features/7.attachments.md index 31bb2573..a130d7ce 100644 --- a/docs/12.features/7.attachments.md +++ b/docs/12.features/7.attachments.md @@ -31,6 +31,57 @@ and validation checks limits can be customized in [telegraph.php config](install ## Attachment types +### Invoice + +Invoices can be sent through Telegraph `->invoice()` method: + +```php +Telegraph::invoice('Invoice title') + ->description('Invoice Description') + ->currency('EUR') //Pass “XTR” for payments in Telegram Stars + ->addItem('First Item Label', 10) //Must contain exactly one item for payments in Telegram Stars + ->addItem('Second Item Label', 10) + ->maxTip(70) //Not supported for payments in Telegram Stars + ->suggestedTips([30,20]) + ->startParameter(10) + ->image('Invoice Image Link', 20 , 20) + ->needName() //Ignored for payments in Telegram Stars + ->needPhoneNumber() //Ignored for payments in Telegram Stars + ->needEmail() //Ignored for payments in Telegram Stars + ->needShippingAddress() //Ignored for payments in Telegram Stars + ->flexible() //Ignored for payments in Telegram Stars + ->send(); +``` + +A link for the invoice can be created through the `->link()` method + +```php +Telegraph::invoice('Invoice title') + ->description('Invoice Description') + ->currency('EUR') + ->addItem('Item Label', 10) + ->link() + ->send(); +``` + +Payments require a provider token, pass an empty string (default) for payments in Telegram Stars. +To change it, you should specify your provider token in the `.env` file. + +```php +TELEGRAPH_PAYMENT_PROVIDER_TOKEN = "provider token" +``` + +Alternatively you can set it through the `->providerData()` method + +```php +Telegraph::invoice('Invoice title') + ->description('Invoice Description') + ->currency('EUR') + ->addItem('Item Label', 10) + ->providerData('provider token') + ->send(); +``` + ### Photos Photos can be sent through Telegraph `->photo()` method: diff --git a/docs/12.features/9.dto.md b/docs/12.features/9.dto.md index bb67bb6a..1477fb9c 100644 --- a/docs/12.features/9.dto.md +++ b/docs/12.features/9.dto.md @@ -42,6 +42,7 @@ contains incoming data (a message or a callback query) - `->contact()` (optional) an instance of [`Contact`](#contact) holding data about the contained contact data - `->voice()` (optional) an instance of [`Voice`](#voice) holding data about the contained voical message - `->sticker()` (optional) an instance of [`Sticker`](#sticker) holding data about the contained sticker +- `->invoice()` (optional) an instance of [`Invoice`](#invoice) holding data about the contained invoice - `->newChatMembers()` a collection of [`User`](#user) holding the list of users that joined the group/supergroup - `->leftChatMember()` (optional) an instance of [`User`](#user) holding data about the user that left the group/supergroup - `->webAppData()` (optional) incoming data from sendData method of telegram WebApp @@ -68,6 +69,14 @@ contains incoming data (a message or a callback query) - `->languageCode()` user's language code - `->isPremium()` user's premium status +## `Invoice` + +- `->title()` invoice title +- `->description()` invoice description +- `->startParameter()` unique bot deep-linking parameter that can be used to generate this invoice +- `->currency()` invoice currency +- `->totalAmount()` invoice total amount (integer, not float/double) + ## `Audio` - `->id()` file ID diff --git a/docs/14.models/2.telegraph-chat.md b/docs/14.models/2.telegraph-chat.md index 0622e3d3..d2af2fe7 100644 --- a/docs/14.models/2.telegraph-chat.md +++ b/docs/14.models/2.telegraph-chat.md @@ -543,6 +543,17 @@ $telegraphChat->setMenuButton()->webApp("Web App", "https://my-web.app")->send() # `Attachments` +### `invoice()` + +sends an invoice + +```php +$telegraphChat->invoice('Invoice title') + ->description('Invoice Description') + ->currency('EUR') + ->addItem('Item Label', 10) + ->send(); +``` ### `document()` diff --git a/src/Concerns/CreatesScopedPayloads.php b/src/Concerns/CreatesScopedPayloads.php index dbade151..16795b35 100644 --- a/src/Concerns/CreatesScopedPayloads.php +++ b/src/Concerns/CreatesScopedPayloads.php @@ -26,6 +26,6 @@ public function invoice(string $title): TelegraphInvoicePayload { $invoicePayload = TelegraphInvoicePayload::makeFrom($this); - return $invoicePayload->invoice($title); + return $invoicePayload->invoice($title); } } diff --git a/src/DTO/Invoice.php b/src/DTO/Invoice.php index 5bd76b47..f7324984 100644 --- a/src/DTO/Invoice.php +++ b/src/DTO/Invoice.php @@ -2,13 +2,82 @@ namespace DefStudio\Telegraph\DTO; -class Invoice +use Illuminate\Contracts\Support\Arrayable; + +/** + * @implements Arrayable + */ +class Invoice implements Arrayable { - public string $payload = 'telegraph invoice'; + public string $title; + + public string $description; + + public string $startParameter; + + public string $currency; + + public int $totalAmount; + + public function __construct() + { + } + + /** + * @param array{ + * title: string, + * description: string, + * start_parameter: string, + * currency: string, + * total_amount: int + * } $data + */ + public static function fromArray(array $data): Invoice + { + $invoice = new self(); + + $invoice->title = $data['title']; + $invoice->description = $data['description']; + $invoice->startParameter = $data['start_parameter']; + $invoice->currency = $data['currency']; + $invoice->totalAmount = $data['total_amount']; + + return $invoice; + } + + public function title(): string + { + return $this->title; + } + + public function description(): string + { + return $this->description; + } + + public function startParameter(): string + { + return $this->startParameter; + } + + public function currency(): string + { + return $this->currency; + } + + public function totalAmount(): int + { + return $this->totalAmount; + } - public function __construct( - public string $title, - public string $description, - ) { + public function toArray(): array + { + return array_filter([ + 'title' => $this->title, + 'description' => $this->description, + 'start_parameter' => $this->startParameter, + 'currency' => $this->currency, + 'total_amount' => $this->totalAmount, + ], fn ($value) => $value !== null); } } diff --git a/src/DTO/Message.php b/src/DTO/Message.php index 2df77a6e..2cfab8d6 100644 --- a/src/DTO/Message.php +++ b/src/DTO/Message.php @@ -51,6 +51,8 @@ class Message implements Arrayable private ?Voice $voice = null; private ?Sticker $sticker = null; + private ?Invoice $invoice = null; + private ?WriteAccessAllowed $writeAccessAllowed = null; private function __construct() @@ -81,6 +83,7 @@ private function __construct() * photo?: array, * location?: array, * contact?: array, + * invoice?: array, * new_chat_members?: array, * left_chat_member?: array, * web_app_data?: array, @@ -178,6 +181,11 @@ public static function fromArray(array $data): Message $message->sticker = Sticker::fromArray($data['sticker']); } + if (isset($data['invoice'])) { + /* @phpstan-ignore-next-line */ + $message->invoice = Invoice::fromArray($data['invoice']); + } + /* @phpstan-ignore-next-line */ $message->newChatMembers = collect($data['new_chat_members'] ?? [])->map(fn (array $userData) => User::fromArray($userData)); @@ -308,6 +316,11 @@ public function sticker(): ?Sticker return $this->sticker; } + public function invoice(): ?Invoice + { + return $this->invoice; + } + /** * @return Collection */ @@ -354,6 +367,7 @@ public function toArray(): array 'contact' => $this->contact?->toArray(), 'voice' => $this->voice?->toArray(), 'sticker' => $this->sticker?->toArray(), + 'invoice' => $this->invoice?->toArray(), 'new_chat_members' => $this->newChatMembers->toArray(), 'left_chat_member' => $this->leftChatMember, 'web_app_data' => $this->webAppData, diff --git a/src/Exceptions/InvoiceException.php b/src/Exceptions/InvoiceException.php index 2f44c080..4e94a1c1 100644 --- a/src/Exceptions/InvoiceException.php +++ b/src/Exceptions/InvoiceException.php @@ -7,7 +7,7 @@ class InvoiceException extends Exception { - public static function validationError(MessageBag $messages): static + public static function validationError(MessageBag $messages): InvoiceException { return new self('Invalid Invoice: ' . $messages->toJson()); } diff --git a/src/Models/TelegraphChat.php b/src/Models/TelegraphChat.php index 9b8778cf..b337237f 100644 --- a/src/Models/TelegraphChat.php +++ b/src/Models/TelegraphChat.php @@ -13,6 +13,7 @@ use DefStudio\Telegraph\Exceptions\TelegraphException; use DefStudio\Telegraph\Facades\Telegraph as TelegraphFacade; use DefStudio\Telegraph\Keyboard\Keyboard; +use DefStudio\Telegraph\Payments\TelegraphInvoicePayload; use DefStudio\Telegraph\ScopedPayloads\SetChatMenuButtonPayload; use DefStudio\Telegraph\ScopedPayloads\TelegraphPollPayload; use DefStudio\Telegraph\ScopedPayloads\TelegraphQuizPayload; @@ -376,6 +377,11 @@ public function quiz(string $question): TelegraphQuizPayload return TelegraphFacade::chat($this)->quiz($question); } + public function invoice(string $title): TelegraphInvoicePayload + { + return TelegraphFacade::chat($this)->invoice($title); + } + public function dice(string $emoji = null): Telegraph { return TelegraphFacade::chat($this)->dice($emoji); diff --git a/src/Payments/TelegraphInvoicePayload.php b/src/Payments/TelegraphInvoicePayload.php index 589e9e25..4c927ca1 100644 --- a/src/Payments/TelegraphInvoicePayload.php +++ b/src/Payments/TelegraphInvoicePayload.php @@ -22,6 +22,9 @@ public function invoice(string $title): static $telegraph->data['title'] = $title; $telegraph->data['payload'] = 'created by Telegraph'; + + $telegraph->data['provider_token'] = config('telegraph.payments.provider_token'); + $telegraph->data['prices'] = []; return $telegraph; @@ -72,10 +75,11 @@ public function providerToken(string $providerToken): static return $telegraph; } - public function price(string $label, int $amount): static + public function addItem(string $label, int $amount): static { $telegraph = clone $this; + /** @phpstan-ignore-next-line */ $telegraph->data['prices'][] = [ 'label' => $label, 'amount' => $amount, @@ -114,6 +118,9 @@ public function startParameter(string $value): static return $telegraph; } + /** + * @param array $data + */ public function providerData(array $data): static { $telegraph = clone $this; @@ -144,7 +151,7 @@ public function image(string $url, int $sizeInBytes = null, int $width = null, i return $telegraph; } - public function needName($needed = true): static + public function needName(bool $needed = true): static { $telegraph = clone $this; @@ -153,7 +160,7 @@ public function needName($needed = true): static return $telegraph; } - public function needPhoneNumber($needed = true, $sendToProvider = false): static + public function needPhoneNumber(bool $needed = true, bool $sendToProvider = false): static { $telegraph = clone $this; @@ -163,7 +170,7 @@ public function needPhoneNumber($needed = true, $sendToProvider = false): static return $telegraph; } - public function needEmail($needed = true, $sendToProvider = false): static + public function needEmail(bool $needed = true, bool $sendToProvider = false): static { $telegraph = clone $this; @@ -173,7 +180,7 @@ public function needEmail($needed = true, $sendToProvider = false): static return $telegraph; } - public function needShippingAddress($needed = true): static + public function needShippingAddress(bool $needed = true): static { $telegraph = clone $this; @@ -182,7 +189,7 @@ public function needShippingAddress($needed = true): static return $telegraph; } - public function flexible($flexible = true): static + public function flexible(bool $flexible = true): static { $telegraph = clone $this; diff --git a/src/Telegraph.php b/src/Telegraph.php index 39ce7ff0..fbf38e03 100755 --- a/src/Telegraph.php +++ b/src/Telegraph.php @@ -117,10 +117,6 @@ class Telegraph public const ENDPOINT_SEND_INVOICE = 'sendInvoice'; public const ENDPOINT_CREATE_INVOICE_LINK = 'createInvoiceLink'; - public const ENDPOINT_SEND_INVOICE = 'sendInvoice'; - public const ENDPOINT_CREATE_INVOICE_LINK = 'createInvoiceLink'; - - /** @var array */ protected array $data = []; diff --git a/tests/.pest/snapshots/Unit/Concerns/SendsAttachmentsTest/it_can_send_an_invoice.snap b/tests/.pest/snapshots/Unit/Concerns/SendsAttachmentsTest/it_can_send_an_invoice.snap new file mode 100644 index 00000000..a8b3c83b --- /dev/null +++ b/tests/.pest/snapshots/Unit/Concerns/SendsAttachmentsTest/it_can_send_an_invoice.snap @@ -0,0 +1 @@ +{"url":"https:\/\/api.telegram.org\/bot3f3814e1-5836-3d77-904e-60f64b15df36\/createInvoiceLink","payload":{"title":"Test Invoice","payload":"created by Telegraph","provider_token":"","prices":[{"label":"Test Label","amount":10}],"description":"Test Description","currency":"EUR","max_tip_amount":70,"suggested_tip_amounts":[30,20],"start_parameter":"10","provider_data":"[\"Test Provider Data\"]","photo_url":"Test Image Link","photo_size":20,"photo_width":20,"need_name":true,"need_phone_number":true,"send_phone_number_to_provider":true,"need_email":true,"send_email_to_provider":true,"need_shipping_address":true,"is_flexible":true},"files":[]} \ No newline at end of file diff --git a/tests/Sandbox/TelegraphTest.php b/tests/Sandbox/TelegraphTest.php index 649cf25a..87990706 100644 --- a/tests/Sandbox/TelegraphTest.php +++ b/tests/Sandbox/TelegraphTest.php @@ -33,15 +33,17 @@ it('can send an invoice', function () { - Telegraph::invoice('Test Invoice') - ->description('A test invoice sent from Telegraph') - ->currency('EUR') - ->price('line 1', 1000) - ->price('line 2', 1500) - ->price('line 3', 5000) - ->providerToken(env('SANDOBOX_TELEGRAM_PAYMENT_PROVIDER_TOKEN')) - ->link() - ->send() - ->dump(); - -})->only()->skip(fn () => empty(env('SANDOBOX_TELEGRAM_PAYMENT_PROVIDER_TOKEN')) || empty(env('SANDOBOX_TELEGRAM_BOT_TOKEN')) || env('SANDOBOX_TELEGRAM_BOT_TOKEN') === ':fake_bot_token:', 'Sandbox telegram bot token missing'); + $results = Telegraph::invoice('Test Invoice') + ->description('A test invoice sent from Telegraph') + ->currency('EUR') + ->addItem('line 1', 1000) + ->addItem('line 2', 1500) + ->addItem('line 3', 5000) + ->providerToken(env('SANDOBOX_TELEGRAM_PAYMENT_PROVIDER_TOKEN')) + ->link() + ->send(); + + expect($results['ok'])->toBeTrue() + ->and($results['result'])->toBeString(); + +})->skip(fn () => empty(env('SANDOBOX_TELEGRAM_PAYMENT_PROVIDER_TOKEN')) || empty(env('SANDOBOX_TELEGRAM_BOT_TOKEN')) || env('SANDOBOX_TELEGRAM_BOT_TOKEN') === ':fake_bot_token:', 'Sandbox telegram bot token missing'); diff --git a/tests/TestCase.php b/tests/TestCase.php index 86511a53..6168d824 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -37,7 +37,6 @@ public function defineEnvironment($app): void protected function defineDatabaseMigrations(): void { - ; $this->loadMigrationsFrom(__DIR__ . '/../database/migrations'); } diff --git a/tests/Unit/Concerns/SendsAttachmentsTest.php b/tests/Unit/Concerns/SendsAttachmentsTest.php index 29d5a028..64ae801e 100644 --- a/tests/Unit/Concerns/SendsAttachmentsTest.php +++ b/tests/Unit/Concerns/SendsAttachmentsTest.php @@ -347,6 +347,27 @@ )->toMatchUtf8TelegramSnapshot(); }); +it('can send an invoice', function () { + expect( + fn (Telegraph $telegraph) => $telegraph->invoice('Test Invoice') + ->description('Test Description') + ->currency('EUR') + ->addItem('Test Label', 10) + ->maxTip(70) + ->suggestedTips([30,20]) + ->startParameter(10) + ->providerData(['Test Provider Data']) + ->image('Test Image Link', 20, 20) + ->needName() + ->needPhoneNumber(sendToProvider: true) + ->needEmail(sendToProvider: true) + ->needShippingAddress() + ->flexible() + ->link() + ) + ->toMatchUtf8TelegramSnapshot(); +}); + test('photos are validated', function (string $path, bool $valid, string $exceptionClass = null, string $exceptionMessage = null, array $customConfigs = []) { foreach ($customConfigs as $key => $value) { Config::set($key, $value); diff --git a/tests/Unit/DTO/InvoiceTest.php b/tests/Unit/DTO/InvoiceTest.php new file mode 100644 index 00000000..ce4f4b6b --- /dev/null +++ b/tests/Unit/DTO/InvoiceTest.php @@ -0,0 +1,23 @@ + 'test title', + 'description' => 'test description', + 'start_parameter' => 'test', + 'currency' => 'EUR', + 'total_amount' => 20, + ]); + + $array = $dto->toArray(); + + $reflection = new ReflectionClass($dto); + foreach ($reflection->getProperties() as $property) { + expect($array)->toHaveKey(Str::of($property->name)->snake()); + } +}); diff --git a/tests/Unit/DTO/MessageTest.php b/tests/Unit/DTO/MessageTest.php index 202760f6..760d4325 100644 --- a/tests/Unit/DTO/MessageTest.php +++ b/tests/Unit/DTO/MessageTest.php @@ -251,6 +251,13 @@ 'file_size' => 42, ], ], + 'invoice' => [ + 'title' => 'Title', + 'description' => 'Description', + 'start_parameter' => 'test', + 'currency' => 'EUR', + 'total_amount' => 20, + ], 'location' => [ 'latitude' => 12456789, 'longitude' => 98765431,