Add Mollie API integration and webhook for software upgrade payments

- Introduced the `CaBundle.php` class for managing CA certificates.
- Updated `installed.json` and `installed.php` to include the new `composer/ca-bundle` dependency.
- Added `platform_check.php` to enforce PHP version requirements.
- Created `initialize.php` for initializing the Mollie API client with the API key.
- Implemented `webhook_mollie.php` to handle webhook callbacks for software upgrade payments, including transaction status updates and invoice generation.
- Integrated DomPDF for generating invoices and sending them via email.
This commit is contained in:
“VeLiTi”
2025-12-21 14:44:37 +01:00
parent 653e33d7e9
commit 0f968aac14
159 changed files with 16197 additions and 21 deletions

View File

@@ -0,0 +1,194 @@
<?php
namespace Mollie\Api\Exceptions;
use DateTime;
class ApiException extends \Exception
{
/**
* @var string
*/
protected $field;
/**
* @var string
*/
protected $plainMessage;
/**
* @var \Psr\Http\Message\RequestInterface|null
*/
protected $request;
/**
* @var \Psr\Http\Message\ResponseInterface|null
*/
protected $response;
/**
* ISO8601 representation of the moment this exception was thrown
*
* @var \DateTimeImmutable
*/
protected $raisedAt;
/**
* @var array
*/
protected $links = [];
/**
* @param string $message
* @param int $code
* @param string|null $field
* @param \Psr\Http\Message\RequestInterface|null $request
* @param \Psr\Http\Message\ResponseInterface|null $response
* @param \Throwable|null $previous
* @throws \Mollie\Api\Exceptions\ApiException
*/
public function __construct($message = "", $code = 0, $field = null, $request = null, $response = null, $previous = null)
{
$this->plainMessage = $message;
$this->raisedAt = new \DateTimeImmutable();
$formattedRaisedAt = $this->raisedAt->format(\DateTime::ISO8601);
$message = "[{$formattedRaisedAt}] " . $message;
if (!empty($field)) {
$this->field = (string) $field;
$message .= ". Field: {$this->field}";
}
if (!empty($response)) {
$this->response = $response;
$object = static::parseResponseBody($this->response);
if (isset($object->_links)) {
foreach ($object->_links as $key => $value) {
$this->links[$key] = $value;
}
}
}
if ($this->hasLink('documentation')) {
$message .= ". Documentation: {$this->getDocumentationUrl()}";
}
$this->request = $request;
if ($request) {
$requestBody = $request->getBody()->__toString();
if ($requestBody) {
$message .= ". Request body: {$requestBody}";
}
}
parent::__construct($message, $code, $previous);
}
/**
* @param \Psr\Http\Message\ResponseInterface $response
* @param \Psr\Http\Message\RequestInterface $request
* @param \Throwable|null $previous
* @return \Mollie\Api\Exceptions\ApiException
* @throws \Mollie\Api\Exceptions\ApiException
*/
public static function createFromResponse($response, $request = null, $previous = null)
{
$object = static::parseResponseBody($response);
$field = null;
if (!empty($object->field)) {
$field = $object->field;
}
return new self("Error executing API call ({$object->status}: {$object->title}): {$object->detail}", $response->getStatusCode(), $field, $request, $response, $previous);
}
/**
* @return string|null
*/
public function getField()
{
return $this->field;
}
/**
* @return string|null
*/
public function getDocumentationUrl()
{
return $this->getUrl('documentation');
}
/**
* @return string|null
*/
public function getDashboardUrl()
{
return $this->getUrl('dashboard');
}
/**
* @return \Psr\Http\Message\ResponseInterface|null
*/
public function getResponse()
{
return $this->response;
}
/**
* @return bool
*/
public function hasResponse()
{
return $this->response !== null;
}
/**
* @param string $key
* @return bool
*/
public function hasLink($key)
{
return \array_key_exists($key, $this->links);
}
/**
* @param string $key
* @return mixed|null
*/
public function getLink($key)
{
if ($this->hasLink($key)) {
return $this->links[$key];
}
return null;
}
/**
* @param string $key
* @return null
*/
public function getUrl($key)
{
if ($this->hasLink($key)) {
return $this->getLink($key)->href;
}
return null;
}
/**
* @return \Psr\Http\Message\RequestInterface
*/
public function getRequest()
{
return $this->request;
}
/**
* Get the ISO8601 representation of the moment this exception was thrown
*
* @return \DateTimeImmutable
*/
public function getRaisedAt()
{
return $this->raisedAt;
}
/**
* @param \Psr\Http\Message\ResponseInterface $response
* @return \stdClass
* @throws \Mollie\Api\Exceptions\ApiException
*/
protected static function parseResponseBody($response)
{
$body = (string) $response->getBody();
$object = @\json_decode($body);
if (\json_last_error() !== \JSON_ERROR_NONE) {
throw new self("Unable to decode Mollie response: '{$body}'.");
}
return $object;
}
/**
* Retrieve the plain exception message.
*
* @return string
*/
public function getPlainMessage()
{
return $this->plainMessage;
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Mollie\Api\Exceptions;
class CurlConnectTimeoutException extends \Mollie\Api\Exceptions\ApiException
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Mollie\Api\Exceptions;
class HttpAdapterDoesNotSupportDebuggingException extends \Mollie\Api\Exceptions\ApiException
{
}

View File

@@ -0,0 +1,12 @@
<?php
namespace Mollie\Api\Exceptions;
class IncompatiblePlatform extends \Mollie\Api\Exceptions\ApiException
{
public const INCOMPATIBLE_PHP_VERSION = 1000;
public const INCOMPATIBLE_CURL_EXTENSION = 2000;
public const INCOMPATIBLE_CURL_FUNCTION = 2500;
public const INCOMPATIBLE_JSON_EXTENSION = 3000;
public const INCOMPATIBLE_RANDOM_BYTES_FUNCTION = 4000;
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Mollie\Api\Exceptions;
class UnrecognizedClientException extends \Mollie\Api\Exceptions\ApiException
{
}