From 8aefdb1d00341741f273fa434b637a4bbfc84dcb Mon Sep 17 00:00:00 2001 From: Krishan Koenig Date: Fri, 6 Dec 2024 13:38:27 +0100 Subject: [PATCH] wip --- README.md | 237 +----- docs/endpoint-collections.md | 718 ++++++++++++++++++ docs/http-adapters.md | 74 ++ docs/idempotency.md | 57 ++ docs/payments.md | 93 +++ docs/requests.md | 47 ++ docs/responses.md | 43 ++ docs/testing.md | 55 ++ examples/captures/create-capture.php | 18 +- examples/captures/get-capture.php | 6 +- examples/captures/list-captures.php | 13 +- examples/client-links/create-client-link.php | 31 +- .../create-customer-first-payment.php | 32 +- phpstan-baseline.neon | 26 +- .../CustomerEndpointCollection.php | 1 + .../PaymentEndpointCollection.php | 12 +- src/Factories/Factory.php | 19 +- src/Helpers.php | 15 +- src/Http/Adapter/GuzzleMollieHttpAdapter.php | 3 +- src/Http/Payload/Metadata.php | 8 +- tests/Helpers/HelpersTest.php | 141 ++++ 21 files changed, 1379 insertions(+), 270 deletions(-) create mode 100644 docs/endpoint-collections.md create mode 100644 docs/http-adapters.md create mode 100644 docs/idempotency.md create mode 100644 docs/payments.md create mode 100644 docs/requests.md create mode 100644 docs/responses.md create mode 100644 docs/testing.md create mode 100644 tests/Helpers/HelpersTest.php diff --git a/README.md b/README.md index 97abdfb5..39e03df3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -****

+

@@ -32,14 +32,6 @@ The easiest way to install the Mollie API client is by using [Composer](http://g composer require mollie/mollie-api-php ``` -To work with the most recent API version, ensure that you are using a version of this API client that is equal to or greater than 2.0.0. If you prefer to continue using the v1 API, make sure your client version is below 2.0.0. For guidance on transitioning from v1 to v2, please refer to the [migration notes](https://docs.mollie.com/docs/migrating-from-v1-to-v2). - -### Manual Installation ### -If you're not familiar with using composer we've added a ZIP file to the releases containing the API client and all the packages normally installed by composer. -Download the ``mollie-api-php.zip`` from the [releases page](https://github.com/mollie/mollie-api-php/releases). - -Include the ``vendor/autoload.php`` as shown in [Initialize example](https://github.com/mollie/mollie-api-php/blob/master/examples/initialize.php). - ## Usage ## Initializing the Mollie API client, and setting your API key. @@ -49,79 +41,12 @@ $mollie = new \Mollie\Api\MollieApiClient(); $mollie->setApiKey("test_dHar4XY7LxsDOtmnkVtjNVWXLSlXsM"); ``` -With the `MollieApiClient` you can now access any of the following endpoints by selecting them as a property of the client: - -| API | Resource | Code | Link to Endpoint file | -| ----------------------- | ----------------------- | -------------------------- | -------------------------------------------------------------------------- | -| **[Balances API](https://docs.mollie.com/reference/v2/balances-api/overview)** | Balance | `$mollie->balances` | [BalanceEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/BalanceEndpoint.php) | -| | Balance Report | `$mollie->balanceReports` | [BalanceReportEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/BalanceReportEndpoint.php) | -| | Balance Transaction | `$mollie->balanceTransactions` | [BalanceTransactionEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/BalanceTransactionEndpoint.php) | -| **[Chargebacks API](https://docs.mollie.com/reference/v2/chargebacks-api/overview)** | Chargeback |`$mollie->chargebacks` | [ChargebackEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/ChargebackEndpoint.php) | -| | Payment Chargeback | `$mollie->paymentChargebacks` | [PaymentChargebackEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/PaymentChargebackEndpoint.php) | -| **[Clients API](https://docs.mollie.com/reference/v2/clients-api/overview)** | Client | `$mollie->clients` | [ClientEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/ClientEndpoint.php) | -| **[Client Links API](https://docs.mollie.com/reference/v2/client-links-api/overview)** | Client Link | `$mollie->clientLinks` | [ClientLinkEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/ClientLinkEndpoint.php) | -| **[Customers API](https://docs.mollie.com/reference/v2/customers-api/overview)** | Customer | `$mollie->customers` | [CustomerEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/CustomerEndpoint.php) | -| | Customer Payment | `$mollie->customerPayments` | [CustomerPaymentsEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/CustomerPaymentsEndpoint.php) | -| **[Invoices API](https://docs.mollie.com/reference/v2/invoices-api/overview)** | Invoice | `$mollie->invoices` | [InvoiceEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/InvoiceEndpoint.php) | -| **[Mandates API](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/MandateEndpoint.php)** | Mandate | `$mollie->mandates` | [MandateEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/MandateEndpoint.php) | -| **[Methods API](https://docs.mollie.com/reference/v2/methods-api/overview)** | Payment Method | `$mollie->methods` | [MethodEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/MethodEndpoint.php) | -| **[Onboarding API](https://docs.mollie.com/reference/v2/onboarding-api/overview)** | Onboarding |`$mollie->onboarding` | [OnboardingEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/OnboardingEndpoint.php) | -| **[Orders API](https://docs.mollie.com/reference/v2/orders-api/overview)** | Order | `$mollie->orders` | [OrderEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/OrderEndpoint.php) | -| | Order Line | `$mollie->orderLines` | [OrderLineEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/OrderLineEndpoint.php) | -| | Order Payment | `$mollie->orderPayments` | [OrderPaymentEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/OrderPaymentEndpoint.php) | -| **[Organizations API](https://docs.mollie.com/reference/v2/organizations-api/overview)** | Organization | `$mollie->organizations` | [OrganizationEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/OrganizationEndpoint.php) | -| | Organization Partner | `$mollie->organizationPartners` | [OrganizationPartnerEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/OrganizationPartnerEndpoint.php) | -| **[Captures API](https://docs.mollie.com/reference/v2/captures-api/overview)** | Payment Captures | `$mollie->organizations` | [PaymentCaptureEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/PaymentCaptureEndpoint.php) | -| **[Payments API](https://docs.mollie.com/reference/v2/payments-api/overview)** | Payment | `$mollie->payments` | [PaymentEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/PaymentEndpoint.php) | -| | Payment Route | `$mollie->paymentRoutes` | [PaymentRouteEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/PaymentRouteEndpoint.php) | -| **[Payment links API](https://docs.mollie.com/reference/v2/payment-links-api/overview)** | Payment Link | `$mollie->paymentLinks` | [PaymentLinkEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/PaymentLinkEndpoint.php) | -| **[Permissions API](https://docs.mollie.com/reference/v2/permissions-api/overview)** | Permission | `$mollie->permissions` | [PermissionEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/PermissionEndpoint.php) | -| **[Profile API](https://docs.mollie.com/reference/v2/profiles-api/overview)** | Profile | `$mollie->profiles` | [ProfileEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/ProfileEndpoint.php) | -| | Profile Method | `$mollie->profileMethods` | [ProfileMethodEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/ProfileMethodEndpoint.php) | -| **[Refund API](https://docs.mollie.com/reference/v2/refunds-api/overview)** | Refund | `$mollie->refunds` | [RefundEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/RefundEndpoint.php) | -| | Order Refund | `$mollie->orderRefunds` | [OrderRefundEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/OrderRefundEndpoint.php) | -| | Payment Refund | `$mollie->paymentRefunds` | [PaymentRefundEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/PaymentRefundEndpoint.php) | -| **[Settlements API](https://docs.mollie.com/reference/v2/settlements-api/overview)** | Settlement | `$mollie->settlements` | [SettlementsEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/SettlementsEndpoint.php) | -| | Settlement Capture | `$mollie->settlementCaptures` | [SettlementCaptureEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/SettlementCaptureEndpoint.php) | -| | Settlement Chargeback | `$mollie->settlementChargebacks` | [SettlementChargebackEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/SettlementChargebackEndpoint.php) | -| | Settlement Payment | `$mollie->settlementPayments` | [SettlementPaymentEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/SettlementPaymentEndpoint.php) | -| | Settlement Refund | `$mollie->settlementRefunds` | [SettlementRefundEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/SettlementRefundEndpoint.php) | -| **[Shipments API](https://docs.mollie.com/reference/v2/shipments-api/overview)** | Shipment | `$mollie->shipments` | [ShipmentEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/ShipmentEndpoint.php) | -| **[Subscriptions API](https://docs.mollie.com/reference/v2/subscriptions-api/overview)** | Subscription | `$mollie->subscriptions` | [SubscriptionEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/SubscriptionEndpoint.php) | -| **[Terminal API](https://docs.mollie.com/reference/v2/terminals-api/overview)** | Terminal | `$mollie->terminals` | [TerminalEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/TerminalEndpoint.php) | -| **[Wallets API](https://docs.mollie.com/reference/v2/wallets-api/overview)** | Wallet | `$mollie->wallets` | [WalletEndpoint](https://github.com/mollie/mollie-api-php/blob/master/src/Endpoints/WalletEndpoint.php) | - Find our full documentation online on [docs.mollie.com](https://docs.mollie.com). -#### Creating Payments #### -**[Create Payment Documentation](https://docs.mollie.com/reference/v2/payments-api/create-payment)** - +#### Example usage #### ```php -// old way of creating a payment -$payment = $mollie->payments->create([ - "amount" => [ - "currency" => "EUR", - "value" => "10.00" - ], - "description" => "My first API payment", - "redirectUrl" => "https://webshop.example.org/order/12345/", - "webhookUrl" => "https://webshop.example.org/mollie-webhook/", -]); - -// newish way use Mollie\Api\Http\Payload\Money; use Mollie\Api\Http\Payload\CreatePaymentPayload; - -$payload = new CreatePaymentPayload( - description: 'My first API payment', - amount: new Money('EUR', '10.00'), - redirectUrl: 'https://webshop.example.org/order/12345/', - webhookUrl: 'https://webshop.example.org/mollie-webhook/' -); - -$payment = $mollie->payments->create($payload); - -// newest ;-) use Mollie\Api\Http\Requests\CreatePaymentRequest; $payload = new CreatePaymentPayload( @@ -131,148 +56,58 @@ $payload = new CreatePaymentPayload( webhookUrl: 'https://webshop.example.org/mollie-webhook/' ); -$payment = $mollie->send($payload); -``` -_After creation, the payment id is available in the `$payment->id` property. You should store this id with your order._ - -After storing the payment id you can send the customer to the checkout using `$payment->getCheckoutUrl()`. +/** @var Mollie\Api\Http\Response $response */ +$response = $mollie->send(new CreatePaymentRequest($payload)); -```php -header("Location: " . $payment->getCheckoutUrl(), true, 303); -``` - -_This header location should always be a GET, thus we enforce 303 http response code_ - -For a payment create example, see [Example - New Payment](https://github.com/mollie/mollie-api-php/blob/master/examples/payments/create-payment.php). - -##### Multicurrency ##### -Since API v2.0 it is now possible to create non-EUR payments for your customers. -A full list of available currencies can be found [in our documentation](https://docs.mollie.com/guides/multicurrency). - -```php -$payment = $mollie->payments->create([ - "amount" => [ - "currency" => "USD", - "value" => "10.00" - ], - //... -]); +/** @var Mollie\Api\Resources\Payment $payment */ +$payment = $response->toResource(); ``` -_After creation, the `settlementAmount` will contain the EUR amount that will be settled on your account._ - -##### Create fully integrated iDEAL payments ##### -To fully integrate iDEAL payments on your website, follow these additional steps: - -1. Retrieve the list of issuers (banks) that support iDEAL. - -```php -$method = $mollie->methods->get(\Mollie\Api\Types\PaymentMethod::IDEAL, ["include" => "issuers"]); -``` - -Use the `$method->issuers` list to let the customer pick their preferred issuer. - -_`$method->issuers` will be a list of objects. Use the property `$id` of this object in the - API call, and the property `$name` for displaying the issuer to your customer._ - -2. Create a payment with the selected issuer: -```php -$payment = $mollie->payments->create([ - "amount" => [ - "currency" => "EUR", - "value" => "10.00" - ], - "description" => "My first API payment", - "redirectUrl" => "https://webshop.example.org/order/12345/", - "webhookUrl" => "https://webshop.example.org/mollie-webhook/", - "method" => \Mollie\Api\Types\PaymentMethod::IDEAL, - "issuer" => $selectedIssuerId, // e.g. "ideal_INGBNL2A" -]); -``` - -_The `_links` property of the `$payment` object will contain an object `checkout` with a `href` property, which is a URL that points directly to the online banking environment of the selected issuer. -A short way of retrieving this URL can be achieved by using the `$payment->getCheckoutUrl()`._ - -For a more in-depth example, see [Example - iDEAL payment](https://github.com/mollie/mollie-api-php/blob/master/examples/payments/create-ideal-payment.php). - -#### Retrieving Payments #### -**[Retrieve Payment Documentation](https://docs.mollie.com/reference/v2/payments-api/get-payment)** - -We can use the `$payment->id` to retrieve a payment and check if the payment `isPaid`. - -```php -$payment = $mollie->payments->get($payment->id); - -if ($payment->isPaid()) -{ - echo "Payment received."; -} -``` - -Or retrieve a collection of payments. - -```php -$payments = $mollie->payments->page(); -``` - -For an extensive example of listing payments with the details and status, see [Example - List Payments](https://github.com/mollie/mollie-api-php/blob/master/examples/payments/list-payments.php). +## Upgrading -#### Refunding payments #### -**[Refund Payment Documentation](https://docs.mollie.com/reference/v2/refunds-api/create-payment-refund)** +Please see [UPGRADING](UPGRADING.md) for details. -Our API provides support for refunding payments. It's important to note that there is no confirmation step, and all refunds are immediate and final. Refunds are available for all payment methods except for paysafecard and gift cards. +## API documentation ## +For an in-depth understanding of our API, please explore the [Mollie Developer Portal](https://www.mollie.com/developers). Our API documentation is available in English. -```php -$payment = $mollie->payments->get($payment->id); - -// Refund € 2 of this payment -$refund = $payment->refund([ - "amount" => [ - "currency" => "EUR", - "value" => "2.00" - ] -]); -``` +## Documentation -#### Payment webhook #### -When the payment status changes, the `webhookUrl` you specified during payment creation will be called. You can use the `id` from the POST parameters to check the status and take appropriate actions. For more details, refer to [Example - Webhook](https://github.com/mollie/mollie-api-php/blob/master/examples/payments/webhook.php). +For detailed documentation about using this PHP client, see the following guides: -For a working example, see [Example - Refund payment](https://github.com/mollie/mollie-api-php/blob/master/examples/payments/refund-payment.php). +- [Endpoint Collections](docs/endpoint-collections.md) - Learn how to interact with all available API endpoints. +- [HTTP Adapters](docs/http-adapters.md) - Information on customizing HTTP communication. +- [Idempotency](docs/idempotency.md) - Best practices and setup for idempotent requests. +- [Payments](docs/payments.md) - Comprehensive guide on handling payments. +- [Requests](docs/requests.md) - Overview and usage of request objects in the API client. +- [Responses](docs/responses.md) - Handling and understanding responses from the API. +- [Testing](docs/testing.md) - Guidelines for testing with the Mollie API client. -### Enabling debug mode ### +These guides provide in-depth explanations and examples for advanced usage of the client. -When troubleshooting, it can be highly beneficial to have access to the submitted request within the `ApiException`. To safeguard against inadvertently exposing sensitive request data in your local application logs, the debugging feature is initially turned off. +## Examples -To enable debugging and inspect the request: +The Mollie API client comes with a variety of examples to help you understand how to implement various API features. These examples are a great resource for learning how to integrate Mollie payments into your application. -```php -/** @var $mollie \Mollie\Api\MollieApiClient */ -$mollie->enableDebugging(); - -try { - $mollie->payments->get('tr_12345678'); -} catch (\Mollie\Api\Exceptions\ApiException $exception) { - $request = $exception->getRequest(); -} -``` +Here are some of the key examples included: -If you are recording instances of `ApiException`, the request details will be included in the logs. It is vital to ensure that no sensitive information is retained within these logs and to perform cleanup after debugging is complete. +- **Create Payment**: Demonstrates how to create a new payment. + - [Create a simple payment](examples/payments/create-payment.php) + - [Create an iDEAL payment](examples/payments/create-ideal-payment.php) + - [Create a payment with manual capture](examples/payments/create-capturable-payment.php) -To disable debugging again: +- **Manage Customers**: Shows how to manage customers in your Mollie account. + - [Create a customer](examples/customers/create-customer.php) + - [Update a customer](examples/customers/update-customer.php) + - [Delete a customer](examples/customers/delete-customer.php) -```php -/** @var $mollie \Mollie\Api\MollieApiClient */ -$mollie->disableDebugging(); -``` +- **Subscriptions and Recurring Payments**: + - [Create a customer for recurring payments](examples/customers/create-customer-first-payment.php) + - [Create a recurring payment](examples/customers/create-customer-recurring-payment.php) -Please note that debugging is only available when using the default Guzzle http adapter (`GuzzleMollieHttpAdapter`). +For a full list of examples, please refer to the [examples directory](examples/). -## Upgrading +These examples are designed to be run in a safe testing environment. Make sure to use your test API keys and review each example's code before integrating it into your production environment. -Please see [UPGRADING](UPGRADING.md) for details. - -## API documentation ## -For an in-depth understanding of our API, please explore the [Mollie Developer Portal](https://www.mollie.com/developers). Our API documentation is available in English. ## Contributing to Our API Client ## Would you like to contribute to improving our API client? We welcome [pull requests](https://github.com/mollie/mollie-api-php/pulls?utf8=%E2%9C%93&q=is%3Apr). But, if you're interested in contributing to a technology-focused organization, Mollie is actively recruiting developers and system engineers. Discover our current [job openings](https://jobs.mollie.com/) or [reach out](mailto:personeel@mollie.com). diff --git a/docs/endpoint-collections.md b/docs/endpoint-collections.md new file mode 100644 index 00000000..6a0f7c2c --- /dev/null +++ b/docs/endpoint-collections.md @@ -0,0 +1,718 @@ +# Introduction +Endpoint collections provide a fluent interface for interacting with Mollie's API. Each collection manages specific resource types like payments, customers, and subscriptions, offering methods to create, retrieve, update, and delete these resources. + +## Setup + +1. **Initialize the Mollie Client:** + +```php +$mollie = new \Mollie\Api\MollieApiClient(); +$mollie->setApiKey("test_*************************"); +``` + +2. **Access Endpoints via the Client:** +```php +// Payments endpoint +$mollie->payments->... + +// Customers endpoint +$mollie->customers->... + +// Other endpoints +$mollie->balances->... +$mollie->orders->... +``` + +3. **Call methods** + +**Simple: Using arrays** +This approach is direct but provides less type safety: +```php +$payment = $mollie->payments->create([ + 'amount' => [ + 'currency' => 'EUR', + 'value' => '10.00' + ], + 'description' => 'My first API payment' +]); +``` + +**Advanced: Using typed payloads/queries** +This approach provides full IDE support and type safety: +```php +use Mollie\Api\Http\Payload\Money; +use Mollie\Api\Http\Payload\CreatePaymentPayload; + +$payment = $mollie->payments->create( + new CreatePaymentPayload( + description: 'My first API payment', + amount: new Money('EUR', '10.00') + ) +); +``` + +If you have an array and need to interact with the payload or query, you can use a dedicated factory to convert the array into a typed class. + +use Mollie\Api\Http\Payload\Money; +use Mollie\Api\Factories\CreatePaymentPayloadFactory; + +// Fully untyped data +$createPaymentPayload = CreatePaymentPayloadFactory::new([ + 'amount' => [ + 'currency' => 'EUR', + 'value' => '10.00' + ], + 'description' => 'My first API payment' +]); + +// Partially untyped +$createPaymentPayload = CreatePaymentPayloadFactory::new([ + 'amount' => new Money('EUR', '10.00'), + 'description' => 'My first API payment' +]); + +// Access payload +$createPaymentPayload->amount->... +``` + +This approach allows you to seamlessly convert arrays into typed objects, providing better IDE support and type safety while working with your data. + +# Available Endpoints + +## Balances + +[Official Documentation](https://docs.mollie.com/reference/v2/balances-api/get-balance) + +**Available Queries:** +- `GetPaginatedBalanceQuery` - For listing balances with pagination + +### Balance Information + +```php +// Get primary balance +$balance = $mollie->balances->primary(); + +// Get specific balance +$balance = $mollie->balances->get('bal_12345'); + +// List balances +$balances = $mollie->balances->page(); +``` + +## Balance Reports + +[Official Documentation](https://docs.mollie.com/reference/v2/balance-reports-api/get-balance-report) + +**Available Queries:** +- `GetBalanceReportQuery` - For retrieving balance reports + +### Balance Report Details + +```php +// Get report for specific balance +$report = $mollie->balanceReports->getForId('bal_12345', [ + 'from' => '2024-01-01', + 'until' => '2024-01-31', + 'grouping' => 'transaction-categories' +]); +``` + +## Balance Transactions + +[Official Documentation](https://docs.mollie.com/reference/v2/balance-transactions-api/list-balance-transactions) + +**Available Queries:** +- `GetPaginatedBalanceQuery` - For listing balance transactions with pagination + +### Balance Transaction Management + +```php +// Get transactions for a balance +$transactions = $mollie->balanceTransactions->pageFor($balance); + +// Use iterator for all transactions +foreach ($mollie->balanceTransactions->iteratorFor($balance) as $transaction) { + echo $transaction->id; +} +``` + +## Chargebacks + +[Official Documentation](https://docs.mollie.com/reference/v2/chargebacks-api/get-chargeback) + +**Available Queries:** +- `GetPaginatedChargebacksQuery` - For listing chargebacks with pagination + +### Chargeback Management + +```php +// Get all chargebacks +$chargebacks = $mollie->chargebacks->page(); + +// Use iterator for all chargebacks +foreach ($mollie->chargebacks->iterator() as $chargeback) { + echo $chargeback->id; +} +``` + +## Clients + +[Official Documentation](https://docs.mollie.com/reference/v2/clients-api/get-client) + +**Available Queries:** +- `GetClientQuery` - For retrieving client details + +### Client Management + +```php +// Get a specific client +$client = $mollie->clients->get('org_12345678', [ + 'testmode' => true +]); + +// List all clients +$clients = $mollie->clients->page( + from: 'org_12345678', + limit: 50 +); +``` + +## Customers + +[Official Documentation](https://docs.mollie.com/reference/v2/customers-api/create-customer) + +**Available Payloads:** +- `CreateCustomerPayload` - For creating new customers +- `UpdateCustomerPayload` - For updating existing customers + +### Customer Management + +```php +// Create a customer +$customer = $mollie->customers->create([ + 'name' => 'John Doe', + 'email' => 'john@example.org', +]); + +// Get a customer +$customer = $mollie->customers->get('cst_8wmqcHMN4U'); + +// Update a customer +$customer = $mollie->customers->update('cst_8wmqcHMN4U', [ + 'name' => 'Updated Name' +]); + +// Delete a customer +$mollie->customers->delete('cst_8wmqcHMN4U'); + +// List customers +$customers = $mollie->customers->page(); +``` + +## Invoices + +[Official Documentation](https://docs.mollie.com/reference/v2/invoices-api/get-invoice) + +**Available Queries:** +- `GetPaginatedInvoiceQuery` - For listing invoices with pagination + +### Invoice Management + +```php +// Get a specific invoice +$invoice = $mollie->invoices->get('inv_xBEbP9rvAq'); + +// List all invoices +$invoices = $mollie->invoices->page( + from: 'inv_xBEbP9rvAq', + limit: 50 +); +``` + +## Mandates + +[Official Documentation](https://docs.mollie.com/reference/v2/mandates-api/create-mandate) + +**Available Payloads:** +- `CreateMandatePayload` - For creating new mandates + +### Mandate Management + +```php +// Create a mandate for a customer +$mandate = $mollie->mandates->createFor($customer, [ + 'method' => \Mollie\Api\Types\PaymentMethod::DIRECTDEBIT, + 'consumerName' => 'John Doe', + 'consumerAccount' => 'NL55INGB0000000000', + 'consumerBic' => 'INGBNL2A', + 'signatureDate' => '2024-01-01', + 'mandateReference' => 'YOUR-COMPANY-MD13804' +]); + +// Get a mandate +$mandate = $mollie->mandates->getFor($customer, 'mdt_h3gAaD5zP'); + +// Revoke a mandate +$mollie->mandates->revokeFor($customer, 'mdt_h3gAaD5zP'); + +// List mandates +$mandates = $mollie->mandates->pageFor($customer); +``` + +## Methods + +[Official Documentation](https://docs.mollie.com/reference/v2/methods-api/get-method) + +**Available Queries:** +- `GetPaymentMethodQuery` - For retrieving method details +- `GetAllMethodsQuery` - For listing all available methods +- `GetEnabledPaymentMethodsQuery` - For listing enabled methods + +### Payment Methods + +```php +// Get a method +$method = $mollie->methods->get(\Mollie\Api\Types\PaymentMethod::IDEAL); + +// List all methods +$methods = $mollie->methods->all(); + +// List enabled methods +$methods = $mollie->methods->allEnabled([ + 'amount' => [ + 'currency' => 'EUR', + 'value' => '100.00' + ] +]); +``` + +### Method Issuers + +```php +// Enable an issuer +$issuer = $mollie->methodIssuers->enable( + 'pfl_v9hTwCvYqw', + \Mollie\Api\Types\PaymentMethod::IDEAL, + 'ideal_INGBNL2A' +); + +// Disable an issuer +$mollie->methodIssuers->disable( + 'pfl_v9hTwCvYqw', + \Mollie\Api\Types\PaymentMethod::IDEAL, + 'ideal_INGBNL2A' +); +``` + +## Onboarding + +[Official Documentation](https://docs.mollie.com/reference/v2/onboarding-api/get-onboarding-status) + +**Available Queries:** +- `GetOnboardingStatusQuery` - For retrieving onboarding status + +### Onboarding Management + +```php +// Get onboarding status +$onboarding = $mollie->onboarding->status(); +``` + +## Organizations + +[Official Documentation](https://docs.mollie.com/reference/v2/organizations-api/get-organization) + +**Available Queries:** +- `GetOrganizationQuery` - For retrieving organization details + +### Organization Management + +```php +// Get an organization +$organization = $mollie->organizations->get('org_12345678'); + +// Get current organization +$organization = $mollie->organizations->current(); + +// Get partner status +$partner = $mollie->organizations->partnerStatus(); +``` + +## Permissions + +[Official Documentation](https://docs.mollie.com/reference/v2/permissions-api/get-permission) + +**Available Queries:** +- `GetPermissionQuery` - For retrieving permission details + +### Permission Management + +```php +// Get a permission +$permission = $mollie->permissions->get('payments.read'); + +// List all permissions +$permissions = $mollie->permissions->list(); +``` + +## Payments + +[Official Documentation](https://docs.mollie.com/reference/v2/payments-api/create-payment) + +**Available Payloads:** +- `CreatePaymentPayload` - For creating new payments +- `UpdatePaymentPayload` - For updating existing payments + +**Available Queries:** +- `GetPaymentQuery` - For retrieving payments with optional embeds +- `GetPaginatedPaymentsQuery` - For listing payments with pagination + +### Payment Management + +```php +// Create a payment using typed payload +use Mollie\Api\Http\Payload\Money; +use Mollie\Api\Http\Payload\CreatePaymentPayload; + +$payload = new CreatePaymentPayload( + description: 'Order #12345', + amount: new Money('EUR', '10.00'), + redirectUrl: 'https://webshop.example.org/order/12345/', + webhookUrl: 'https://webshop.example.org/mollie-webhook/' +); + +$payment = $mollie->payments->create($payload); + +// Get a payment +$payment = $mollie->payments->get('tr_7UhSN1zuXS'); + +// Update a payment +$payment = $mollie->payments->update('tr_7UhSN1zuXS', [ + 'description' => 'Updated description' +]); + +// Cancel a payment +$mollie->payments->cancel('tr_7UhSN1zuXS'); + +// List payments +$payments = $mollie->payments->page(); +``` + +### Payment Links + +[Official Documentation](https://docs.mollie.com/reference/v2/payment-links-api/create-payment-link) + +**Available Payloads:** +- `CreatePaymentLinkPayload` - For creating payment links +- `UpdatePaymentLinkPayload` - For updating payment links + +### Payment Captures + +[Official Documentation](https://docs.mollie.com/reference/v2/captures-api/get-capture) + +**Available Payloads:** +- `CreatePaymentCapturePayload` - For creating captures + +**Available Queries:** +- `GetPaymentCaptureQuery` - For retrieving capture details +- `GetPaginatedPaymentCapturesQuery` - For listing captures + +### Payment Chargebacks + +```php +// Get a chargeback +$chargeback = $mollie->paymentChargebacks->getFor($payment, 'chb_n9z0tp'); + +// List chargebacks for payment +$chargebacks = $mollie->paymentChargebacks->pageFor($payment); +``` + +### Payment Routes + +```php +// Update release date for a route +$route = $mollie->paymentRoutes->updateReleaseDateFor( + $payment, + 'rt_abc123', + '2024-01-01' +); +``` + +## Profiles + +[Official Documentation](https://docs.mollie.com/reference/v2/profiles-api/create-profile) + +**Available Payloads:** +- `CreateProfilePayload` - For creating new profiles + +**Available Factories:** +- `ProfileFactory` - For creating profile instances + +### Profile Management + +```php +// Create a profile +$profile = $mollie->profiles->create(new CreateProfilePayload( + 'My Test Profile', + 'https://example.org', + 'info@example.org', + '+31612345678', + 'test' +)); + +// Get a profile +$profile = $mollie->profiles->get('pfl_v9hTwCvYqw'); + +// Get current profile +$profile = $mollie->profiles->getCurrent(); + +// Update a profile +$profile = $mollie->profiles->update('pfl_v9hTwCvYqw', [ + 'name' => 'Updated Profile Name' +]); + +// Delete a profile +$mollie->profiles->delete('pfl_v9hTwCvYqw'); + +// List profiles +$profiles = $mollie->profiles->page(); +``` + +### Profile Methods + +```php +// Enable a method +$method = $mollie->profileMethods->enable( + 'pfl_v9hTwCvYqw', + \Mollie\Api\Types\PaymentMethod::IDEAL +); + +// Disable a method +$mollie->profileMethods->disable( + 'pfl_v9hTwCvYqw', + \Mollie\Api\Types\PaymentMethod::IDEAL +); +``` + +## Refunds + +[Official Documentation](https://docs.mollie.com/reference/v2/refunds-api/create-refund) + +**Available Payloads:** +- `CreateRefundPaymentPayload` - For creating refunds on payments +- `CreateRefundOrderPayload` - For creating refunds on orders + +**Available Queries:** +- `GetPaginatedRefundsQuery` - For listing refunds with pagination + +### Refund Management + +```php +// Create a refund for a payment +$refund = $mollie->refunds->createForPayment($paymentId, [ + 'amount' => [ + 'currency' => 'EUR', + 'value' => '15.00' + ], + 'description' => 'Refund for returned item' +]); + +// List refunds +$refunds = $mollie->refunds->page(); +``` + +## Sessions + +[Official Documentation](https://docs.mollie.com/reference/v2/sessions-api/create-session) + +**Available Payloads:** +- `CreateSessionPayload` - For creating sessions + +**Available Queries:** +- `GetPaginatedSessionsQuery` - For listing sessions with pagination + +### Session Management + +```php +// Create a session +$session = $mollie->sessions->create([ + 'amount' => [ + 'currency' => 'EUR', + 'value' => '100.00' + ], + 'description' => 'Session for service' +]); + +// Get a session +$session = $mollie->sessions->get('sessionId'); +``` + +## Settlements + +[Official Documentation](https://docs.mollie.com/reference/v2/settlements-api/get-settlement) + +**Available Queries:** +- `GetPaginatedSettlementsQuery` - For listing settlements with pagination + +### Settlement Management + +```php +// Get a settlement +$settlement = $mollie->settlements->get('settlementId'); + +// List settlements +$settlements = $mollie->settlements->page(); +``` + +### Settlement Captures + +```php +// Get captures for a settlement +$captures = $mollie->settlementCaptures->pageFor($settlement); + +// Use iterator +foreach ($mollie->settlementCaptures->iteratorFor($settlement) as $capture) { + echo $capture->id; +} +``` + +### Settlement Chargebacks + +```php +// List chargebacks for a settlement +$chargebacks = $mollie->settlementChargebacks->pageFor($settlement); + +// Use iterator +foreach ($mollie->settlementChargebacks->iteratorFor($settlement) as $chargeback) { + echo $chargeback->id; +} +``` + +### Settlement Payments + +```php +// List payments in a settlement +$payments = $mollie->settlementPayments->pageFor($settlement); + +// Use iterator +foreach ($mollie->settlementPayments->iteratorFor($settlement) as $payment) { + echo $payment->id; +} +``` + +## Subscriptions + +[Official Documentation](https://docs.mollie.com/reference/v2/subscriptions-api/create-subscription) + +**Available Payloads:** +- `CreateSubscriptionPayload` - For creating subscriptions + +**Available Queries:** +- `GetAllPaginatedSubscriptionsQuery` - For listing all subscriptions + +### Subscription Management + +```php +// Create a subscription +$subscription = $mollie->subscriptions->createForCustomer('customerId', [ + 'amount' => [ + 'currency' => 'EUR', + 'value' => '25.00' + ], + 'interval' => '1 month', + 'description' => 'Monthly subscription' +]); + +// List subscriptions +$subscriptions = $mollie->subscriptions->pageForCustomer('customerId'); +``` + +## Terminals + +[Official Documentation](https://docs.mollie.com/reference/v2/terminals-api/get-terminal) + +**Available Queries:** +- `GetPaginatedTerminalsQuery` - For listing terminals with pagination + +### Terminal Management + +```php +// Get a terminal +$terminal = $mollie->terminals->get('terminalId'); + +// List terminals +$terminals = $mollie->terminals->page(); +``` + +## Wallets + +[Official Documentation](https://docs.mollie.com/reference/v2/wallets-api/request-apple-pay-payment-session) + +**Available Payloads:** +- `RequestApplePayPaymentSessionPayload` - For requesting Apple Pay payment sessions + +### Wallet Management + +```php +// Request an Apple Pay payment session +$session = $mollie->wallets->requestApplePayPaymentSession([ + 'domainName' => 'example.com', + 'validationUrl' => 'https://apple-pay-gateway.apple.com/paymentservices/startSession' +]); +``` + +## Common Patterns + +### Pagination + +Most list methods support pagination: + +```php +// Get first page +$payments = $mollie->payments->page(); + +// Get specific page +$payments = $mollie->payments->page( + from: 'tr_7UhSN1zuXS', // Start from this ID + limit: 50 // Items per page +); + +// Get all items using iterator +foreach ($mollie->payments->iterator() as $payment) { + echo $payment->id; +} +``` + +### Error Handling + +Handle errors using `ApiException`: + +```php +try { + $payment = $mollie->payments->get('tr_xxx'); +} catch (\Mollie\Api\Exceptions\ApiException $e) { + echo "API call failed: {$e->getMessage()}"; +} +``` + +## Common Data Types + +**Shared Payloads:** +- `Money` - For handling currency amounts +- `Address` - For handling address information +- `Metadata` - For handling custom metadata +- `DataCollection` - For handling collections of data +- `ApplicationFee` - For handling application fees + +**Base Classes:** +- `DataBag` - Base class for payload objects +- `Query` - Base class for query objects + +## Factories + +**Query Factories:** +- `PaginatedQueryFactory` - For creating paginated queries +- `SortablePaginatedQueryFactory` - For creating sortable paginated queries + +**Payload Factories:** +- `CreatePaymentPayloadFactory` - For creating payment payloads diff --git a/docs/http-adapters.md b/docs/http-adapters.md new file mode 100644 index 00000000..46d05aa2 --- /dev/null +++ b/docs/http-adapters.md @@ -0,0 +1,74 @@ +# HTTP Adapters + +## Overview + +An HTTP adapter is a component that manages the communication between your application and an API. It abstracts the details of making HTTP requests and handling responses, allowing you to use different HTTP clients (like Guzzle, cURL, or custom clients) interchangeably without changing the way you interact with the API. + +## MollieHttpAdapterPicker + +The `MollieHttpAdapterPicker` is responsible for selecting the appropriate HTTP adapter based on the environment or the provided HTTP client. If no client is specified in the `MollieApiClient` constructor, it picks a default adapter. + +### How It Works + +1. **No Client Specified**: If no client is provided, it checks if Guzzle is available and picks the appropriate version of the Guzzle adapter. +2. **Custom Client Provided**: If a custom client is provided and it implements the `HttpAdapterContract`, it is used directly. If it's a Guzzle client, it is wrapped in a `GuzzleMollieHttpAdapter`. +3. **Unrecognized Client**: Throws an `UnrecognizedClientException` if the client is not recognized. + +## Creating a Custom Adapter + +To create a custom HTTP adapter: +1. Implement HttpAdapterContract. +2. Use HasDefaultFactories Trait to simplify adapter implementation. + +```php +use Mollie\Api\Contracts\HttpAdapterContract; +use Mollie\Api\Traits\HasDefaultFactories; + +class MyCustomHttpAdapter implements HttpAdapterContract { + use HasDefaultFactories; + + public function sendRequest(PendingRequest $pendingRequest): Response { + // Implementation for sending HTTP request + } + + public function version(): ?string { + return 'my-custom-adapter/1.0'; + } +} +``` + +### Debugging + +When debugging mode is enabled, adapters remove sensitive information before they fire an exception. + +Adapters that support debugging must implement the `SupportsDebuggingContract`. This contract defines methods `enableDebugging()` and `disableDebugging()` to control debugging behavior. + +```php +use Mollie\Api\Contracts\SupportsDebuggingContract; + +class MyCustomHttpAdapter implements HttpAdapterContract, SupportsDebuggingContract { + // Implementation of debugging methods +} +``` + +## Available Adapters + +Out of the box, the Mollie API client provides several adapters: + +- **GuzzleMollieHttpAdapter**: Wraps a Guzzle HTTP client for sending requests. +- **CurlMollieHttpAdapter**: Uses cURL for sending HTTP requests. This is the default if Guzzle is not available. + +## Enabling Debugging + +Debugging can be enabled through the `HandlesDebugging` trait. This trait allows you to toggle debugging on the HTTP client, which is useful for development and troubleshooting. + +### How to Enable Debugging + +1. **Enable Debugging**: Call `enableDebugging()` on the Mollie API client instance. This sets the debugging mode on the underlying HTTP adapter, if it supports debugging. + +2. **Disable Debugging**: Call `disableDebugging()` to turn off debugging. + +```php +$mollieClient->enableDebugging(); +$mollieClient->disableDebugging(); +``` diff --git a/docs/idempotency.md b/docs/idempotency.md new file mode 100644 index 00000000..020afd3f --- /dev/null +++ b/docs/idempotency.md @@ -0,0 +1,57 @@ +# Idempotency + +## Overview + +Idempotency ensures that multiple identical requests to an API result in the same outcome without creating duplicate resources or effects. This is crucial for operations that involve financial transactions to prevent unintended charges or state changes. + +Mollie API supports idempotent requests for critical operations such as creating payments or refunds. This is automatically managed by the API client using the `ApplyIdempotencyKey` middleware. + +For more detailed information, refer to the [Mollie API Idempotency Documentation](https://docs.mollie.com/reference/api-idempotency). + +## Automatic Idempotency Key Handling + +The Mollie API client automatically handles idempotency for mutating requests (POST, PATCH, DELETE) through the `ApplyIdempotencyKey` middleware. This middleware checks if the request is a mutating type and applies an idempotency key if one is not already provided. + +### How It Works + +1. **Check Request Type**: The middleware checks if the request method is POST, PATCH, or DELETE. +2. **Apply Idempotency Key**: If the request is mutating, the middleware will: + - Use a custom idempotency key if provided. + - Otherwise, generate a key using the configured `IdempotencyKeyGenerator` if available. + - If no generator is set and no key is provided, no idempotency key is applied. + +### Customizing Idempotency Key + +You can customize how idempotency keys are generated or applied in several ways: + +- **Provide a Custom Key**: Manually set an idempotency key for a specific request. +- **Use a Custom Generator**: Implement the `IdempotencyKeyGeneratorContract` to provide a custom method of generating idempotency keys. + +#### Example: Setting a Custom Key + +```php +$mollie->setIdempotencyKey('your_custom_key_here'); +``` + +#### Example: Using a Custom Key Generator + +Implement your key generator: + +```php +class MyCustomKeyGenerator implements IdempotencyKeyGeneratorContract { + public function generate(): string { + return 'custom_' . bin2hex(random_bytes(10)); + } +} + +$mollie->setIdempotencyKeyGenerator(new MyCustomKeyGenerator()); +``` + +## Best Practices + +- **Unique Keys**: Ensure that each idempotency key is unique to each operation. Typically, UUIDs are used for this purpose. +- **Handling Errors**: Handle errors gracefully, especially when an API call with an idempotency key fails. Check the error message to understand whether you should retry the request with the same key or a new key. + +## Conclusionz + +Using idempotency keys in your API requests to Mollie can significantly enhance the reliability of your payment processing system, ensuring that payments are not unintentionally duplicated and that your application behaves predictably even in the face of network and service interruptions. diff --git a/docs/payments.md b/docs/payments.md new file mode 100644 index 00000000..2d98141d --- /dev/null +++ b/docs/payments.md @@ -0,0 +1,93 @@ +##### Multicurrency ##### +Since API v2.0 it is now possible to create non-EUR payments for your customers. +A full list of available currencies can be found [in our documentation](https://docs.mollie.com/guides/multicurrency). + +```php +$payment = $mollie->payments->create([ + "amount" => [ + "currency" => "USD", + "value" => "10.00" + ], + //... +]); +``` +_After creation, the `settlementAmount` will contain the EUR amount that will be settled on your account._ + +##### Create fully integrated iDEAL payments ##### +To fully integrate iDEAL payments on your website, follow these additional steps: + +1. Retrieve the list of issuers (banks) that support iDEAL. + +```php +$method = $mollie->methods->get(\Mollie\Api\Types\PaymentMethod::IDEAL, ["include" => "issuers"]); +``` + +Use the `$method->issuers` list to let the customer pick their preferred issuer. + +_`$method->issuers` will be a list of objects. Use the property `$id` of this object in the + API call, and the property `$name` for displaying the issuer to your customer._ + +2. Create a payment with the selected issuer: + +```php +$payment = $mollie->payments->create([ + "amount" => [ + "currency" => "EUR", + "value" => "10.00" + ], + "description" => "My first API payment", + "redirectUrl" => "https://webshop.example.org/order/12345/", + "webhookUrl" => "https://webshop.example.org/mollie-webhook/", + "method" => \Mollie\Api\Types\PaymentMethod::IDEAL, + "issuer" => $selectedIssuerId, // e.g. "ideal_INGBNL2A" +]); +``` + +_The `_links` property of the `$payment` object will contain an object `checkout` with a `href` property, which is a URL that points directly to the online banking environment of the selected issuer. +A short way of retrieving this URL can be achieved by using the `$payment->getCheckoutUrl()`._ + +For a more in-depth example, see [Example - iDEAL payment](https://github.com/mollie/mollie-api-php/blob/master/examples/payments/create-ideal-payment.php). + +#### Retrieving Payments #### +**[Retrieve Payment Documentation](https://docs.mollie.com/reference/v2/payments-api/get-payment)** + +We can use the `$payment->id` to retrieve a payment and check if the payment `isPaid`. + +```php +$payment = $mollie->payments->get($payment->id); + +if ($payment->isPaid()) +{ + echo "Payment received."; +} +``` + +Or retrieve a collection of payments. + +```php +$payments = $mollie->payments->page(); +``` + +For an extensive example of listing payments with the details and status, see [Example - List Payments](https://github.com/mollie/mollie-api-php/blob/master/examples/payments/list-payments.php). + +#### Refunding payments #### +**[Refund Payment Documentation](https://docs.mollie.com/reference/v2/refunds-api/create-payment-refund)** + +Our API provides support for refunding payments. It's important to note that there is no confirmation step, and all refunds are immediate and final. Refunds are available for all payment methods except for paysafecard and gift cards. + +```php +$payment = $mollie->payments->get($payment->id); + +// Refund € 2 of this payment +$refund = $payment->refund([ + "amount" => [ + "currency" => "EUR", + "value" => "2.00" + ] +]); +``` + +#### Payment webhook #### +When the payment status changes, the `webhookUrl` you specified during payment creation will be called. You can use the `id` from the POST parameters to check the status and take appropriate actions. For more details, refer to [Example - Webhook](https://github.com/mollie/mollie-api-php/blob/master/examples/payments/webhook.php). + +For a working example, see [Example - Refund payment](https://github.com/mollie/mollie-api-php/blob/master/examples/payments/refund-payment.php). diff --git a/docs/requests.md b/docs/requests.md new file mode 100644 index 00000000..cae956b9 --- /dev/null +++ b/docs/requests.md @@ -0,0 +1,47 @@ +# Requests + +## Overview + +The Mollie API client uses request classes to communicate with the Mollie API. Each request class handles specific API endpoints and operations. + +## Sending a Request + +To send a request using the Mollie API client, you typically need to: + +1. **Create an instance of the client**: + ```php + use Mollie\Api\MollieApiClient; + + $mollie = new MollieApiClient(); + $mollie->setApiKey('test_dHar4XY7LxsDOtmnkVtjNVWXLSlXsM'); + ``` + +2. **Create and configure the request**: + Depending on the operation, you might need to create an instance of a specific request class and configure it with necessary parameters. + +3. **Send the request**: + Use the client to send the request and handle the response. + ```php + use Mollie\Api\MollieApiClient; + use Mollie\Api\Http\Payload\Money; + use Mollie\Api\Http\Payload\CreatePaymentPayload; + use Mollie\Api\Http\Requests\CreatePaymentRequest; + + $mollie = new MollieApiClient(); + $createPaymentRequest = new CreatePaymentRequest( + new CreatePaymentPayload( + 'Test payment', + new Money('EUR', '10.00'), + 'https://example.org/redirect', + 'https://example.org/webhook' + ) + ); + + /** @var \Mollie\Api\Http\Response $response */ + $response = $mollie->send($createPaymentRequest); + + $this->assertEquals(200, $response->status()); + + /** @var \Mollie\Api\Resources\Payment */ + $payment = $response->toResource(); + ``` diff --git a/docs/responses.md b/docs/responses.md new file mode 100644 index 00000000..cd705811 --- /dev/null +++ b/docs/responses.md @@ -0,0 +1,43 @@ +# Responses + +Whether you interact with the endpoints using the traditional method (`$mollie->payments->...`) or the new `Request` classes, you can always inspect the `Response`. + +## Resource Hydration +By default, the response from the `EndpointCollection`s automatically hydrates into the corresponding `Resource` or `ResourceCollection` objects. You can still access the raw response using the `->getResponse()` method. + +```php +/** @var Mollie\Api\Resources\Payment $payment */ +$payment = $mollie->payments->get('tr_*********'); + +$response = $payment->getResponse(); +``` + +With the Request-based approach, you get a Response by default: + +```php +/** @var Mollie\Api\Http\Response $response */ +$response = $mollie->send(new GetPaymentRequest('tr_*********')); + +/** + * Accessing the response is mainly for debugging, + * like checking the status or inspecting the payload or URL. + */ +$status = $response->status(); +$sentPayload = $response->getPendingRequest()->payload; +$sentUrlWithFilters = $response->getPendingRequest()->getUri(); + +/** @var Mollie\Api\Resources\Payment $payment */ +$payment = $response->toResource(); +``` + +Thanks to the DelegatesToResource Trait in Response, you can still access methods and attributes from the underlying Resource: + +```php +// calling a method on the underlying Mollie\Api\Resources\Payment object +$response->hasSplitPayments(); + +// accessing an attribute on the underlying Mollie\Api\Resources\Payment object +$amount = $response->amount; +``` + +If you prefer the old approach of directly receiving the Resource class, you can enable **auto-hydration** by calling `MollieApiClient::setAutoHydrate()`. diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 00000000..2e3e9b95 --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,55 @@ +# Testing with Mollie API Client + +## Overview + +Version 3 of the Mollie API client refines the handling of the test mode parameter: + +- **Automatic Removal of Test Mode**: When using an API key, the test mode parameter is managed based on the key prefix (`test_` or `live_`). +- **Explicit Test Mode Control**: For operations requiring explicit control, such as when using OAuth tokens, you can still pass the `testmode` parameter. + +## Enabling Test Mode + +### Global Test Mode + +You can enable test mode globally on the Mollie client. This setting will apply test mode to all operations performed with the client. + +```php +use Mollie\Api\MollieApiClient; + +$mollie = new MollieApiClient(); +$mollie->setApiKey("test_dHar4XY7LxsDOtmnkVtjNVWXLSlXsM"); +$mollie->test(true); // Enable test mode globally +``` + +### Per Request Test Mode + +For specific control, you can enable test mode per request. This is useful for mixed-mode operations. + +```php +// Creating a payment in test mode +use Mollie\Api\MollieApiClient; +use Mollie\Api\Http\Payload\Money; +use Mollie\Api\Http\Payload\CreatePaymentPayload; +use Mollie\Api\Http\Requests\CreatePaymentRequest; + +$mollie = new MollieApiClient(); +$createPaymentRequest = new CreatePaymentRequest( + new CreatePaymentPayload( + 'Test payment', + new Money('EUR', '10.00'), + 'https://example.org/redirect', + 'https://example.org/webhook' + ) +); + +$mollie->send($createPaymentRequest->test(true)); +``` + +### Using Test Mode with Endpoint Collections + +When using endpoint collections, pass the test mode parameter directly to methods that support it. + +```php +// Fetch a customer in test mode +$customer = $mollie->customers->get('cust_12345678', testmode: true); +``` diff --git a/examples/captures/create-capture.php b/examples/captures/create-capture.php index 2ccc3a5a..580323df 100644 --- a/examples/captures/create-capture.php +++ b/examples/captures/create-capture.php @@ -4,6 +4,10 @@ * How to prepare a new payment with the Mollie API. */ +use Mollie\Api\Http\Payload\CreatePaymentCapturePayload; +use Mollie\Api\Http\Payload\Money; +use Mollie\Api\Http\Requests\CreatePaymentCaptureRequest; + try { /* * Initialize the Mollie API library with your API key. @@ -18,14 +22,12 @@ * description Description of the capture. * metadata Custom metadata that is stored with the payment. */ - $capture = $mollie - ->paymentCaptures->createForId('tr_WDqYK6vllg', [ - 'amount' => [ - 'currency' => 'EUR', - 'value' => '5.00', - ], - 'description' => 'Order #12345', - ]); + $response = $mollie->send(new CreatePaymentCaptureRequest('tr_WDqYK6vllg', new CreatePaymentCapturePayload( + 'Order #12345', + new Money('EUR', '5.00') + ))); + + $capture = $response->toResource(); echo '

New capture created '.htmlspecialchars($capture->id).' ('.htmlspecialchars($capture->description).').

'; } catch (\Mollie\Api\Exceptions\ApiException $e) { diff --git a/examples/captures/get-capture.php b/examples/captures/get-capture.php index 6978271c..0f6e076d 100644 --- a/examples/captures/get-capture.php +++ b/examples/captures/get-capture.php @@ -4,6 +4,8 @@ * Retrieve a payment capture using the Mollie API. */ +use Mollie\Api\Http\Requests\GetPaymentCaptureRequest; + try { /* * Initialize the Mollie API library with your API key or OAuth access token. @@ -17,9 +19,9 @@ * See: https://docs.mollie.com/reference/v2/captures-api/get-capture */ - $payment = $mollie->payments->get('tr_WDqYK6vllg'); - $capture = $payment->getCapture('cpt_4qqhO89gsT'); + $response = $mollie->send(new GetPaymentCaptureRequest('tr_WDqYK6vllg', 'cpt_4qqhO89gsT')); + $capture = $response->toResource(); $amount = $capture->amount->currency.' '.$capture->amount->value; echo 'Captured '.$amount; diff --git a/examples/captures/list-captures.php b/examples/captures/list-captures.php index 1bd9b7a6..5045ba1b 100644 --- a/examples/captures/list-captures.php +++ b/examples/captures/list-captures.php @@ -4,6 +4,8 @@ * List captures for a payment using the Mollie API. */ +use Mollie\Api\Http\Requests\GetPaginatedPaymentCapturesRequest; + try { /* * Initialize the Mollie API library with your API key or OAuth access token. @@ -16,13 +18,12 @@ * See: https://docs.mollie.com/reference/v2/captures-api/list-captures */ - $payment = $mollie->payments->get('tr_WDqYK6vllg'); - $captures = $payment->captures(); + $response = $mollie->send(new GetPaginatedPaymentCapturesRequest('tr_WDqYK6vllg')); - foreach ($captures as $capture) { - $amount = $capture->amount->currency.' '.$capture->amount->value; - echo 'Captured '.$amount.' for payment '.$payment->id; + foreach ($payment = $response->toResource() as $capture) { + $amount = $capture->amount->currency . ' ' . $capture->amount->value; + echo 'Captured ' . $amount . ' for payment ' . $payment->id; } } catch (\Mollie\Api\Exceptions\ApiException $e) { - echo 'API call failed: '.htmlspecialchars($e->getMessage()); + echo 'API call failed: ' . htmlspecialchars($e->getMessage()); } diff --git a/examples/client-links/create-client-link.php b/examples/client-links/create-client-link.php index ffdddc11..e4c3a9d1 100644 --- a/examples/client-links/create-client-link.php +++ b/examples/client-links/create-client-link.php @@ -4,6 +4,11 @@ * How to create a new client link in the Mollie API. */ +use Mollie\Api\Http\Payload\CreateClientLinkPayload; +use Mollie\Api\Http\Payload\Owner; +use Mollie\Api\Http\Payload\OwnerAddress; +use Mollie\Api\Http\Requests\CreateClientLinkRequest; + try { /* * Initialize the Mollie API library with your API key or OAuth access token. @@ -22,23 +27,15 @@ * * See: https://docs.mollie.com/reference/v2/client-links-api/create-client-link */ - $clientLink = $mollie->clientLinks->create([ - 'owner' => [ - 'email' => 'foo@test.com', - 'givenName' => 'foo', - 'familyName' => 'bar', - 'locale' => 'nl_NL', - ], - 'name' => 'Foo Company', - 'address' => [ - 'streetAndNumber' => 'Keizersgracht 313', - 'postalCode' => '1016 EE', - 'city' => 'Amsterdam', - 'country' => 'nl', - ], - 'registrationNumber' => '30204462', - 'vatNumber' => 'NL123456789B01', - ]); + $response = $mollie->send(new CreateClientLinkRequest(new CreateClientLinkPayload( + new Owner('foo@test.com', 'foo', 'bar', 'nl_NL'), + 'Foo Company', + new OwnerAddress('NL', 'Keizersgracht 313', '1016 EE', 'Amsterdam'), + '30204462', + 'NL123456789B01', + ))); + + $clientLink = $response->toResource(); /** * Get the redirect url for the client link, by passing in the 'client_id' of the your app, diff --git a/examples/customers/create-customer-first-payment.php b/examples/customers/create-customer-first-payment.php index ba740c32..d1b8417f 100644 --- a/examples/customers/create-customer-first-payment.php +++ b/examples/customers/create-customer-first-payment.php @@ -4,6 +4,13 @@ * How to create a first payment to allow recurring payments later. */ +use Mollie\Api\Factories\CreatePaymentPayloadFactory; +use Mollie\Api\Http\Payload\Metadata; +use Mollie\Api\Http\Payload\Money; +use Mollie\Api\Http\Requests\CreateCustomerPaymentRequest; +use Mollie\Api\Http\Requests\GetPaginatedCustomerRequest; +use Mollie\Api\Types\SequenceType; + try { /* * Initialize the Mollie API library with your API key or OAuth access token. @@ -14,7 +21,7 @@ * Retrieve the last created customer for this example. * If no customers are created yet, run the create-customer example. */ - $customer = $mollie->customers->collect(null, 1)[0]; + $customer = $mollie->send(new GetPaginatedCustomerRequest())->toResource()[0]; /* * Generate a unique order id for this example. It is important to include this unique attribute @@ -33,21 +40,20 @@ * * @See: https://docs.mollie.com/reference/v2/customers-api/create-customer-payment */ - $payment = $customer->createPayment([ - 'amount' => [ - 'value' => '10.00', // You must send the correct number of decimals, thus we enforce the use of strings - 'currency' => 'EUR', - ], + $payload = CreatePaymentPayloadFactory::new([ 'description' => "First payment - Order #{$orderId}", + 'amount' => new Money('EUR', '10.00'), 'redirectUrl' => "{$protocol}://{$hostname}/payments/return.php?order_id={$orderId}", 'webhookUrl' => "{$protocol}://{$hostname}/payments/webhook.php", - 'metadata' => [ + 'metadata' => new Metadata([ 'order_id' => $orderId, - ], + ]), + 'sequenceType' => SequenceType::FIRST, + ])->create(); - // Flag this payment as a first payment to allow recurring payments later. - 'sequenceType' => \Mollie\Api\Types\SequenceType::FIRST, - ]); + $payment = $mollie->send( + new CreateCustomerPaymentRequest($customer->id, $payload) + ); /* * In this example we store the order with its payment status in a database. @@ -61,7 +67,7 @@ * After completion, the customer will have a pending or valid mandate that can be * used for recurring payments and subscriptions. */ - header('Location: '.$payment->getCheckoutUrl(), true, 303); + header('Location: ' . $payment->getCheckoutUrl(), true, 303); } catch (\Mollie\Api\Exceptions\ApiException $e) { - echo 'API call failed: '.htmlspecialchars($e->getMessage()); + echo 'API call failed: ' . htmlspecialchars($e->getMessage()); } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 6bc67aa4..d9e52786 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -27,7 +27,7 @@ parameters: - message: '#^Variable \$mollie might not be defined\.$#' identifier: variable.undefined - count: 1 + count: 2 path: examples/customers/create-customer-first-payment.php - @@ -342,6 +342,30 @@ parameters: count: 3 path: tests/Fixtures/MockResponse.php + - + message: '#^Property Tests\\Helpers\\TestPropertiesClass\:\:\$privateProp is unused\.$#' + identifier: property.unused + count: 1 + path: tests/Helpers/HelpersTest.php + + - + message: '#^Trait Tests\\Helpers\\TestTraitBase is used zero times and is not analysed\.$#' + identifier: trait.unused + count: 1 + path: tests/Helpers/HelpersTest.php + + - + message: '#^Trait Tests\\Helpers\\TestTraitMain is used zero times and is not analysed\.$#' + identifier: trait.unused + count: 1 + path: tests/Helpers/HelpersTest.php + + - + message: '#^Trait Tests\\Helpers\\TestTraitNested is used zero times and is not analysed\.$#' + identifier: trait.unused + count: 1 + path: tests/Helpers/HelpersTest.php + - message: '#^Unsafe access to private property Tests\\Http\\Adapter\\MockMollieHttpAdapter\:\:\$factories through static\:\:\.$#' identifier: staticClassAccess.privateProperty diff --git a/src/EndpointCollection/CustomerEndpointCollection.php b/src/EndpointCollection/CustomerEndpointCollection.php index b5ca5fa9..9b0d3fe4 100644 --- a/src/EndpointCollection/CustomerEndpointCollection.php +++ b/src/EndpointCollection/CustomerEndpointCollection.php @@ -45,6 +45,7 @@ public function create($data = [], $testmode = []): Customer * * Will throw a ApiException if the customer id is invalid or the resource cannot be found. * + * @param bool|array $testmode * @throws ApiException */ public function get(string $id, $testmode = []): Customer diff --git a/src/EndpointCollection/PaymentEndpointCollection.php b/src/EndpointCollection/PaymentEndpointCollection.php index 8b64a2c0..9a87e7f3 100644 --- a/src/EndpointCollection/PaymentEndpointCollection.php +++ b/src/EndpointCollection/PaymentEndpointCollection.php @@ -50,16 +50,16 @@ public function get(string $id, $query = [], bool $testmode = false): Payment /** * Creates a payment in Mollie. * - * @param CreatePaymentPayload|array $data An array containing details on the payment. + * @param CreatePaymentPayload|array $payload An array containing details on the payment. * @param CreatePaymentQuery|array|string $query An array of strings or a single string containing the details to include. * * @throws ApiException */ - public function create($data = [], $query = [], bool $testmode = false): Payment + public function create($payload = [], $query = [], bool $testmode = false): Payment { - if (! $data instanceof CreatePaymentPayload) { - $testmode = Helpers::extractBool($data, 'testmode', $testmode); - $data = CreatePaymentPayloadFactory::new($data) + if (! $payload instanceof CreatePaymentPayload) { + $testmode = Helpers::extractBool($payload, 'testmode', $testmode); + $payload = CreatePaymentPayloadFactory::new($payload) ->create(); } @@ -68,7 +68,7 @@ public function create($data = [], $query = [], bool $testmode = false): Payment } /** @var Payment */ - return $this->send((new CreatePaymentRequest($data, $query))->test($testmode)); + return $this->send((new CreatePaymentRequest($payload, $query))->test($testmode)); } /** diff --git a/src/Factories/Factory.php b/src/Factories/Factory.php index 20decae7..6cbd7eea 100644 --- a/src/Factories/Factory.php +++ b/src/Factories/Factory.php @@ -2,20 +2,29 @@ namespace Mollie\Api\Factories; +use Mollie\Api\Contracts\Arrayable; +use Mollie\Api\Contracts\DataProvider; use Mollie\Api\Contracts\Factory as FactoryContract; use Mollie\Api\Helpers; use Mollie\Api\Helpers\Arr; +use Mollie\Api\Http\Payload\DataBag; abstract class Factory implements FactoryContract { protected array $data; - public function __construct(array $data) + public function __construct($data) { - $this->data = $data; + if ($data instanceof Arrayable) { + $this->data = $data->toArray(); + } else if ($data instanceof DataProvider) { + $this->data = $data->data(); + } else { + $this->data = $data; + } } - public static function new(array $data): self + public static function new($data): self { /** @phpstan-ignore-next-line */ return new static($data); @@ -32,7 +41,7 @@ protected function get($key, $default = null, $backupKey = 'filters.') $keys = (array) $key; if ($backupKey !== null) { - $keys[] = $backupKey.$key; + $keys[] = $backupKey . $key; } foreach ($keys as $key) { @@ -55,7 +64,7 @@ protected function has($keys): bool */ protected function includes($key, $value, $backupKey = 'filters.'): bool { - return Arr::includes($this->data, [$backupKey.$key, $key], $value); + return Arr::includes($this->data, [$backupKey . $key, $key], $value); } /** diff --git a/src/Helpers.php b/src/Helpers.php index d2379a80..8ada088e 100644 --- a/src/Helpers.php +++ b/src/Helpers.php @@ -69,13 +69,13 @@ public static function getProperties($class, $flag = ReflectionProperty::IS_PUBL public static function filterByProperties($class, array $array): array { $properties = array_map( - fn (ReflectionProperty $prop) => $prop->getName(), + fn(ReflectionProperty $prop) => $prop->getName(), static::getProperties($class) ); return array_filter( $array, - fn ($key) => ! in_array($key, $properties, true), + fn($key) => ! in_array($key, $properties, true), ARRAY_FILTER_USE_KEY ); } @@ -90,9 +90,14 @@ public static function filterByProperties($class, array $array): array */ public static function compose($value, $composable, $default = null) { - $composable = is_callable($composable) - ? $composable - : fn ($value) => new $composable($value); + // If the value is an instance of the composable class, return it. + if (is_string($composable) && $value instanceof $composable) { + return $value; + } + + $composable = is_string($composable) + ? fn($value) => new $composable($value) + : $composable; return (bool) $value ? $composable($value) : $default; } diff --git a/src/Http/Adapter/GuzzleMollieHttpAdapter.php b/src/Http/Adapter/GuzzleMollieHttpAdapter.php index c9eba669..84430140 100644 --- a/src/Http/Adapter/GuzzleMollieHttpAdapter.php +++ b/src/Http/Adapter/GuzzleMollieHttpAdapter.php @@ -9,7 +9,6 @@ use GuzzleHttp\Exception\RequestException; use GuzzleHttp\HandlerStack; use GuzzleHttp\Psr7\HttpFactory; -use GuzzleHttp\Psr7\Request; use GuzzleHttp\RequestOptions as GuzzleRequestOptions; use Mollie\Api\Contracts\HttpAdapterContract; use Mollie\Api\Contracts\SupportsDebuggingContract; @@ -133,6 +132,6 @@ protected function createResponse( */ public function version(): string { - return 'Guzzle/'.ClientInterface::MAJOR_VERSION; + return 'Guzzle/' . ClientInterface::MAJOR_VERSION; } } diff --git a/src/Http/Payload/Metadata.php b/src/Http/Payload/Metadata.php index 2fdd7b49..1b8a2e12 100644 --- a/src/Http/Payload/Metadata.php +++ b/src/Http/Payload/Metadata.php @@ -6,15 +6,15 @@ class Metadata implements DataProvider { - public array $metadata; + public array $data; - public function __construct(array $metadata) + public function __construct(array $data) { - $this->metadata = $metadata; + $this->data = $data; } public function data(): string { - return @json_encode($this->metadata); + return @json_encode($this->data); } } diff --git a/tests/Helpers/HelpersTest.php b/tests/Helpers/HelpersTest.php new file mode 100644 index 00000000..655a2a2a --- /dev/null +++ b/tests/Helpers/HelpersTest.php @@ -0,0 +1,141 @@ +assertContains(TestTrait1::class, $result); + $this->assertContains(TestTrait2::class, $result); + $this->assertContains(TestTrait3::class, $result); + } + + /** @test */ + public function trait_uses_recursive() + { + $result = Helpers::traitUsesRecursive(TestTraitMain::class); + + $this->assertContains(TestTraitBase::class, $result); + $this->assertContains(TestTraitNested::class, $result); + } + + /** @test */ + public function get_properties() + { + // Test getting all properties + $allProps = Helpers::getProperties(TestPropertiesClass::class); + $this->assertCount(3, $allProps); + $this->assertContainsOnlyInstancesOf(ReflectionProperty::class, $allProps); + + // Test getting only public properties + $publicProps = Helpers::getProperties(TestPropertiesClass::class, ReflectionProperty::IS_PUBLIC); + $this->assertCount(1, $publicProps); + $this->assertEquals('publicProp', $publicProps[0]->getName()); + } + + /** @test */ + public function filter_by_properties() + { + $array = [ + 'prop1' => 'value1', + 'prop2' => 'value2', + 'extraProp' => 'extraValue' + ]; + + $filtered = Helpers::filterByProperties(TestFilterClass::class, $array); + + $this->assertArrayHasKey('extraProp', $filtered); + $this->assertArrayNotHasKey('prop1', $filtered); + $this->assertArrayNotHasKey('prop2', $filtered); + } + + /** @test */ + public function compose() + { + // Test with callable + $composedWithCallable = Helpers::compose(5, fn($x) => $x * 2); + $this->assertEquals(10, $composedWithCallable); + + $composedWithClass = Helpers::compose('test', TestComposable::class); + $this->assertInstanceOf(TestComposable::class, $composedWithClass); + $this->assertEquals('test', $composedWithClass->value); + + // Test with falsy value + $composedWithDefault = Helpers::compose(false, fn($x) => $x * 2, 'default'); + $this->assertEquals('default', $composedWithDefault); + + $existingValueIsNotOverriden = Helpers::compose(new Metadata(['key' => 'value']), Metadata::class); + $this->assertInstanceOf(Metadata::class, $existingValueIsNotOverriden); + $this->assertEquals(['key' => 'value'], $existingValueIsNotOverriden->data); + } + + /** @test */ + public function extract_bool() + { + // Test with direct boolean + $this->assertTrue(Helpers::extractBool(true, 'key')); + $this->assertFalse(Helpers::extractBool(false, 'key')); + + // Test with array + $array = ['enabled' => true]; + $this->assertTrue(Helpers::extractBool($array, 'enabled')); + $this->assertFalse(Helpers::extractBool($array, 'nonexistent')); + + // Test with default value + $this->assertTrue(Helpers::extractBool([], 'key', true)); + } +} + +trait TestTrait1 {} +trait TestTrait2 {} +trait TestTrait3 +{ + use TestTrait1; +} + +class TestParentClass +{ + use TestTrait1; +} + +class TestChildClass extends TestParentClass +{ + use TestTrait2, TestTrait3; +} + +trait TestTraitBase {} +trait TestTraitNested +{ + use TestTraitBase; +} +trait TestTraitMain +{ + use TestTraitNested; +} + +class TestPropertiesClass +{ + public $publicProp; + protected $protectedProp; + private $privateProp; +} + +class TestFilterClass +{ + public $prop1; + public $prop2; +} + +class TestComposable +{ + public function __construct(public $value) {} +}