diff --git a/composer.json b/composer.json index 577affa..b15a0c0 100644 --- a/composer.json +++ b/composer.json @@ -1,64 +1,64 @@ { - "name": "wcpos/sumup-terminal-for-woocommerce", - "description": "SumUp Terminal integration for WooCommerce POS.", - "type": "wordpress-plugin", - "license": "GPL-3.0+", - "authors": [ - { - "name": "kilbot", - "email": "paul@kilbot.com" - } - ], - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^1.0", - "friendsofphp/php-cs-fixer": "^3.0", - "php-stubs/woocommerce-stubs": "^10.5", - "phpcompatibility/phpcompatibility-wp": "^2.0", - "sirbrillig/phpcs-variable-analysis": "^2.0", - "squizlabs/php_codesniffer": "^3.0", - "woocommerce/woocommerce-sniffs": "^1.0", - "wp-coding-standards/wpcs": "^3.0", - "wp-phpunit/wp-phpunit": "^7.0", - "yoast/phpunit-polyfills": "^4.0" - }, - "require": { - "php": ">=7.4", - "ext-json": "*", - "sumup/sumup-ecom-php-sdk": "^1.2.0" - }, - "config": { - "platform": { - "php": "7.4" + "name": "wcpos/sumup-terminal-for-woocommerce", + "description": "SumUp Terminal integration for WooCommerce POS.", + "type": "wordpress-plugin", + "license": "GPL-3.0+", + "authors": [ + { + "name": "kilbot", + "email": "paul@kilbot.com" + } + ], + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "friendsofphp/php-cs-fixer": "^3.0", + "php-stubs/woocommerce-stubs": "^10.5", + "phpcompatibility/phpcompatibility-wp": "^2.0", + "sirbrillig/phpcs-variable-analysis": "^2.0", + "squizlabs/php_codesniffer": "^3.0", + "woocommerce/woocommerce-sniffs": "^1.0", + "wp-coding-standards/wpcs": "^3.0", + "wp-phpunit/wp-phpunit": "^7.0", + "yoast/phpunit-polyfills": "^4.0" }, - "platform-check": false, - "process-timeout": 0, - "optimize-autoloader": true, - "vendor-dir": "vendor", - "sort-packages": true, - "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "scripts": { - "format": "phpcbf --standard=./.phpcs.xml.dist --report-summary --report-source", - "lint": "phpcs --standard=./.phpcs.xml.dist", - "lint-report": "phpcs --standard=./.phpcs.xml.dist --report=checkstyle", - "fix": "php-cs-fixer fix .", - "prefix-dependencies": [ - "composer --working-dir=php-scoper install", - "cd php-scoper && vendor/bin/php-scoper add-prefix --output-dir=../vendor_prefixed --force && cd ..", - "composer dump-autoload -o", - "php generate_autoload.php" - ] - }, - "autoload": { - "psr-4": { - "WCPOS\\WooCommercePOS\\SumUpTerminal\\": "includes/" - } - }, - "autoload-dev": { - "psr-4": { - "WCPOS\\WooCommercePOS\\SumUpTerminal\\Tests\\": "tests/includes/" + "require": { + "php": ">=7.4", + "ext-json": "*" + }, + "config": { + "platform": { + "php": "7.4" + }, + "platform-check": false, + "process-timeout": 0, + "optimize-autoloader": true, + "vendor-dir": "vendor", + "sort-packages": true, + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "format": "phpcbf --standard=./.phpcs.xml.dist --report-summary --report-source", + "lint": "phpcs --standard=./.phpcs.xml.dist", + "lint-report": "phpcs --standard=./.phpcs.xml.dist --report=checkstyle", + "fix": "php-cs-fixer fix .", + "prefix-dependencies": [ + "composer --working-dir=php-scoper install --no-dev --optimize-autoloader", + "php php-scoper/vendor/bin/php-scoper add-prefix --config=php-scoper/scoper.inc.php --output-dir=vendor_prefixed/sumup-sdk --force", + "php tools/prefix-sumup-sdk-namespaces.php", + "php tools/generate-prefixed-sumup-autoload.php", + "composer dump-autoload -o" + ] + }, + "autoload": { + "psr-4": { + "WCPOS\\WooCommercePOS\\SumUpTerminal\\": "includes/" + } + }, + "autoload-dev": { + "psr-4": { + "WCPOS\\WooCommercePOS\\SumUpTerminal\\Tests\\": "tests/includes/" + } } - } } diff --git a/docs/adr/0001-sumup-sdk-hybrid.md b/docs/adr/0001-sumup-sdk-hybrid.md new file mode 100644 index 0000000..6ae6cc5 --- /dev/null +++ b/docs/adr/0001-sumup-sdk-hybrid.md @@ -0,0 +1,29 @@ +# ADR 0001: Use prefixed SumUp SDK on PHP 8.2+ with WordPress HTTP compatibility fallback + +## Status + +Accepted + +## Context + +This plugin integrates SumUp Terminal reader operations for WooCommerce. The previous Composer dependency, `sumup/sumup-ecom-php-sdk`, was unused and did not provide first-class Terminal reader operations. + +SumUp now publishes `sumup/sumup-php`, which includes reader support for listing, pairing, retrieving, deleting, checkout creation, checkout termination, and reader status. That SDK requires PHP 8.2+ and ext-curl, while this plugin continues to support WordPress sites running PHP 7.4+. + +WordPress also provides its own HTTP API, `wp_remote_request()`, which handles WordPress-specific hosting concerns such as proxies, filters, and transport selection. The official SDK uses its own cURL client; this is an intentional trade-off on PHP 8.2+ unless a future WordPress-backed SDK HTTP client is added. + +## Decision + +The plugin uses a prefixed copy of the official `sumup/sumup-php` SDK for supported Terminal reader operations when PHP 8.2+ is available and the prefixed SDK is bundled. + +On PHP 7.4-8.1, or when the prefixed SDK is unavailable, the plugin uses its WordPress HTTP compatibility client. Payments can still work normally in compatibility mode. + +The SDK is loaded through a PHP-version-guarded autoloader so older PHP runtimes do not parse PHP 8.2-only SDK files. + +## Consequences + +- Users on PHP 8.2+ get the official SDK path for supported operations. +- Users on older PHP versions keep the existing direct HTTP behavior. +- The plugin maintains two reader API transports, so behavior must remain covered by shared service-level tests or regression checks. +- Operations not exposed by the SDK, such as connect and disconnect, remain on the WordPress HTTP client. +- The settings UI explains when the compatibility client is active because the server PHP version is below 8.2. diff --git a/includes/Gateway.php b/includes/Gateway.php index 33769cd..0626dae 100644 --- a/includes/Gateway.php +++ b/includes/Gateway.php @@ -10,6 +10,7 @@ use WCPOS\WooCommercePOS\SumUpTerminal\Services\ProfileService; use WCPOS\WooCommercePOS\SumUpTerminal\Services\ReaderService; +use WCPOS\WooCommercePOS\SumUpTerminal\Services\SdkAvailability; /** * Class SumUpTerminalGateway. @@ -150,6 +151,8 @@ public static function register_gateway( $methods ) { public function admin_options(): void { parent::admin_options(); + echo wp_kses_post( $this->get_sdk_status_html() ); + // Add Connection Status section outside of form fields ?> @@ -526,6 +529,28 @@ private function get_connection_status_html() { return $html; } + /** + * Get the SDK status HTML for display. + * + * @return string HTML for SDK status. + */ + private function get_sdk_status_html() { + $type = SdkAvailability::is_sdk_available() ? 'success' : 'info'; + + $html = '
'; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= '
'; + $html .= ''; + $html .= ''; + $html .= $this->render_status_card( $type, __( 'Terminal API Client', 'sumup-terminal-for-woocommerce' ), SdkAvailability::get_status_message() ); + $html .= '
'; + + return $html; + } + /** * Render a status card. * diff --git a/includes/Services/ReaderApiClientFactory.php b/includes/Services/ReaderApiClientFactory.php new file mode 100644 index 0000000..35539c3 --- /dev/null +++ b/includes/Services/ReaderApiClientFactory.php @@ -0,0 +1,22 @@ +profile_service = $profile_service; + public function __construct( $api_key = '', ?ReaderApiClientInterface $client = null ) { + $this->client = $client ? $client : ReaderApiClientFactory::create( $api_key ); } /** - * Get all readers for the merchant. + * Set the profile service for lazy loading merchant ID. * - * @return array|false Readers data or false on failure. + * @param ProfileService $profile_service Profile service instance. */ - public function get_all() { - if ( ! $this->ensure_merchant_id() ) { - return false; - } + public function set_profile_service( ProfileService $profile_service ): void { + $this->client->set_profile_service( $profile_service ); + } - $result = parent::get( "/merchants/{$this->get_merchant_id()}/readers" ); - - if ( $result ) { - // Check if response has 'items' structure - if ( isset( $result['items'] ) && \is_array( $result['items'] ) ) { - return $result['items']; - } + public function get_all() { + return $this->client->get_all(); + } - return $result; - } + public function get_reader( $reader_id ) { + return $this->client->get_reader( $reader_id ); + } - return false; + public function create( array $data ) { + return $this->client->create( $data ); } - /** - * Get a specific reader by ID. - * - * @param string $reader_id Reader ID. - * - * @return array|false Reader data or false on failure. - */ - public function get_reader( $reader_id ) { - if ( ! $this->ensure_merchant_id() ) { - return false; - } + public function destroy( $reader_id ) { + return $this->client->destroy( $reader_id ); + } - return parent::get( "/merchants/{$this->get_merchant_id()}/readers/{$reader_id}" ); + public function checkout( $reader_id, $checkout_data ) { + return $this->client->checkout( $reader_id, $checkout_data ); } - /** - * Create/register a new reader. - * - * @param array $data Reader data. - * - * @return array|false Reader data or false on failure. - */ - public function create( array $data ) { - if ( ! $this->ensure_merchant_id() ) { - return false; - } + public function cancel_checkout( $reader_id ) { + return $this->client->cancel_checkout( $reader_id ); + } - return parent::post( "/merchants/{$this->get_merchant_id()}/readers", $data ); + public function get_status( $reader_id ) { + return $this->client->get_status( $reader_id ); } - /** - * Delete/unregister a reader. - * - * @param string $reader_id Reader ID. - * - * @return bool True on success, false on failure. - */ - public function destroy( $reader_id ) { - if ( ! $this->ensure_merchant_id() ) { - return false; - } + public function connect( $reader_id ) { + return $this->client->connect( $reader_id ); + } - $response = parent::delete( "/merchants/{$this->get_merchant_id()}/readers/{$reader_id}" ); + public function disconnect( $reader_id ) { + return $this->client->disconnect( $reader_id ); + } - return $response && ( $response['success'] ?? true ); + public function set_merchant_id( $merchant_id ): void { + $this->client->set_merchant_id( $merchant_id ); } - /** - * Initiate a checkout on a specific reader. - * - * @param string $reader_id Reader ID. - * @param array $checkout_data Checkout data. - * - * @return array|false Checkout response or false on failure. - */ - public function checkout( $reader_id, $checkout_data ) { - if ( ! $this->ensure_merchant_id() ) { - return false; - } + public function get_merchant_id() { + return $this->client->get_merchant_id(); + } - return parent::post( - "/merchants/{$this->get_merchant_id()}/readers/{$reader_id}/checkout", - $checkout_data - ); + public function has_api_key() { + return $this->client->has_api_key(); } /** @@ -125,7 +93,7 @@ public function checkout( $reader_id, $checkout_data ) { * @return array|false Checkout data or false on failure. */ public function create_checkout_for_order( $order, $reader_id ) { - if ( ! $this->has_api_key() || ! $this->ensure_merchant_id() ) { + if ( ! $this->client->has_api_key() ) { return false; } @@ -153,125 +121,30 @@ public function create_checkout_for_order( $order, $reader_id ) { // Construct webhook URL using WordPress AJAX with user-independent token and order ID $webhook_token = $this->generate_webhook_token( $order->get_id() ); - - $checkout_data['return_url'] = add_query_arg( array( - 'action' => 'sumup_webhook', - 'nonce' => $webhook_token, - 'order_id' => $order->get_id(), - ), admin_url( 'admin-ajax.php' ) ); + + $checkout_data['return_url'] = add_query_arg( + array( + 'action' => 'sumup_webhook', + 'nonce' => $webhook_token, + 'order_id' => $order->get_id(), + ), + admin_url( 'admin-ajax.php' ) + ); $result = $this->checkout( $reader_id, $checkout_data ); - + // If successful, save the transaction ID to the order if ( $result && isset( $result['data']['client_transaction_id'] ) ) { $transaction_id = $result['data']['client_transaction_id']; $order->set_transaction_id( $transaction_id ); $order->save(); - + Logger::log( 'SumUp transaction ID saved: ' . $transaction_id . ' for order: ' . $order->get_id() ); } return $result; } - /** - * Cancel/terminate a checkout on a reader. - * - * Note: This is an asynchronous operation. The API only confirms the terminate - * request was accepted, but actual termination may take time. If successful, - * a webhook will be sent to the return_url with status "FAILED". - * - * @param string $reader_id Reader ID. - * - * @return bool True if terminate request was accepted, false if rejected. - */ - public function cancel_checkout( $reader_id ) { - if ( ! $this->ensure_merchant_id() ) { - return false; - } - - // According to SumUp API docs, terminate action is sent to the reader - // This is asynchronous - no confirmation of actual termination - // No request body is required for terminate - $response = parent::post( "/merchants/{$this->get_merchant_id()}/readers/{$reader_id}/terminate" ); - - // SumUp API returns HTTP 204 (no content) for terminate requests - // We consider it successful if the request was sent (no network error) - // The actual termination result will come via webhook to return_url - return ! is_wp_error( $response ); - } - - /** - * Get the status of a reader. - * - * @param string $reader_id Reader ID. - * - * @return array|false Reader status or false on failure. - */ - public function get_status( $reader_id ) { - if ( ! $this->ensure_merchant_id() ) { - return false; - } - - return parent::get( "/merchants/{$this->get_merchant_id()}/readers/{$reader_id}/status" ); - } - - /** - * Connect to a reader. - * - * @param string $reader_id Reader ID. - * - * @return array|false Connection response or false on failure. - */ - public function connect( $reader_id ) { - if ( ! $this->ensure_merchant_id() ) { - return false; - } - - return parent::post( "/merchants/{$this->get_merchant_id()}/readers/{$reader_id}/connect" ); - } - - /** - * Disconnect from a reader. - * - * @param string $reader_id Reader ID. - * - * @return bool True on success, false on failure. - */ - public function disconnect( $reader_id ) { - if ( ! $this->ensure_merchant_id() ) { - return false; - } - - $response = parent::post( "/merchants/{$this->get_merchant_id()}/readers/{$reader_id}/disconnect" ); - - return $response && ( $response['success'] ?? true ); - } - - /** - * Get merchant ID, fetching it lazily if not set. - * - * @return null|string Merchant ID or null if unavailable. - */ - private function ensure_merchant_id() { - // If merchant ID is already set, return it - if ( $this->get_merchant_id() ) { - return $this->get_merchant_id(); - } - - // Try to fetch it from the profile service - if ( $this->profile_service ) { - $merchant_code = $this->profile_service->get_merchant_code(); - if ( $merchant_code ) { - $this->set_merchant_id( $merchant_code ); - - return $merchant_code; - } - } - - return null; - } - /** * Generate a webhook token for a specific order. * This is user-independent and suitable for external webhook validation. @@ -284,7 +157,7 @@ private function generate_webhook_token( $order_id ) { // Create a deterministic token based on order ID and WordPress salts // This is user-independent but specific to this WordPress installation $data = 'sumup_webhook_' . $order_id . wp_salt( 'nonce' ); - + return substr( wp_hash( $data ), 0, 10 ); } } diff --git a/includes/Services/SdkAvailability.php b/includes/Services/SdkAvailability.php new file mode 100644 index 0000000..06ac23a --- /dev/null +++ b/includes/Services/SdkAvailability.php @@ -0,0 +1,39 @@ += self::MINIMUM_PHP_VERSION_ID; + } + + public static function is_sdk_available(): bool { + return self::is_php_version_supported() && class_exists( self::PREFIXED_SUMUP_CLASS ); + } + + public static function get_status_message(): string { + if ( self::is_sdk_available() ) { + return __( 'This site is using the official SumUp PHP SDK for supported Terminal API operations.', 'sumup-terminal-for-woocommerce' ); + } + + if ( ! self::is_php_version_supported() ) { + return sprintf( + /* translators: 1: current PHP version. */ + __( 'Your server is running PHP %1$s. The official SumUp PHP SDK requires PHP 8.2 or newer, so this plugin is using its WordPress HTTP compatibility client. Payments can still work normally. Upgrade to PHP 8.2+ to use the official SumUp SDK integration.', 'sumup-terminal-for-woocommerce' ), + PHP_VERSION + ); + } + + return __( 'The official SumUp PHP SDK is not bundled in this build, so this plugin is using its WordPress HTTP compatibility client. Payments can still work normally.', 'sumup-terminal-for-woocommerce' ); + } +} diff --git a/includes/Services/SdkReaderApiClient.php b/includes/Services/SdkReaderApiClient.php new file mode 100644 index 0000000..a56a684 --- /dev/null +++ b/includes/Services/SdkReaderApiClient.php @@ -0,0 +1,276 @@ +api_key = $api_key; + $this->fallback = $fallback; + } + + public function set_profile_service( ProfileService $profile_service ): void { + $this->profile_service = $profile_service; + $this->fallback->set_profile_service( $profile_service ); + } + + public function set_merchant_id( $merchant_id ): void { + $this->merchant_id = $merchant_id; + $this->fallback->set_merchant_id( $merchant_id ); + } + + public function get_merchant_id() { + return $this->merchant_id ? $this->merchant_id : $this->fallback->get_merchant_id(); + } + + public function has_api_key() { + return ! empty( $this->api_key ); + } + + public function get_all() { + $merchant_id = $this->ensure_merchant_id(); + if ( ! $merchant_id ) { + return false; + } + + return $this->sdk_call( + 'list readers', + function () use ( $merchant_id ) { + $response = $this->get_readers_service()->list( $merchant_id ); + $items = $response->items ?? array(); + + return array_map( array( $this, 'normalize_reader' ), $items ); + }, + function () { + return $this->fallback->get_all(); + } + ); + } + + public function get_reader( $reader_id ) { + $merchant_id = $this->ensure_merchant_id(); + if ( ! $merchant_id ) { + return false; + } + + return $this->sdk_call( + 'get reader', + function () use ( $merchant_id, $reader_id ) { + return $this->normalize_reader( $this->get_readers_service()->get( $merchant_id, $reader_id ) ); + }, + function () use ( $reader_id ) { + return $this->fallback->get_reader( $reader_id ); + } + ); + } + + public function create( array $data ) { + $merchant_id = $this->ensure_merchant_id(); + if ( ! $merchant_id ) { + return false; + } + + return $this->sdk_call( + 'create reader', + function () use ( $merchant_id, $data ) { + return $this->normalize_reader( $this->get_readers_service()->create( $merchant_id, $data ) ); + }, + function () use ( $data ) { + return $this->fallback->create( $data ); + } + ); + } + + public function destroy( $reader_id ) { + $merchant_id = $this->ensure_merchant_id(); + if ( ! $merchant_id ) { + return false; + } + + return $this->sdk_call( + 'delete reader', + function () use ( $merchant_id, $reader_id ) { + $this->get_readers_service()->delete( $merchant_id, $reader_id ); + + return true; + }, + function () use ( $reader_id ) { + return $this->fallback->destroy( $reader_id ); + } + ); + } + + public function checkout( $reader_id, $checkout_data ) { + $merchant_id = $this->ensure_merchant_id(); + if ( ! $merchant_id ) { + return false; + } + + return $this->sdk_call( + 'create reader checkout', + function () use ( $merchant_id, $reader_id, $checkout_data ) { + return $this->normalize_checkout_response( $this->get_readers_service()->createCheckout( $merchant_id, $reader_id, $checkout_data ) ); + }, + function () use ( $reader_id, $checkout_data ) { + return $this->fallback->checkout( $reader_id, $checkout_data ); + } + ); + } + + public function cancel_checkout( $reader_id ) { + $merchant_id = $this->ensure_merchant_id(); + if ( ! $merchant_id ) { + return false; + } + + return $this->sdk_call( + 'terminate reader checkout', + function () use ( $merchant_id, $reader_id ) { + $this->get_readers_service()->terminateCheckout( $merchant_id, $reader_id ); + + return true; + }, + function () use ( $reader_id ) { + return $this->fallback->cancel_checkout( $reader_id ); + } + ); + } + + public function get_status( $reader_id ) { + $merchant_id = $this->ensure_merchant_id(); + if ( ! $merchant_id ) { + return false; + } + + return $this->sdk_call( + 'get reader status', + function () use ( $merchant_id, $reader_id ) { + return $this->normalize_status( $this->get_readers_service()->getStatus( $merchant_id, $reader_id ) ); + }, + function () use ( $reader_id ) { + return $this->fallback->get_status( $reader_id ); + } + ); + } + + public function connect( $reader_id ) { + return $this->fallback->connect( $reader_id ); + } + + public function disconnect( $reader_id ) { + return $this->fallback->disconnect( $reader_id ); + } + + private function ensure_merchant_id() { + if ( $this->merchant_id ) { + return $this->merchant_id; + } + + if ( $this->profile_service ) { + $merchant_code = $this->profile_service->get_merchant_code(); + if ( $merchant_code ) { + $this->set_merchant_id( $merchant_code ); + + return $merchant_code; + } + } + + return null; + } + + private function get_sdk() { + if ( $this->sdk ) { + return $this->sdk; + } + + $class = SdkAvailability::PREFIXED_SUMUP_CLASS; + $this->sdk = new $class( $this->api_key ); + + return $this->sdk; + } + + private function get_readers_service() { + $sdk = $this->get_sdk(); + + return $sdk->readers(); + } + + private function normalize_reader( $reader ) { + if ( is_array( $reader ) ) { + return $reader; + } + + return array( + 'id' => $reader->id ?? null, + 'name' => $reader->name ?? null, + 'status' => $this->enum_value( $reader->status ?? null ), + 'created_at' => $reader->createdAt ?? $reader->created_at ?? null, + 'device' => array( + 'model' => $this->enum_value( $reader->device->model ?? null ), + 'identifier' => $reader->device->identifier ?? null, + ), + ); + } + + private function normalize_status( $status ) { + return json_decode( wp_json_encode( $status ), true ); + } + + private function normalize_checkout_response( $response ) { + return $this->snake_case_array_keys( json_decode( wp_json_encode( $response ), true ) ); + } + + private function enum_value( $value ) { + if ( is_object( $value ) && $value instanceof \BackedEnum ) { + return $value->value; + } + + if ( is_object( $value ) && property_exists( $value, 'value' ) ) { + return $value->value; + } + + return $value; + } + + private function snake_case_array_keys( $value ) { + if ( ! is_array( $value ) ) { + return $value; + } + + $normalized = array(); + + foreach ( $value as $key => $item ) { + if ( is_string( $key ) ) { + $key = strtolower( preg_replace( '/(?snake_case_array_keys( $item ); + } + + return $normalized; + } + + private function sdk_call( $operation, callable $callback, callable $fallback ) { + try { + return $callback(); + } catch ( \Throwable $e ) { + Logger::log( 'SumUp SDK ' . $operation . ' failed; falling back to WordPress HTTP client: ' . $e->getMessage() ); + + return $fallback(); + } + } +} diff --git a/includes/Services/WordPressHttpReaderApiClient.php b/includes/Services/WordPressHttpReaderApiClient.php new file mode 100644 index 0000000..9ab970f --- /dev/null +++ b/includes/Services/WordPressHttpReaderApiClient.php @@ -0,0 +1,216 @@ +profile_service = $profile_service; + } + + /** + * Get all readers for the merchant. + * + * @return array|false Readers data or false on failure. + */ + public function get_all() { + if ( ! $this->ensure_merchant_id() ) { + return false; + } + + $result = parent::get( "/merchants/{$this->get_merchant_id()}/readers" ); + + if ( $result ) { + // Check if response has 'items' structure + if ( isset( $result['items'] ) && \is_array( $result['items'] ) ) { + return $result['items']; + } + + return $result; + } + + return false; + } + + /** + * Get a specific reader by ID. + * + * @param string $reader_id Reader ID. + * + * @return array|false Reader data or false on failure. + */ + public function get_reader( $reader_id ) { + if ( ! $this->ensure_merchant_id() ) { + return false; + } + + return parent::get( "/merchants/{$this->get_merchant_id()}/readers/{$reader_id}" ); + } + + /** + * Create/register a new reader. + * + * @param array $data Reader data. + * + * @return array|false Reader data or false on failure. + */ + public function create( array $data ) { + if ( ! $this->ensure_merchant_id() ) { + return false; + } + + return parent::post( "/merchants/{$this->get_merchant_id()}/readers", $data ); + } + + /** + * Delete/unregister a reader. + * + * @param string $reader_id Reader ID. + * + * @return bool True on success, false on failure. + */ + public function destroy( $reader_id ) { + if ( ! $this->ensure_merchant_id() ) { + return false; + } + + $response = parent::delete( "/merchants/{$this->get_merchant_id()}/readers/{$reader_id}" ); + + return $response && ( $response['success'] ?? true ); + } + + /** + * Initiate a checkout on a specific reader. + * + * @param string $reader_id Reader ID. + * @param array $checkout_data Checkout data. + * + * @return array|false Checkout response or false on failure. + */ + public function checkout( $reader_id, $checkout_data ) { + if ( ! $this->ensure_merchant_id() ) { + return false; + } + + return parent::post( + "/merchants/{$this->get_merchant_id()}/readers/{$reader_id}/checkout", + $checkout_data + ); + } + + /** + * Cancel/terminate a checkout on a reader. + * + * Note: This is an asynchronous operation. The API only confirms the terminate + * request was accepted, but actual termination may take time. If successful, + * a webhook will be sent to the return_url with status "FAILED". + * + * @param string $reader_id Reader ID. + * + * @return bool True if terminate request was accepted, false if rejected. + */ + public function cancel_checkout( $reader_id ) { + if ( ! $this->ensure_merchant_id() ) { + return false; + } + + // According to SumUp API docs, terminate action is sent to the reader + // This is asynchronous - no confirmation of actual termination + // No request body is required for terminate + $response = parent::post( "/merchants/{$this->get_merchant_id()}/readers/{$reader_id}/terminate" ); + + // SumUp API returns HTTP 204 (no content) for terminate requests + // We consider it successful if the request was sent (no network error) + // The actual termination result will come via webhook to return_url + if ( false === $response || is_wp_error( $response ) ) { + return false; + } + + return true; + } + + /** + * Get the status of a reader. + * + * @param string $reader_id Reader ID. + * + * @return array|false Reader status or false on failure. + */ + public function get_status( $reader_id ) { + if ( ! $this->ensure_merchant_id() ) { + return false; + } + + return parent::get( "/merchants/{$this->get_merchant_id()}/readers/{$reader_id}/status" ); + } + + /** + * Connect to a reader. + * + * @param string $reader_id Reader ID. + * + * @return array|false Connection response or false on failure. + */ + public function connect( $reader_id ) { + if ( ! $this->ensure_merchant_id() ) { + return false; + } + + return parent::post( "/merchants/{$this->get_merchant_id()}/readers/{$reader_id}/connect" ); + } + + /** + * Disconnect from a reader. + * + * @param string $reader_id Reader ID. + * + * @return bool True on success, false on failure. + */ + public function disconnect( $reader_id ) { + if ( ! $this->ensure_merchant_id() ) { + return false; + } + + $response = parent::post( "/merchants/{$this->get_merchant_id()}/readers/{$reader_id}/disconnect" ); + + return $response && ( $response['success'] ?? true ); + } + + /** + * Get merchant ID, fetching it lazily if not set. + * + * @return null|string Merchant ID or null if unavailable. + */ + private function ensure_merchant_id() { + // If merchant ID is already set, return it + if ( $this->get_merchant_id() ) { + return $this->get_merchant_id(); + } + + // Try to fetch it from the profile service + if ( $this->profile_service ) { + $merchant_code = $this->profile_service->get_merchant_code(); + if ( $merchant_code ) { + $this->set_merchant_id( $merchant_code ); + + return $merchant_code; + } + } + + return null; + } +} diff --git a/php-scoper/composer.json b/php-scoper/composer.json new file mode 100644 index 0000000..a69ced8 --- /dev/null +++ b/php-scoper/composer.json @@ -0,0 +1,15 @@ +{ + "name": "wcpos/sumup-terminal-for-woocommerce-scoper", + "private": true, + "type": "project", + "require": { + "humbug/php-scoper": "^0.18", + "sumup/sumup-php": "0.1.4" + }, + "config": { + "platform": { + "php": "8.2.0" + }, + "allow-plugins": {} + } +} diff --git a/php-scoper/scoper.inc.php b/php-scoper/scoper.inc.php new file mode 100644 index 0000000..c2901a7 --- /dev/null +++ b/php-scoper/scoper.inc.php @@ -0,0 +1,15 @@ + 'WCPOS\\WooCommercePOS\\SumUpTerminal\\Vendor\\SumUpSdk', + 'finders' => [ + Finder::create() + ->files() + ->in(__DIR__ . '/vendor/sumup/sumup-php/src'), + ], + 'patchers' => [], +]; diff --git a/sumup-terminal-for-woocommerce.php b/sumup-terminal-for-woocommerce.php index 1c80a45..8f4d753 100644 --- a/sumup-terminal-for-woocommerce.php +++ b/sumup-terminal-for-woocommerce.php @@ -24,6 +24,8 @@ \define( 'SUTWC_VERSION', '0.0.8' ); \define( 'SUTWC_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); \define( 'SUTWC_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); +\define( 'SUTWC_MINIMUM_PHP_VERSION', '7.4' ); +\define( 'SUTWC_MINIMUM_PHP_VERSION_ID', 70400 ); // Include Composer's autoloader. if ( file_exists( SUTWC_PLUGIN_DIR . 'vendor/autoload.php' ) ) { @@ -32,6 +34,13 @@ Logger::log( 'SumUp Terminal for WooCommerce: Composer autoloader not found.' ); } +// Include the prefixed official SumUp SDK only on PHP versions that can parse it. +// This runtime guard must happen before requiring the SDK autoloader. +$sutwc_prefixed_sumup_autoload = SUTWC_PLUGIN_DIR . 'vendor_prefixed/sumup-sdk-autoload.php'; +if ( PHP_VERSION_ID >= 80200 && file_exists( $sutwc_prefixed_sumup_autoload ) ) { + require_once $sutwc_prefixed_sumup_autoload; +} + // Autoload classes using PSR-4. spl_autoload_register( function ( $class ): void { @@ -52,6 +61,31 @@ function ( $class ): void { } ); +/** + * Validate runtime requirements during plugin activation. + */ +function sutwc_activate(): void { + if ( PHP_VERSION_ID >= SUTWC_MINIMUM_PHP_VERSION_ID ) { + return; + } + + deactivate_plugins( plugin_basename( __FILE__ ) ); + + wp_die( + esc_html( + sprintf( + /* translators: 1: required PHP version, 2: current PHP version. */ + __( 'SumUp Terminal for WooCommerce requires PHP %1$s or newer. Your server is running PHP %2$s.', 'sumup-terminal-for-woocommerce' ), + SUTWC_MINIMUM_PHP_VERSION, + PHP_VERSION + ) + ), + esc_html__( 'Plugin activation failed', 'sumup-terminal-for-woocommerce' ), + array( 'back_link' => true ) + ); +} +register_activation_hook( __FILE__, __NAMESPACE__ . '\\sutwc_activate' ); + /** * Initialize the plugin. */ diff --git a/tests/regression/activation-guard.php b/tests/regression/activation-guard.php new file mode 100644 index 0000000..85ee219 --- /dev/null +++ b/tests/regression/activation-guard.php @@ -0,0 +1,23 @@ += SUTWC_MINIMUM_PHP_VERSION_ID') === false) { + fwrite(STDERR, "Activation guard does not perform the supported-runtime early return.\n"); + exit(1); +} + +echo "PASS: activation guard blocks unsupported plugin runtimes cleanly.\n"; diff --git a/tests/regression/prefixed-sdk-autoload-is-conditional.php b/tests/regression/prefixed-sdk-autoload-is-conditional.php new file mode 100644 index 0000000..cacef40 --- /dev/null +++ b/tests/regression/prefixed-sdk-autoload-is-conditional.php @@ -0,0 +1,21 @@ += 80200") === false) { + fwrite(STDERR, "Prefixed SDK autoloader is not guarded by PHP_VERSION_ID >= 80200.\n"); + exit(1); +} + +echo "PASS: prefixed SDK autoload is conditional.\n"; diff --git a/tests/regression/sdk-compatibility-message.php b/tests/regression/sdk-compatibility-message.php new file mode 100644 index 0000000..a960f66 --- /dev/null +++ b/tests/regression/sdk-compatibility-message.php @@ -0,0 +1,29 @@ +get_sdk_status_html()') === false) { + fwrite(STDERR, "Gateway does not render SDK status from admin_options().\n"); + exit(1); +} + +echo "PASS: SDK compatibility message is present.\n"; diff --git a/tests/regression/sdk-reader-normalization.php b/tests/regression/sdk-reader-normalization.php new file mode 100644 index 0000000..32a58db --- /dev/null +++ b/tests/regression/sdk-reader-normalization.php @@ -0,0 +1,84 @@ +newInstanceWithoutConstructor(); +$method = $reflection->getMethod('normalize_reader'); +if (PHP_VERSION_ID < 80100) { + $method->setAccessible(true); +} + +$reader = new stdClass(); +$reader->id = 'reader-123'; +$reader->name = 'Front Desk'; +$reader->status = 'paired'; +$reader->createdAt = '2026-05-26T10:00:00Z'; +$reader->device = new stdClass(); +$reader->device->model = 'solo'; +$reader->device->identifier = 'SN123'; + +$result = $method->invoke($client, $reader); + +$expected = array( + 'id' => 'reader-123', + 'name' => 'Front Desk', + 'status' => 'paired', + 'created_at' => '2026-05-26T10:00:00Z', + 'device' => array( + 'model' => 'solo', + 'identifier' => 'SN123', + ), +); + +if ($result !== $expected) { + fwrite(STDERR, "SDK reader normalization did not preserve the existing array shape.\n"); + fwrite(STDERR, var_export($result, true) . "\n"); + exit(1); +} + +echo "PASS: SDK reader normalization matches existing array shape.\n"; + +$checkoutMethod = $reflection->getMethod('normalize_checkout_response'); +if (PHP_VERSION_ID < 80100) { + $checkoutMethod->setAccessible(true); +} + +$checkout = (object) array( + 'data' => (object) array( + 'clientTransactionId' => 'txn-123', + 'totalAmount' => (object) array( + 'minorUnit' => 2, + ), + ), +); + +$checkoutResult = $checkoutMethod->invoke($client, $checkout); + +$expectedCheckout = array( + 'data' => array( + 'client_transaction_id' => 'txn-123', + 'total_amount' => array( + 'minor_unit' => 2, + ), + ), +); + +if ($checkoutResult !== $expectedCheckout) { + fwrite(STDERR, "SDK checkout normalization did not convert response keys to snake_case.\n"); + fwrite(STDERR, var_export($checkoutResult, true) . "\n"); + exit(1); +} + +echo "PASS: SDK checkout normalization matches existing array shape.\n"; diff --git a/tools/generate-prefixed-sumup-autoload.php b/tools/generate-prefixed-sumup-autoload.php new file mode 100644 index 0000000..ef531a0 --- /dev/null +++ b/tools/generate-prefixed-sumup-autoload.php @@ -0,0 +1,81 @@ +isFile() || $file->getExtension() !== 'php') { + continue; + } + + $path = $file->getPathname(); + $contents = file_get_contents($path); + if ($contents === false) { + fwrite(STDERR, "Failed to read file: {$path}\n"); + exit(1); + } + + $replaced = preg_replace('/namespace\\s+SumUp\\b/', 'namespace ' . $prefix, $contents); + if ($replaced === null || preg_last_error() !== PREG_NO_ERROR) { + fwrite(STDERR, "Regex namespace replacement failed for file: {$path}\n"); + exit(1); + } + $contents = $replaced; + + $replaced = preg_replace('/use\\s+SumUp\\\\/', 'use ' . str_replace('\\', '\\\\', $prefix) . '\\\\', $contents); + if ($replaced === null || preg_last_error() !== PREG_NO_ERROR) { + fwrite(STDERR, "Regex use replacement failed for file: {$path}\n"); + exit(1); + } + $contents = $replaced; + + $replaced = preg_replace('/(? $context, + 'target' => $target, + ], self::class, $this); + } + + /** + * Create request DTO from an associative array. + * + * @param array $data + */ + public static function fromArray(array $data): self + { + self::assertRequiredFields($data, [ + 'context' => 'context', + 'target' => 'target', + ]); + + $request = (new \ReflectionClass(self::class))->newInstanceWithoutConstructor(); + \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Hydrator::hydrate($data, self::class, $request); + + return $request; + } + + /** + * @param array $data + * @param array $requiredFields + */ + private static function assertRequiredFields(array $data, array $requiredFields): void + { + foreach ($requiredFields as $serializedName => $propertyName) { + if (!array_key_exists($serializedName, $data) && !array_key_exists($propertyName, $data)) { + throw new \InvalidArgumentException(sprintf('Missing required field "%s".', $serializedName)); + } + } + } + +} + +class CheckoutsListAvailablePaymentMethodsResponse +{ + /** + * + * @var CheckoutsListAvailablePaymentMethodsResponseItem[]|null + */ + public ?array $availablePaymentMethods = null; + +} + +class CheckoutsListAvailablePaymentMethodsResponseItem +{ + /** + * The ID of the payment method. + * + * @var string + */ + public string $id; + +} + +/** + * Query parameters for CheckoutsListParams. + * + * @package SumUp\Services + */ +class CheckoutsListParams +{ + /** + * Filters the list of checkout resources by the unique ID of the checkout. + * + * @var string|null + */ + public ?string $checkoutReference = null; + +} + +/** + * Query parameters for CheckoutsListAvailablePaymentMethodsParams. + * + * @package SumUp\Services + */ +class CheckoutsListAvailablePaymentMethodsParams +{ + /** + * The amount for which the payment methods should be eligible, in major units. + * + * @var float|null + */ + public ?float $amount = null; + + /** + * The currency for which the payment methods should be eligible. + * + * @var string|null + */ + public ?string $currency = null; + +} + +/** + * Class Checkouts + * + * Checkouts represent online payment sessions that you create before attempting to charge a payer. A checkout captures the payment intent, such as the amount, currency, merchant, and optional customer or redirect settings, and then moves through its lifecycle as you process it. + * + * Use this tag to: + * - create a checkout before collecting or confirming payment details + * - process the checkout with a card, saved card, wallet, or supported alternative payment method + * - retrieve or list checkouts to inspect their current state and associated payment attempts + * - deactivate a checkout that should no longer be used + * + * Typical workflow: + * - create a checkout with the order amount, currency, and merchant information + * - process the checkout through SumUp client tools such as the [Payment Widget and Swift Checkout SDK](https://developer.sumup.com/online-payments/checkouts) + * - retrieve the checkout or use the Transactions endpoints to inspect the resulting payment record + * + * Checkouts are used to initiate and orchestrate online payments. Transactions remain the authoritative record of the resulting payment outcome. + * + * @package SumUp\Services + */ +class Checkouts implements SumUpService +{ + /** + * The client for the http communication. + * + * @var HttpClientInterface + */ + protected HttpClientInterface $client; + + /** + * The access token needed for authentication for the services. + * + * @var string + */ + protected string $accessToken; + + /** + * Checkouts constructor. + * + * @param HttpClientInterface $client + * @param string $accessToken + */ + public function __construct(HttpClientInterface $client, string $accessToken) + { + $this->client = $client; + $this->accessToken = $accessToken; + } + + /** + * Create a checkout + * + * @param \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\CheckoutCreateRequest|array $body Required request payload + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Checkout + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function create(\WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\CheckoutCreateRequest|array $body, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Checkout + { + $path = '/v0.1/checkouts'; + $payload = []; + $requestBody = $body; + if (is_array($requestBody)) { + $requestBody = \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\CheckoutCreateRequest::fromArray($requestBody); + } + $payload = RequestEncoder::encode($requestBody); + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('POST', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, [ + '201' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Checkout::class], + ], [ + '400' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\ErrorExtended::class], + '401' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '403' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\ErrorForbidden::class], + '409' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Error::class], + ], 'POST', $path); + } + + /** + * Create an Apple Pay session + * + * @param string $id Unique ID of the checkout resource. + * @param CheckoutsCreateApplePaySessionRequest|array|null $body Optional request payload + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return array + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function createApplePaySession(string $id, CheckoutsCreateApplePaySessionRequest|array|null $body = null, ?RequestOptions $requestOptions = null): array + { + $path = sprintf('/v0.2/checkouts/%s/apple-pay-session', rawurlencode((string) $id)); + $payload = []; + if ($body !== null) { + $requestBody = $body; + if (is_array($requestBody)) { + $requestBody = CheckoutsCreateApplePaySessionRequest::fromArray($requestBody); + } + $payload = RequestEncoder::encode($requestBody); + } + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('PUT', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, [ + '200' => ['type' => 'object'], + ], [ + '400' => ['type' => 'mixed'], + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Error::class], + ], 'PUT', $path); + } + + /** + * Deactivate a checkout + * + * @param string $id Unique ID of the checkout resource. + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Checkout + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function deactivate(string $id, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Checkout + { + $path = sprintf('/v0.1/checkouts/%s', rawurlencode((string) $id)); + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('DELETE', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Checkout::class, [ + '401' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Error::class], + '409' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Error::class], + ], 'DELETE', $path); + } + + /** + * Retrieve a checkout + * + * @param string $id Unique ID of the checkout resource. + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\CheckoutSuccess + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function get(string $id, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\CheckoutSuccess + { + $path = sprintf('/v0.1/checkouts/%s', rawurlencode((string) $id)); + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('GET', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\CheckoutSuccess::class, [ + '401' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Error::class], + ], 'GET', $path); + } + + /** + * List checkouts + * + * @param CheckoutsListParams|null $queryParams Optional query string parameters + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\CheckoutSuccess[] + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function list(?CheckoutsListParams $queryParams = null, ?RequestOptions $requestOptions = null): array + { + $path = '/v0.1/checkouts'; + if ($queryParams !== null) { + $queryParamsData = []; + if (isset($queryParams->checkoutReference)) { + $queryParamsData['checkout_reference'] = $queryParams->checkoutReference; + } + if (!empty($queryParamsData)) { + $queryString = http_build_query($queryParamsData); + if (!empty($queryString)) { + $path .= '?' . $queryString; + } + } + } + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('GET', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, [ + '200' => ['type' => 'array', 'items' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\CheckoutSuccess::class]], + ], [ + '401' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'GET', $path); + } + + /** + * Get available payment methods + * + * @param string $merchantCode The SumUp merchant code. + * @param CheckoutsListAvailablePaymentMethodsParams|null $queryParams Optional query string parameters + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Services\CheckoutsListAvailablePaymentMethodsResponse + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function listAvailablePaymentMethods(string $merchantCode, ?CheckoutsListAvailablePaymentMethodsParams $queryParams = null, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Services\CheckoutsListAvailablePaymentMethodsResponse + { + $path = sprintf('/v0.1/merchants/%s/payment-methods', rawurlencode((string) $merchantCode)); + if ($queryParams !== null) { + $queryParamsData = []; + if (isset($queryParams->amount)) { + $queryParamsData['amount'] = $queryParams->amount; + } + if (isset($queryParams->currency)) { + $queryParamsData['currency'] = $queryParams->currency; + } + if (!empty($queryParamsData)) { + $queryString = http_build_query($queryParamsData); + if (!empty($queryString)) { + $path .= '?' . $queryString; + } + } + } + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('GET', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Services\CheckoutsListAvailablePaymentMethodsResponse::class, [ + '400' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\DetailsError::class], + ], 'GET', $path); + } + + /** + * Process a checkout + * + * @param string $id Unique ID of the checkout resource. + * @param \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\ProcessCheckout|array $body Required request payload + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\CheckoutSuccess|\WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\CheckoutAccepted + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function process(string $id, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\ProcessCheckout|array $body, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\CheckoutSuccess|\WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\CheckoutAccepted + { + $path = sprintf('/v0.1/checkouts/%s', rawurlencode((string) $id)); + $payload = []; + $requestBody = $body; + if (is_array($requestBody)) { + $requestBody = \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\ProcessCheckout::fromArray($requestBody); + } + $payload = RequestEncoder::encode($requestBody); + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('PUT', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, [ + '200' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\CheckoutSuccess::class], + '202' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\CheckoutAccepted::class], + ], [ + '400' => ['type' => 'mixed'], + '401' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Error::class], + '409' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Error::class], + ], 'PUT', $path); + } +} diff --git a/vendor_prefixed/sumup-sdk/Customers/Customers.php b/vendor_prefixed/sumup-sdk/Customers/Customers.php new file mode 100644 index 0000000..0098a70 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Customers/Customers.php @@ -0,0 +1,245 @@ + $personalDetails, + ], self::class, $this); + } + + /** + * Create request DTO from an associative array. + * + * @param array $data + */ + public static function fromArray(array $data): self + { + $request = (new \ReflectionClass(self::class))->newInstanceWithoutConstructor(); + \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Hydrator::hydrate($data, self::class, $request); + + return $request; + } + +} + +/** + * Class Customers + * + * Allow your regular customers to save their information with the Customers model. + * + * This will prevent re-entering payment instrument information for recurring payments on your platform. + * + * Depending on the needs you can allow, creating, listing or deactivating payment instruments & creating, retrieving and updating customers. + * + * @package SumUp\Services + */ +class Customers implements SumUpService +{ + /** + * The client for the http communication. + * + * @var HttpClientInterface + */ + protected HttpClientInterface $client; + + /** + * The access token needed for authentication for the services. + * + * @var string + */ + protected string $accessToken; + + /** + * Customers constructor. + * + * @param HttpClientInterface $client + * @param string $accessToken + */ + public function __construct(HttpClientInterface $client, string $accessToken) + { + $this->client = $client; + $this->accessToken = $accessToken; + } + + /** + * Create a customer + * + * @param \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Customer|array $body Required request payload + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Customer + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function create(\WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Customer|array $body, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Customer + { + $path = '/v0.1/customers'; + $payload = []; + $requestBody = $body; + if (is_array($requestBody)) { + $requestBody = \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Customer::fromArray($requestBody); + } + $payload = RequestEncoder::encode($requestBody); + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('POST', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, [ + '201' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Customer::class], + ], [ + '400' => ['type' => 'mixed'], + '401' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '403' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\ErrorForbidden::class], + '409' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Error::class], + ], 'POST', $path); + } + + /** + * Deactivate a payment instrument + * + * @param string $customerId Unique ID of the saved customer resource. + * @param string $token Unique token identifying the card saved as a payment instrument resource. + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return null + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function deactivatePaymentInstrument(string $customerId, string $token, ?RequestOptions $requestOptions = null): null + { + $path = sprintf('/v0.1/customers/%s/payment-instruments/%s', rawurlencode((string) $customerId), rawurlencode((string) $token)); + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('DELETE', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, [ + '204' => ['type' => 'void'], + ], [ + '400' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Error::class], + '401' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '403' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\ErrorForbidden::class], + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Error::class], + ], 'DELETE', $path); + } + + /** + * Retrieve a customer + * + * @param string $customerId Unique ID of the saved customer resource. + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Customer + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function get(string $customerId, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Customer + { + $path = sprintf('/v0.1/customers/%s', rawurlencode((string) $customerId)); + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('GET', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Customer::class, [ + '401' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '403' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\ErrorForbidden::class], + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Error::class], + ], 'GET', $path); + } + + /** + * List payment instruments + * + * @param string $customerId Unique ID of the saved customer resource. + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\PaymentInstrumentResponse[] + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function listPaymentInstruments(string $customerId, ?RequestOptions $requestOptions = null): array + { + $path = sprintf('/v0.1/customers/%s/payment-instruments', rawurlencode((string) $customerId)); + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('GET', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, [ + '200' => ['type' => 'array', 'items' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\PaymentInstrumentResponse::class]], + ], [ + '401' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '403' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\ErrorForbidden::class], + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Error::class], + ], 'GET', $path); + } + + /** + * Update a customer + * + * @param string $customerId Unique ID of the saved customer resource. + * @param CustomersUpdateRequest|array $body Required request payload + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Customer + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function update(string $customerId, CustomersUpdateRequest|array $body, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Customer + { + $path = sprintf('/v0.1/customers/%s', rawurlencode((string) $customerId)); + $payload = []; + $requestBody = $body; + if (is_array($requestBody)) { + $requestBody = CustomersUpdateRequest::fromArray($requestBody); + } + $payload = RequestEncoder::encode($requestBody); + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('PUT', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Customer::class, [ + '401' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '403' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\ErrorForbidden::class], + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Error::class], + ], 'PUT', $path); + } +} diff --git a/vendor_prefixed/sumup-sdk/Exception/ApiException.php b/vendor_prefixed/sumup-sdk/Exception/ApiException.php new file mode 100644 index 0000000..0aacef8 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Exception/ApiException.php @@ -0,0 +1,42 @@ +httpMethod = $httpMethod; + $this->path = $path; + } + + public function getHttpMethod(): ?string + { + return $this->httpMethod; + } + + public function getPath(): ?string + { + return $this->path; + } +} diff --git a/vendor_prefixed/sumup-sdk/Exception/ArgumentException.php b/vendor_prefixed/sumup-sdk/Exception/ArgumentException.php new file mode 100644 index 0000000..a2ebaac --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Exception/ArgumentException.php @@ -0,0 +1,12 @@ +> + */ + private array $headers; + + /** + * @param array> $headers + */ + public function __construct(int $status, string $message, mixed $raw = null, array $headers = []) + { + $this->status = $status; + $this->message = $message; + $this->raw = $raw; + $this->headers = $headers; + } + + public function getStatus(): int + { + return $this->status; + } + + public function getMessage(): string + { + return $this->message; + } + + public function getRaw(): mixed + { + return $this->raw; + } + + /** + * @return array> + */ + public function getHeaders(): array + { + return $this->headers; + } + + /** + * @return array>|mixed> + */ + public function toArray(): array + { + return [ + 'status' => $this->status, + 'message' => $this->message, + 'raw' => $this->raw, + 'headers' => $this->headers, + ]; + } +} diff --git a/vendor_prefixed/sumup-sdk/Exception/SDKException.php b/vendor_prefixed/sumup-sdk/Exception/SDKException.php new file mode 100644 index 0000000..0de7486 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Exception/SDKException.php @@ -0,0 +1,63 @@ +statusCode = $statusCode; + $this->responseBody = $responseBody; + } + + /** + * Returns the HTTP status code provided by the API, or 0 when absent. + * + * @return int + */ + public function getStatusCode(): int + { + return $this->statusCode; + } + + /** + * Returns the decoded response body or the raw string payload. + * + * @return mixed + */ + public function getResponseBody(): mixed + { + return $this->responseBody; + } +} diff --git a/vendor_prefixed/sumup-sdk/Exception/UnexpectedApiException.php b/vendor_prefixed/sumup-sdk/Exception/UnexpectedApiException.php new file mode 100644 index 0000000..9ed8f6b --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Exception/UnexpectedApiException.php @@ -0,0 +1,84 @@ +|null $headers + */ + public function __construct( + string $message = '', + int $statusCode = 0, + mixed $responseBody = null, + ?string $httpMethod = null, + ?string $path = null, + ?array $headers = null, + ?string $rawResponseBody = null, + ?\Throwable $previous = null + ) { + parent::__construct($message, $statusCode, $responseBody, $httpMethod, $path, $previous); + $this->rawResponseBody = $rawResponseBody; + $this->errorEnvelope = new ErrorEnvelope( + $statusCode, + $message, + $rawResponseBody ?? $responseBody, + self::normalizeHeaders($headers) + ); + } + + public function getErrorEnvelope(): ErrorEnvelope + { + return $this->errorEnvelope; + } + + public function getRawResponseBody(): ?string + { + return $this->rawResponseBody; + } + + /** + * @param array|null $headers + * + * @return array> + */ + private static function normalizeHeaders(?array $headers): array + { + if ($headers === null) { + return []; + } + + $normalized = []; + foreach ($headers as $name => $value) { + if ($name == '') { + continue; + } + + if (is_array($value)) { + $items = []; + foreach ($value as $item) { + if (is_scalar($item) || (is_object($item) && method_exists($item, '__toString'))) { + $items[] = (string) $item; + } + } + if (!empty($items)) { + $normalized[$name] = $items; + } + continue; + } + + if (is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) { + $normalized[$name] = [(string) $value]; + } + } + + return $normalized; + } +} diff --git a/vendor_prefixed/sumup-sdk/ExceptionMessages.php b/vendor_prefixed/sumup-sdk/ExceptionMessages.php new file mode 100644 index 0000000..3dadd96 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/ExceptionMessages.php @@ -0,0 +1,23 @@ + + */ + private array $customHeaders; + + /** + * The CA bundle path used to verify HTTPS calls. + * + * @var string|null + */ + private $caBundlePath; + + /** + * CurlClient constructor. + * + * @param string $baseUrl + * @param array $customHeaders + * @param string|null $caBundlePath + */ + public function __construct(string $baseUrl, array $customHeaders = [], ?string $caBundlePath = null) + { + $this->baseUrl = $baseUrl; + $this->customHeaders = $customHeaders; + $this->caBundlePath = $this->normalizeCABundlePath($caBundlePath); + if ($this->caBundlePath === null) { + $this->caBundlePath = $this->getDefaultCABundlePath(); + } + } + + /** + * @param string $method The request method. + * @param string $url The endpoint to send the request to. + * @param array $body The body of the request. + * @param array $headers The headers of the request. + * @param RequestOptions|null $options Optional typed request options. + * + * @return Response + * + * @throws ConnectionException + * @throws SDKException + */ + public function send(string $method, string $url, array $body, array $headers = [], ?RequestOptions $options = null): Response + { + if ($method === '') { + throw new SDKException('Request method cannot be empty.'); + } + + $requestUrl = $this->baseUrl . $url; + if ($requestUrl === '') { + throw new SDKException('Request URL cannot be empty.'); + } + + $reqHeaders = array_merge($headers, $this->customHeaders); + $retries = $options !== null ? ($options->retries ?? 0) : 0; + $backoffMs = $options !== null ? ($options->retryBackoffMs ?? 0) : 0; + + $attempt = 0; + do { + $ch = curl_init(); + if ($ch === false) { + throw new ConnectionException('Failed to initialize cURL handle.', 0); + } + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_URL, $requestUrl); + curl_setopt($ch, CURLOPT_HTTPHEADER, $this->formatHeaders($reqHeaders)); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, true); + if (!empty($body)) { + $payload = json_encode($body); + if (!is_string($payload)) { + throw new SDKException('Failed to encode request body to JSON.'); + } + curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); + } + + if (!empty($this->caBundlePath)) { + curl_setopt($ch, CURLOPT_CAINFO, $this->caBundlePath); + } + + if ($options?->timeout !== null) { + curl_setopt($ch, CURLOPT_TIMEOUT, $options->timeout); + } + + if ($options?->connectTimeout !== null) { + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $options->connectTimeout); + } + + $response = curl_exec($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + $error = curl_error($ch); + if ($error) { + $this->closeHandle($ch); + if ($attempt < $retries) { + $this->sleepBackoff($backoffMs, $attempt); + $attempt++; + continue; + } + throw new ConnectionException($error, $code); + } + + $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + + $this->closeHandle($ch); + if ($code >= 500 && $attempt < $retries) { + $this->sleepBackoff($backoffMs, $attempt); + $attempt++; + continue; + } + + if (!is_string($response)) { + throw new ConnectionException('Unexpected empty response body from cURL request.', $code); + } + + $rawHeaders = ''; + $rawBody = $response; + if ($headerSize > 0) { + $parsedHeaders = substr($response, 0, $headerSize); + $parsedBody = substr($response, $headerSize); + $rawHeaders = $parsedHeaders; + $rawBody = $parsedBody; + } + + return new Response( + $code, + $this->parseBody($rawBody), + $this->parseHeaders($rawHeaders), + $rawBody + ); + } while (true); + } + + /** + * Format the headers to be compatible with cURL. + * + * @param array|null $headers + * + * @return array + */ + private function formatHeaders(?array $headers = null): array + { + if (empty($headers)) { + return []; + } + + $keys = array_keys($headers); + $formattedHeaders = []; + foreach ($keys as $key) { + $formattedHeaders[] = $key . ': ' . $headers[$key]; + } + return $formattedHeaders; + } + + /** + * Returns JSON encoded the response's body if it is of JSON type. + * + * @param $response + * + * @return mixed + */ + private function parseBody(string $response) + { + $jsonBody = json_decode($response, true); + if (isset($jsonBody)) { + return $jsonBody; + } + return $response; + } + + /** + * Parse raw HTTP header text into normalized header map. + * + * @param string $rawHeaders + * + * @return array> + */ + private function parseHeaders(string $rawHeaders): array + { + if ($rawHeaders === '') { + return []; + } + + $headers = []; + $lines = preg_split("/\r\n|\n|\r/", $rawHeaders); + if (!is_array($lines)) { + return []; + } + + foreach ($lines as $line) { + if ($line === '' || strpos($line, ':') === false) { + continue; + } + + $parts = explode(':', $line, 2); + if (count($parts) !== 2) { + continue; + } + + $name = trim($parts[0]); + $value = trim($parts[1]); + if ($name === '' || $value === '') { + continue; + } + + if (!isset($headers[$name])) { + $headers[$name] = []; + } + $headers[$name][] = $value; + } + + return $headers; + } + + /** + * Close the cURL handle. + */ + private function closeHandle(\CurlHandle $handle): void + { + curl_close($handle); + } + + /** + * @param int $backoffMs + * @param int $attempt + */ + private function sleepBackoff(int $backoffMs, int $attempt): void + { + if ($backoffMs <= 0) { + return; + } + + $delay = $backoffMs * (int) pow(2, $attempt); + usleep($delay * 1000); + } + + /** + * Normalize and validate the CA bundle path. + * + * @param string|null $caBundlePath + * + * @return string|null + * + * @throws ConfigurationException + */ + private function normalizeCABundlePath(?string $caBundlePath): ?string + { + if ($caBundlePath === null || $caBundlePath === '') { + return null; + } + + if (!is_readable($caBundlePath)) { + throw new ConfigurationException(sprintf('The provided ca_bundle_path "%s" is not readable.', $caBundlePath)); + } + + return $caBundlePath; + } + + /** + * Returns the path to the CA bundle shipped with the SDK, if present. + * + * @return string|null + */ + private function getDefaultCABundlePath(): ?string + { + $path = realpath(__DIR__ . '/../../resources/ca-bundle.crt'); + if ($path && is_readable($path)) { + return $path; + } + + return null; + } +} diff --git a/vendor_prefixed/sumup-sdk/HttpClient/GuzzleClient.php b/vendor_prefixed/sumup-sdk/HttpClient/GuzzleClient.php new file mode 100644 index 0000000..f2bd7a7 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/HttpClient/GuzzleClient.php @@ -0,0 +1,181 @@ + + */ + private array $customHeaders; + + /** + * @var string|null + */ + private $caBundlePath; + + /** + * GuzzleClient constructor. + * + * @param string $baseUrl + * @param array $customHeaders + * @param string|null $caBundlePath + * + * @throws ConfigurationException + */ + public function __construct(string $baseUrl, array $customHeaders = [], ?string $caBundlePath = null) + { + $this->ensureGuzzleInstalled(); + + $this->baseUrl = $baseUrl; + $this->customHeaders = $customHeaders; + $this->caBundlePath = $caBundlePath; + } + + /** + * @param string $method The request method. + * @param string $url The endpoint to send the request to. + * @param array $body The body of the request. + * @param array $headers The headers of the request. + * @param RequestOptions|null $options Optional typed request options. + * + * @return Response + * + * @throws ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function send(string $method, string $url, array $body, array $headers, ?RequestOptions $options = null): Response + { + $this->ensureGuzzleInstalled(); + + $reqHeaders = array_merge($headers, $this->customHeaders); + $retries = $options !== null ? ($options->retries ?? 0) : 0; + $backoffMs = $options !== null ? ($options->retryBackoffMs ?? 0) : 0; + + $handler = \GuzzleHttp\HandlerStack::create(); + if ($retries > 0) { + $handler->push(\GuzzleHttp\Middleware::retry( + function ($retry, $request, $response = null, $exception = null) use ($retries) { + if ($retry >= $retries) { + return false; + } + if ($exception !== null) { + return true; + } + if ($response && $response->getStatusCode() >= 500) { + return true; + } + return false; + }, + function ($retry) use ($backoffMs) { + if ($backoffMs <= 0) { + return 0; + } + return (int) ($backoffMs * pow(2, $retry)); + } + )); + } + + $client = new \GuzzleHttp\Client([ + 'base_uri' => $this->baseUrl, + 'handler' => $handler, + 'http_errors' => false, + 'verify' => $this->caBundlePath ?: true, + ]); + + $requestParams = ['headers' => $reqHeaders]; + + if (!empty($body)) { + $requestParams['json'] = $body; + } + + if ($options?->timeout !== null) { + $requestParams['timeout'] = $options->timeout; + } + + if ($options?->connectTimeout !== null) { + $requestParams['connect_timeout'] = $options->connectTimeout; + } + + try { + $response = $client->request($method, $url, $requestParams); + } catch (\GuzzleHttp\Exception\RequestException $exception) { + if ($exception->hasResponse()) { + $response = $exception->getResponse(); + } else { + throw new ConnectionException($exception->getMessage(), $exception->getCode()); + } + } + + $statusCode = $response->getStatusCode(); + $responseBody = (string) $response->getBody(); + $parsedBody = $this->parseBody($responseBody); + $headers = $this->normalizeHeaders($response->getHeaders()); + + return new Response($statusCode, $parsedBody, $headers, $responseBody); + } + + /** + * @param string $response + * + * @return mixed + */ + private function parseBody(string $response) + { + $jsonBody = json_decode($response, true); + if (isset($jsonBody)) { + return $jsonBody; + } + return $response; + } + + /** + * @param array> $headers + * + * @return array> + */ + private function normalizeHeaders(array $headers): array + { + $normalized = []; + foreach ($headers as $name => $values) { + if ($name === '') { + continue; + } + + $items = []; + foreach ($values as $value) { + if ($value !== '') { + $items[] = $value; + } + } + if (!empty($items)) { + $normalized[$name] = $items; + } + } + + return $normalized; + } + + /** + * @throws ConfigurationException + */ + private function ensureGuzzleInstalled(): void + { + if (!class_exists('\\GuzzleHttp\\Client')) { + throw new ConfigurationException( + 'Guzzle is not installed. Run `composer require guzzlehttp/guzzle` to use WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\\HttpClient\\GuzzleClient.' + ); + } + } +} diff --git a/vendor_prefixed/sumup-sdk/HttpClient/HttpClientInterface.php b/vendor_prefixed/sumup-sdk/HttpClient/HttpClientInterface.php new file mode 100644 index 0000000..25ebee5 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/HttpClient/HttpClientInterface.php @@ -0,0 +1,22 @@ + $body The body of the request. + * @param array $headers The headers of the request. + * @param RequestOptions|null $options Optional typed request options. + * + * @return Response + */ + public function send(string $method, string $url, array $body, array $headers, ?RequestOptions $options = null): Response; +} diff --git a/vendor_prefixed/sumup-sdk/HttpClient/RequestHeaders.php b/vendor_prefixed/sumup-sdk/HttpClient/RequestHeaders.php new file mode 100644 index 0000000..78fecfc --- /dev/null +++ b/vendor_prefixed/sumup-sdk/HttpClient/RequestHeaders.php @@ -0,0 +1,36 @@ + $headers + * + * @return array + */ + public static function build(?string $accessToken = null, ?RequestOptions $options = null, array $headers = []): array + { + $requestHeaders = array_merge([ + 'Content-Type' => 'application/json', + 'User-Agent' => SdkInfo::getUserAgent(), + ], SdkInfo::getRuntimeHeaders(), $headers); + + if (!empty($accessToken)) { + $requestHeaders['Authorization'] = 'Bearer ' . $accessToken; + } + + if ($options !== null && !empty($options->headers)) { + $requestHeaders = array_merge($requestHeaders, $options->headers); + } + + return $requestHeaders; + } +} diff --git a/vendor_prefixed/sumup-sdk/HttpClient/RequestOptions.php b/vendor_prefixed/sumup-sdk/HttpClient/RequestOptions.php new file mode 100644 index 0000000..8eeeeeb --- /dev/null +++ b/vendor_prefixed/sumup-sdk/HttpClient/RequestOptions.php @@ -0,0 +1,61 @@ + + */ + public array $headers = []; + + /** + * Total request timeout in seconds. + * + * @var int|null + */ + public ?int $timeout = null; + + /** + * Connection timeout in seconds. + * + * @var int|null + */ + public ?int $connectTimeout = null; + + /** + * Number of retry attempts for transient failures. + * + * @var int|null + */ + public ?int $retries = null; + + /** + * Base retry backoff in milliseconds. + * + * @var int|null + */ + public ?int $retryBackoffMs = null; + + /** + * @param array $headers + */ + public function __construct( + ?int $timeout = null, + ?int $connectTimeout = null, + ?int $retries = null, + ?int $retryBackoffMs = null, + array $headers = [] + ) { + $this->timeout = $timeout; + $this->connectTimeout = $connectTimeout; + $this->retries = $retries; + $this->retryBackoffMs = $retryBackoffMs; + $this->headers = $headers; + } +} diff --git a/vendor_prefixed/sumup-sdk/HttpClient/Response.php b/vendor_prefixed/sumup-sdk/HttpClient/Response.php new file mode 100644 index 0000000..c9693d6 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/HttpClient/Response.php @@ -0,0 +1,101 @@ +> + */ + protected array $headers; + + /** + * Raw response body before parsing, when available. + * + * @var string|null + */ + protected ?string $rawBody; + + /** + * Response constructor. + * + * @param int $httpResponseCode + * @param mixed $body + * @param array> $headers + * @param string|null $rawBody + * + */ + public function __construct( + int $httpResponseCode, + mixed $body, + array $headers = [], + ?string $rawBody = null + ) { + $this->httpResponseCode = $httpResponseCode; + $this->body = $body; + $this->headers = $headers; + $this->rawBody = $rawBody; + } + + /** + * Get HTTP response code. + * + * @return int + */ + public function getHttpResponseCode(): int + { + return $this->httpResponseCode; + } + + /** + * Get the response body. + * + * @return array|mixed + */ + public function getBody(): mixed + { + return $this->body; + } + + /** + * Get normalized response headers. + * + * @return array> + */ + public function getHeaders(): array + { + return $this->headers; + } + + /** + * Get raw response body before JSON decoding, when available. + * + * @return string|null + */ + public function getRawBody(): ?string + { + return $this->rawBody; + } + +} diff --git a/vendor_prefixed/sumup-sdk/Hydrator.php b/vendor_prefixed/sumup-sdk/Hydrator.php new file mode 100644 index 0000000..e17314d --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Hydrator.php @@ -0,0 +1,329 @@ +> + */ + private static $propertyCache = []; + + /** + * Hydrate the provided payload into the given class. + * + * @param mixed $payload + * @param string $className + * @param object|null $target Existing instance to hydrate. + * + * @return mixed + */ + public static function hydrate($payload, $className, $target = null) + { + if ($payload === null || $className === '' || !class_exists($className)) { + return $payload; + } + + if ($payload instanceof $className) { + return $payload; + } + + if (!is_array($payload)) { + return $payload; + } + + $object = ($target instanceof $className) + ? $target + : (new ReflectionClass($className))->newInstanceWithoutConstructor(); + $properties = self::getClassProperties($className); + + foreach ($payload as $key => $value) { + $propertyName = self::normalizePropertyName($key); + if (!isset($properties[$propertyName])) { + continue; + } + + $property = $properties[$propertyName]; + $property->setValue($object, self::castValue($value, $property)); + } + + return $object; + } + + /** + * @param string $className + * + * @return array + */ + private static function getClassProperties($className) + { + if (isset(self::$propertyCache[$className])) { + return self::$propertyCache[$className]; + } + + if (!class_exists($className)) { + return []; + } + + $refClass = new ReflectionClass($className); + $properties = []; + foreach ($refClass->getProperties(ReflectionProperty::IS_PUBLIC) as $property) { + $properties[$property->getName()] = $property; + } + + self::$propertyCache[$className] = $properties; + + return $properties; + } + + /** + * @param mixed $value + * @param ReflectionProperty $property + * + * @return mixed + */ + private static function castValue($value, ReflectionProperty $property) + { + if ($value === null) { + return null; + } + + $type = $property->getType(); + if (!$type instanceof ReflectionNamedType) { + return $value; + } + + $typeName = $type->getName(); + if ($type->isBuiltin()) { + switch ($typeName) { + case 'int': + return (int) $value; + case 'float': + return (float) $value; + case 'bool': + return (bool) $value; + case 'string': + return (string) $value; + case 'array': + return self::castArrayValue($value, $property); + default: + return $value; + } + } + + if (enum_exists($typeName)) { + return self::castEnumValue($value, $typeName); + } + + return self::hydrate($value, $typeName); + } + + /** + * @param mixed $value + * @param string $enumClass + * + * @return mixed + */ + private static function castEnumValue($value, $enumClass) + { + if ($value instanceof $enumClass) { + return $value; + } + + if (method_exists($enumClass, 'tryFrom')) { + $enum = $enumClass::tryFrom($value); + if ($enum !== null) { + return $enum; + } + } + + if (method_exists($enumClass, 'from')) { + return $enumClass::from($value); + } + + return $value; + } + + /** + * @param mixed $value + * @param ReflectionProperty $property + * + * @return array + */ + private static function castArrayValue($value, ReflectionProperty $property) + { + if (!is_array($value)) { + return []; + } + + $itemType = self::extractArrayItemType($property); + if ($itemType === null) { + return $value; + } + + $result = []; + foreach ($value as $key => $item) { + $result[$key] = self::castArrayItem($item, $itemType, $property); + } + + return $result; + } + + /** + * @param ReflectionProperty $property + * + * @return string|null + */ + private static function extractArrayItemType(ReflectionProperty $property) + { + $docComment = $property->getDocComment(); + if ($docComment === false) { + return null; + } + + if (!preg_match('/@var\s+([^\s]+)/', $docComment, $matches)) { + return null; + } + + $types = explode('|', $matches[1]); + foreach ($types as $type) { + $type = trim($type); + if ($type === '' || $type === 'null') { + continue; + } + if (substr($type, -2) === '[]') { + return substr($type, 0, -2); + } + } + + return null; + } + + /** + * @param mixed $item + * @param string $itemType + * @param ReflectionProperty $property + * + * @return mixed + */ + private static function castArrayItem($item, $itemType, ReflectionProperty $property) + { + $normalizedType = ltrim($itemType, '\\'); + switch ($normalizedType) { + case 'string': + return (string) $item; + case 'int': + return (int) $item; + case 'float': + return (float) $item; + case 'bool': + return (bool) $item; + case 'array': + return is_array($item) ? $item : []; + case 'mixed': + return $item; + } + + $className = $itemType; + if ($itemType[0] !== '\\') { + $namespace = $property->getDeclaringClass()->getNamespaceName(); + if (!empty($namespace)) { + $className = $namespace . '\\' . $itemType; + } else { + $className = $itemType; + } + } else { + $className = ltrim($itemType, '\\'); + } + + if (!class_exists($className)) { + return $item; + } + + return self::hydrate($item, $className); + } + + /** + * Normalize serialized property names into PHP property names. + * + * @param string $name + * + * @return string + */ + private static function normalizePropertyName($name) + { + $value = trim((string) $name); + $value = str_replace('[]', 'List', $value); + $value = str_replace(['.', '-', ' '], '_', $value); + if ($value === '') { + $value = 'field'; + } + + $value = self::toLowerCamel($value); + if (self::isReservedWord($value)) { + $value .= 'Value'; + } + + return $value; + } + + /** + * Convert strings to lower camelCase. + * + * @param string $value + * + * @return string + */ + private static function toLowerCamel($value) + { + $value = str_replace('_', ' ', $value); + $value = ucwords($value); + $value = str_replace(' ', '', $value); + + return lcfirst($value); + } + + /** + * @param string $word + * + * @return bool + */ + private static function isReservedWord($word) + { + static $reserved = [ + 'abstract' => true, + 'array' => true, + 'callable' => true, + 'class' => true, + 'const' => true, + 'default' => true, + 'function' => true, + 'global' => true, + 'interface' => true, + 'new' => true, + 'private' => true, + 'protected' => true, + 'public' => true, + 'static' => true, + 'string' => true, + 'int' => true, + 'float' => true, + 'bool' => true, + 'self' => true, + 'parent' => true, + 'trait' => true, + 'namespace' => true, + ]; + + return isset($reserved[$word]); + } +} diff --git a/vendor_prefixed/sumup-sdk/Members/Members.php b/vendor_prefixed/sumup-sdk/Members/Members.php new file mode 100644 index 0000000..f872836 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Members/Members.php @@ -0,0 +1,505 @@ +|null + */ + public ?array $metadata = null; + + /** + * Object attributes that are modifiable only by SumUp applications. + * + * @var array|null + */ + public ?array $attributes = null; + + /** + * Create request DTO. + * + * @param string $email + * @param string[] $roles + * @param bool|null $isManagedUser + * @param string|null $password + * @param string|null $nickname + * @param array|null $metadata + * @param array|null $attributes + */ + public function __construct( + string $email, + array $roles, + ?bool $isManagedUser = null, + ?string $password = null, + ?string $nickname = null, + ?array $metadata = null, + ?array $attributes = null + ) { + \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Hydrator::hydrate([ + 'email' => $email, + 'roles' => $roles, + 'is_managed_user' => $isManagedUser, + 'password' => $password, + 'nickname' => $nickname, + 'metadata' => $metadata, + 'attributes' => $attributes, + ], self::class, $this); + } + + /** + * Create request DTO from an associative array. + * + * @param array $data + */ + public static function fromArray(array $data): self + { + self::assertRequiredFields($data, [ + 'email' => 'email', + 'roles' => 'roles', + ]); + + $request = (new \ReflectionClass(self::class))->newInstanceWithoutConstructor(); + \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Hydrator::hydrate($data, self::class, $request); + + return $request; + } + + /** + * @param array $data + * @param array $requiredFields + */ + private static function assertRequiredFields(array $data, array $requiredFields): void + { + foreach ($requiredFields as $serializedName => $propertyName) { + if (!array_key_exists($serializedName, $data) && !array_key_exists($propertyName, $data)) { + throw new \InvalidArgumentException(sprintf('Missing required field "%s".', $serializedName)); + } + } + } + +} + +class MembersUpdateRequest +{ + /** + * + * @var string[]|null + */ + public ?array $roles = null; + + /** + * Set of user-defined key-value pairs attached to the object. Partial updates are not supported. When updating, always submit whole metadata. Maximum of 64 parameters are allowed in the object. + * + * @var array|null + */ + public ?array $metadata = null; + + /** + * Object attributes that are modifiable only by SumUp applications. + * + * @var array|null + */ + public ?array $attributes = null; + + /** + * Allows you to update user data of managed users. + * + * @var MembersUpdateRequestUser|null + */ + public ?MembersUpdateRequestUser $user = null; + + /** + * Create request DTO. + * + * @param string[]|null $roles + * @param array|null $metadata + * @param array|null $attributes + * @param MembersUpdateRequestUser|null $user + */ + public function __construct( + ?array $roles = null, + ?array $metadata = null, + ?array $attributes = null, + ?MembersUpdateRequestUser $user = null + ) { + \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Hydrator::hydrate([ + 'roles' => $roles, + 'metadata' => $metadata, + 'attributes' => $attributes, + 'user' => $user, + ], self::class, $this); + } + + /** + * Create request DTO from an associative array. + * + * @param array $data + */ + public static function fromArray(array $data): self + { + $request = (new \ReflectionClass(self::class))->newInstanceWithoutConstructor(); + \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Hydrator::hydrate($data, self::class, $request); + + return $request; + } + +} + +class MembersListResponse +{ + /** + * + * @var \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Member[] + */ + public array $items; + + /** + * + * @var int|null + */ + public ?int $totalCount = null; + +} + +/** + * Allows you to update user data of managed users. + */ +class MembersUpdateRequestUser +{ + /** + * User's preferred name. Used for display purposes only. + * + * @var string|null + */ + public ?string $nickname = null; + + /** + * Password of the member to add. Only used if `is_managed_user` is true. + * + * @var string|null + */ + public ?string $password = null; + +} + +/** + * Query parameters for MembersListParams. + * + * @package SumUp\Services + */ +class MembersListParams +{ + /** + * Offset of the first member to return. + * + * @var int|null + */ + public ?int $offset = null; + + /** + * Maximum number of members to return. + * + * @var int|null + */ + public ?int $limit = null; + + /** + * Indicates to skip count query. + * + * @var bool|null + */ + public ?bool $scroll = null; + + /** + * Filter the returned members by email address prefix. + * + * @var string|null + */ + public ?string $email = null; + + /** + * Search for a member by user id. + * + * @var string|null + */ + public ?string $userId = null; + + /** + * Filter the returned members by the membership status. + * + * @var string|null + */ + public ?string $status = null; + + /** + * Filter the returned members by role. + * + * @var string[]|null + */ + public ?array $roles = null; + +} + +/** + * Class Members + * + * Endpoints to manage account members. Members are users that have membership within merchant accounts. + * + * @package SumUp\Services + */ +class Members implements SumUpService +{ + /** + * The client for the http communication. + * + * @var HttpClientInterface + */ + protected HttpClientInterface $client; + + /** + * The access token needed for authentication for the services. + * + * @var string + */ + protected string $accessToken; + + /** + * Members constructor. + * + * @param HttpClientInterface $client + * @param string $accessToken + */ + public function __construct(HttpClientInterface $client, string $accessToken) + { + $this->client = $client; + $this->accessToken = $accessToken; + } + + /** + * Create a member + * + * @param string $merchantCode Short unique identifier for the merchant. + * @param MembersCreateRequest|array $body Required request payload + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Member + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function create(string $merchantCode, MembersCreateRequest|array $body, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Member + { + $path = sprintf('/v0.1/merchants/%s/members', rawurlencode((string) $merchantCode)); + $payload = []; + $requestBody = $body; + if (is_array($requestBody)) { + $requestBody = MembersCreateRequest::fromArray($requestBody); + } + $payload = RequestEncoder::encode($requestBody); + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('POST', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, [ + '201' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Member::class], + ], [ + '400' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '429' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'POST', $path); + } + + /** + * Delete a member + * + * @param string $merchantCode Short unique identifier for the merchant. + * @param string $memberId The ID of the member to retrieve. + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return null + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function delete(string $merchantCode, string $memberId, ?RequestOptions $requestOptions = null): null + { + $path = sprintf('/v0.1/merchants/%s/members/%s', rawurlencode((string) $merchantCode), rawurlencode((string) $memberId)); + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('DELETE', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, [ + '200' => ['type' => 'void'], + ], [ + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'DELETE', $path); + } + + /** + * Retrieve a member + * + * @param string $merchantCode Short unique identifier for the merchant. + * @param string $memberId The ID of the member to retrieve. + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Member + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function get(string $merchantCode, string $memberId, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Member + { + $path = sprintf('/v0.1/merchants/%s/members/%s', rawurlencode((string) $merchantCode), rawurlencode((string) $memberId)); + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('GET', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Member::class, [ + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'GET', $path); + } + + /** + * List members + * + * @param string $merchantCode Short unique identifier for the merchant. + * @param MembersListParams|null $queryParams Optional query string parameters + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Services\MembersListResponse + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function list(string $merchantCode, ?MembersListParams $queryParams = null, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Services\MembersListResponse + { + $path = sprintf('/v0.1/merchants/%s/members', rawurlencode((string) $merchantCode)); + if ($queryParams !== null) { + $queryParamsData = []; + if (isset($queryParams->offset)) { + $queryParamsData['offset'] = $queryParams->offset; + } + if (isset($queryParams->limit)) { + $queryParamsData['limit'] = $queryParams->limit; + } + if (isset($queryParams->scroll)) { + $queryParamsData['scroll'] = $queryParams->scroll; + } + if (isset($queryParams->email)) { + $queryParamsData['email'] = $queryParams->email; + } + if (isset($queryParams->userId)) { + $queryParamsData['user.id'] = $queryParams->userId; + } + if (isset($queryParams->status)) { + $queryParamsData['status'] = $queryParams->status; + } + if (isset($queryParams->roles)) { + $queryParamsData['roles'] = $queryParams->roles; + } + if (!empty($queryParamsData)) { + $queryString = http_build_query($queryParamsData); + if (!empty($queryString)) { + $path .= '?' . $queryString; + } + } + } + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('GET', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Services\MembersListResponse::class, [ + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'GET', $path); + } + + /** + * Update a member + * + * @param string $merchantCode Short unique identifier for the merchant. + * @param string $memberId The ID of the member to retrieve. + * @param MembersUpdateRequest|array $body Required request payload + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Member + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function update(string $merchantCode, string $memberId, MembersUpdateRequest|array $body, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Member + { + $path = sprintf('/v0.1/merchants/%s/members/%s', rawurlencode((string) $merchantCode), rawurlencode((string) $memberId)); + $payload = []; + $requestBody = $body; + if (is_array($requestBody)) { + $requestBody = MembersUpdateRequest::fromArray($requestBody); + } + $payload = RequestEncoder::encode($requestBody); + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('PUT', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Member::class, [ + '400' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '403' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '409' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'PUT', $path); + } +} diff --git a/vendor_prefixed/sumup-sdk/Memberships/Memberships.php b/vendor_prefixed/sumup-sdk/Memberships/Memberships.php new file mode 100644 index 0000000..933b62c --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Memberships/Memberships.php @@ -0,0 +1,224 @@ +client = $client; + $this->accessToken = $accessToken; + } + + /** + * List memberships + * + * @param MembershipsListParams|null $queryParams Optional query string parameters + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Services\MembershipsListResponse + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function list(?MembershipsListParams $queryParams = null, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Services\MembershipsListResponse + { + $path = '/v0.1/memberships'; + if ($queryParams !== null) { + $queryParamsData = []; + if (isset($queryParams->offset)) { + $queryParamsData['offset'] = $queryParams->offset; + } + if (isset($queryParams->limit)) { + $queryParamsData['limit'] = $queryParams->limit; + } + if (isset($queryParams->kind)) { + $queryParamsData['kind'] = $queryParams->kind; + } + if (isset($queryParams->status)) { + $queryParamsData['status'] = $queryParams->status; + } + if (isset($queryParams->resourceType)) { + $queryParamsData['resource.type'] = $queryParams->resourceType; + } + if (isset($queryParams->resourceAttributesSandbox)) { + $queryParamsData['resource.attributes.sandbox'] = $queryParams->resourceAttributesSandbox; + } + if (isset($queryParams->resourceName)) { + $queryParamsData['resource.name'] = $queryParams->resourceName; + } + if (isset($queryParams->resourceParentId) || $queryParams->resourceParentIdPresent) { + $queryParamsData['resource.parent.id'] = $queryParams->resourceParentId ?? ''; + } + if (isset($queryParams->resourceParentType) || $queryParams->resourceParentTypePresent) { + $queryParamsData['resource.parent.type'] = $queryParams->resourceParentType ?? ''; + } + if (isset($queryParams->roles)) { + $queryParamsData['roles'] = $queryParams->roles; + } + if (!empty($queryParamsData)) { + $queryString = http_build_query($queryParamsData); + if (!empty($queryString)) { + $path .= '?' . $queryString; + } + } + } + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('GET', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Services\MembershipsListResponse::class, [ + '400' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '401' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'GET', $path); + } +} diff --git a/vendor_prefixed/sumup-sdk/Merchants/Merchants.php b/vendor_prefixed/sumup-sdk/Merchants/Merchants.php new file mode 100644 index 0000000..42af393 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Merchants/Merchants.php @@ -0,0 +1,211 @@ +client = $client; + $this->accessToken = $accessToken; + } + + /** + * Retrieve a Merchant + * + * @param string $merchantCode Short unique identifier for the merchant. + * @param MerchantsGetParams|null $queryParams Optional query string parameters + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Merchant + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function get(string $merchantCode, ?MerchantsGetParams $queryParams = null, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Merchant + { + $path = sprintf('/v1/merchants/%s', rawurlencode((string) $merchantCode)); + if ($queryParams !== null) { + $queryParamsData = []; + if (isset($queryParams->version)) { + $queryParamsData['version'] = $queryParams->version; + } + if (!empty($queryParamsData)) { + $queryString = http_build_query($queryParamsData); + if (!empty($queryString)) { + $path .= '?' . $queryString; + } + } + } + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('GET', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Merchant::class, [ + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'GET', $path); + } + + /** + * Retrieve a Person + * + * @param string $merchantCode Short unique identifier for the merchant. + * @param string $personId Person ID + * @param MerchantsGetPersonParams|null $queryParams Optional query string parameters + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Person + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function getPerson(string $merchantCode, string $personId, ?MerchantsGetPersonParams $queryParams = null, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Person + { + $path = sprintf('/v1/merchants/%s/persons/%s', rawurlencode((string) $merchantCode), rawurlencode((string) $personId)); + if ($queryParams !== null) { + $queryParamsData = []; + if (isset($queryParams->version)) { + $queryParamsData['version'] = $queryParams->version; + } + if (!empty($queryParamsData)) { + $queryString = http_build_query($queryParamsData); + if (!empty($queryString)) { + $path .= '?' . $queryString; + } + } + } + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('GET', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Person::class, [ + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'GET', $path); + } + + /** + * List Persons + * + * @param string $merchantCode Short unique identifier for the merchant. + * @param MerchantsListPersonsParams|null $queryParams Optional query string parameters + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\ListPersonsResponseBody + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function listPersons(string $merchantCode, ?MerchantsListPersonsParams $queryParams = null, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\ListPersonsResponseBody + { + $path = sprintf('/v1/merchants/%s/persons', rawurlencode((string) $merchantCode)); + if ($queryParams !== null) { + $queryParamsData = []; + if (isset($queryParams->version)) { + $queryParamsData['version'] = $queryParams->version; + } + if (!empty($queryParamsData)) { + $queryString = http_build_query($queryParamsData); + if (!empty($queryString)) { + $path .= '?' . $queryString; + } + } + } + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('GET', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\ListPersonsResponseBody::class, [ + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'GET', $path); + } +} diff --git a/vendor_prefixed/sumup-sdk/Payouts/Payouts.php b/vendor_prefixed/sumup-sdk/Payouts/Payouts.php new file mode 100644 index 0000000..a302b18 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Payouts/Payouts.php @@ -0,0 +1,147 @@ +client = $client; + $this->accessToken = $accessToken; + } + + /** + * List payouts + * + * @param string $merchantCode Merchant code of the account whose payouts should be listed. + * @param PayoutsListParams|null $queryParams Optional query string parameters + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\FinancialPayout[] + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function list(string $merchantCode, ?PayoutsListParams $queryParams = null, ?RequestOptions $requestOptions = null): array + { + $path = sprintf('/v1.0/merchants/%s/payouts', rawurlencode((string) $merchantCode)); + if ($queryParams !== null) { + $queryParamsData = []; + if (isset($queryParams->startDate)) { + $queryParamsData['start_date'] = $queryParams->startDate; + } + if (isset($queryParams->endDate)) { + $queryParamsData['end_date'] = $queryParams->endDate; + } + if (isset($queryParams->format)) { + $queryParamsData['format'] = $queryParams->format; + } + if (isset($queryParams->limit)) { + $queryParamsData['limit'] = $queryParams->limit; + } + if (isset($queryParams->order)) { + $queryParamsData['order'] = $queryParams->order; + } + if (!empty($queryParamsData)) { + $queryString = http_build_query($queryParamsData); + if (!empty($queryString)) { + $path .= '?' . $queryString; + } + } + } + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('GET', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, [ + '200' => ['type' => 'array', 'items' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\FinancialPayout::class]], + ], [ + '400' => ['type' => 'array', 'items' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\ErrorExtended::class]], + '401' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'GET', $path); + } +} diff --git a/vendor_prefixed/sumup-sdk/Readers/Readers.php b/vendor_prefixed/sumup-sdk/Readers/Readers.php new file mode 100644 index 0000000..eb07e1d --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Readers/Readers.php @@ -0,0 +1,454 @@ +|null + */ + public ?array $metadata = null; + + /** + * Create request DTO. + * + * @param string $pairingCode + * @param string $name + * @param array|null $metadata + */ + public function __construct( + string $pairingCode, + string $name, + ?array $metadata = null + ) { + \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Hydrator::hydrate([ + 'pairing_code' => $pairingCode, + 'name' => $name, + 'metadata' => $metadata, + ], self::class, $this); + } + + /** + * Create request DTO from an associative array. + * + * @param array $data + */ + public static function fromArray(array $data): self + { + self::assertRequiredFields($data, [ + 'pairing_code' => 'pairingCode', + 'name' => 'name', + ]); + + $request = (new \ReflectionClass(self::class))->newInstanceWithoutConstructor(); + \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Hydrator::hydrate($data, self::class, $request); + + return $request; + } + + /** + * @param array $data + * @param array $requiredFields + */ + private static function assertRequiredFields(array $data, array $requiredFields): void + { + foreach ($requiredFields as $serializedName => $propertyName) { + if (!array_key_exists($serializedName, $data) && !array_key_exists($propertyName, $data)) { + throw new \InvalidArgumentException(sprintf('Missing required field "%s".', $serializedName)); + } + } + } + +} + +/** + * Request payload for ReadersTerminateCheckoutRequest. + * + * @package SumUp\Services + */ +class ReadersTerminateCheckoutRequest +{ + /** + * Create request DTO from an associative array. + * + * @param array $data + */ + public static function fromArray(array $data): self + { + if ($data !== []) { + throw new \InvalidArgumentException('ReadersTerminateCheckoutRequest does not accept payload fields.'); + } + + return new self(); + } +} + +class ReadersUpdateRequest +{ + /** + * Custom human-readable, user-defined name for easier identification of the reader. + * + * @var string|null + */ + public ?string $name = null; + + /** + * Set of user-defined key-value pairs attached to the object. Partial updates are not supported. When updating, always submit whole metadata. Maximum of 64 parameters are allowed in the object. + * + * @var array|null + */ + public ?array $metadata = null; + + /** + * Create request DTO. + * + * @param string|null $name + * @param array|null $metadata + */ + public function __construct( + ?string $name = null, + ?array $metadata = null + ) { + \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Hydrator::hydrate([ + 'name' => $name, + 'metadata' => $metadata, + ], self::class, $this); + } + + /** + * Create request DTO from an associative array. + * + * @param array $data + */ + public static function fromArray(array $data): self + { + $request = (new \ReflectionClass(self::class))->newInstanceWithoutConstructor(); + \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Hydrator::hydrate($data, self::class, $request); + + return $request; + } + +} + +class ReadersListResponse +{ + /** + * + * @var \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Reader[] + */ + public array $items; + +} + +/** + * Class Readers + * + * A reader represents a device that accepts payments. You can use the SumUp Solo to accept in-person payments. + * + * @package SumUp\Services + */ +class Readers implements SumUpService +{ + /** + * The client for the http communication. + * + * @var HttpClientInterface + */ + protected HttpClientInterface $client; + + /** + * The access token needed for authentication for the services. + * + * @var string + */ + protected string $accessToken; + + /** + * Readers constructor. + * + * @param HttpClientInterface $client + * @param string $accessToken + */ + public function __construct(HttpClientInterface $client, string $accessToken) + { + $this->client = $client; + $this->accessToken = $accessToken; + } + + /** + * Create a Reader + * + * @param string $merchantCode Short unique identifier for the merchant. + * @param ReadersCreateRequest|array $body Required request payload + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Reader + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function create(string $merchantCode, ReadersCreateRequest|array $body, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Reader + { + $path = sprintf('/v0.1/merchants/%s/readers', rawurlencode((string) $merchantCode)); + $payload = []; + $requestBody = $body; + if (is_array($requestBody)) { + $requestBody = ReadersCreateRequest::fromArray($requestBody); + } + $payload = RequestEncoder::encode($requestBody); + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('POST', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, [ + '201' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Reader::class], + ], [ + '400' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '409' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'POST', $path); + } + + /** + * Create a Reader Checkout + * + * @param string $merchantCode Merchant Code + * @param string $readerId The unique identifier of the Reader + * @param \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\CreateReaderCheckoutRequest|array $body Required request payload + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\CreateReaderCheckoutResponse + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function createCheckout(string $merchantCode, string $readerId, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\CreateReaderCheckoutRequest|array $body, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\CreateReaderCheckoutResponse + { + $path = sprintf('/v0.1/merchants/%s/readers/%s/checkout', rawurlencode((string) $merchantCode), rawurlencode((string) $readerId)); + $payload = []; + $requestBody = $body; + if (is_array($requestBody)) { + $requestBody = \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\CreateReaderCheckoutRequest::fromArray($requestBody); + } + $payload = RequestEncoder::encode($requestBody); + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('POST', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, [ + '201' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\CreateReaderCheckoutResponse::class], + ], [ + '400' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '401' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '422' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'POST', $path); + } + + /** + * Delete a reader + * + * @param string $merchantCode Short unique identifier for the merchant. + * @param string $id The unique identifier of the reader. + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return null + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function delete(string $merchantCode, string $id, ?RequestOptions $requestOptions = null): null + { + $path = sprintf('/v0.1/merchants/%s/readers/%s', rawurlencode((string) $merchantCode), rawurlencode((string) $id)); + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('DELETE', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, [ + '200' => ['type' => 'void'], + ], [ + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'DELETE', $path); + } + + /** + * Retrieve a Reader + * + * @param string $merchantCode Short unique identifier for the merchant. + * @param string $id The unique identifier of the reader. + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Reader + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function get(string $merchantCode, string $id, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Reader + { + $path = sprintf('/v0.1/merchants/%s/readers/%s', rawurlencode((string) $merchantCode), rawurlencode((string) $id)); + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('GET', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Reader::class, [ + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'GET', $path); + } + + /** + * Get a Reader Status + * + * @param string $merchantCode Merchant Code + * @param string $readerId The unique identifier of the Reader + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\StatusResponse + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function getStatus(string $merchantCode, string $readerId, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\StatusResponse + { + $path = sprintf('/v0.1/merchants/%s/readers/%s/status', rawurlencode((string) $merchantCode), rawurlencode((string) $readerId)); + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('GET', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\StatusResponse::class, [ + '400' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '401' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'GET', $path); + } + + /** + * List Readers + * + * @param string $merchantCode Short unique identifier for the merchant. + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Services\ReadersListResponse + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function list(string $merchantCode, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Services\ReadersListResponse + { + $path = sprintf('/v0.1/merchants/%s/readers', rawurlencode((string) $merchantCode)); + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('GET', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Services\ReadersListResponse::class, [ + '401' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'GET', $path); + } + + /** + * Terminate a Reader Checkout + * + * @param string $merchantCode Merchant Code + * @param string $readerId The unique identifier of the Reader + * @param ReadersTerminateCheckoutRequest|array|null $body Optional request payload + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return null + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function terminateCheckout(string $merchantCode, string $readerId, ReadersTerminateCheckoutRequest|array|null $body = null, ?RequestOptions $requestOptions = null): null + { + $path = sprintf('/v0.1/merchants/%s/readers/%s/terminate', rawurlencode((string) $merchantCode), rawurlencode((string) $readerId)); + $payload = []; + if ($body !== null) { + $requestBody = $body; + if (is_array($requestBody)) { + $requestBody = ReadersTerminateCheckoutRequest::fromArray($requestBody); + } + $payload = RequestEncoder::encode($requestBody); + } + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('POST', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, [ + '202' => ['type' => 'void'], + ], [ + '400' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '401' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '422' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'POST', $path); + } + + /** + * Update a Reader + * + * @param string $merchantCode Short unique identifier for the merchant. + * @param string $id The unique identifier of the reader. + * @param ReadersUpdateRequest|array $body Required request payload + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Reader + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function update(string $merchantCode, string $id, ReadersUpdateRequest|array $body, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Reader + { + $path = sprintf('/v0.1/merchants/%s/readers/%s', rawurlencode((string) $merchantCode), rawurlencode((string) $id)); + $payload = []; + $requestBody = $body; + if (is_array($requestBody)) { + $requestBody = ReadersUpdateRequest::fromArray($requestBody); + } + $payload = RequestEncoder::encode($requestBody); + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('PATCH', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Reader::class, [ + '403' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'PATCH', $path); + } +} diff --git a/vendor_prefixed/sumup-sdk/Receipts/Receipts.php b/vendor_prefixed/sumup-sdk/Receipts/Receipts.php new file mode 100644 index 0000000..2464e51 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Receipts/Receipts.php @@ -0,0 +1,114 @@ +client = $client; + $this->accessToken = $accessToken; + } + + /** + * Retrieve receipt details + * + * @param string $id SumUp unique transaction ID or transaction code, e.g. TS7HDYLSKD. + * @param ReceiptsGetParams|null $queryParams Optional query string parameters + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Receipt + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function get(string $id, ?ReceiptsGetParams $queryParams = null, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Receipt + { + $path = sprintf('/v1.1/receipts/%s', rawurlencode((string) $id)); + if ($queryParams !== null) { + $queryParamsData = []; + if (isset($queryParams->mid)) { + $queryParamsData['mid'] = $queryParams->mid; + } + if (isset($queryParams->txEventId)) { + $queryParamsData['tx_event_id'] = $queryParams->txEventId; + } + if (!empty($queryParamsData)) { + $queryString = http_build_query($queryParamsData); + if (!empty($queryString)) { + $path .= '?' . $queryString; + } + } + } + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('GET', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Receipt::class, [ + '400' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Error::class], + '401' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Error::class], + ], 'GET', $path); + } +} diff --git a/vendor_prefixed/sumup-sdk/RequestEncoder.php b/vendor_prefixed/sumup-sdk/RequestEncoder.php new file mode 100644 index 0000000..66afa90 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/RequestEncoder.php @@ -0,0 +1,82 @@ + + */ + public static function encode($value): array + { + if ($value === null) { + return []; + } + + if (is_array($value)) { + return self::normalize($value); + } + + $normalized = self::normalize($value); + if (is_array($normalized)) { + return $normalized; + } + + return []; + } + + /** + * @param mixed $value + * + * @return mixed + */ + private static function normalize($value) + { + if ($value instanceof \BackedEnum) { + return $value->value; + } + + if (is_array($value)) { + $result = []; + foreach ($value as $key => $item) { + $result[$key] = self::normalize($item); + } + + return $result; + } + + if (!is_object($value)) { + return $value; + } + + $result = []; + foreach (get_object_vars($value) as $key => $item) { + if ($item === null) { + continue; + } + $result[self::toSnakeCase((string) $key)] = self::normalize($item); + } + + return $result; + } + + /** + * @param string $value + * + * @return string + */ + private static function toSnakeCase($value): string + { + $snake = preg_replace('/([a-z])([A-Z])/', '$1_$2', $value); + if (!is_string($snake) || $snake === '') { + return $value; + } + + return strtolower($snake); + } +} diff --git a/vendor_prefixed/sumup-sdk/ResponseDecoder.php b/vendor_prefixed/sumup-sdk/ResponseDecoder.php new file mode 100644 index 0000000..8043790 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/ResponseDecoder.php @@ -0,0 +1,258 @@ +|string|null $successDescriptors + * @param array|string|null $errorDescriptors + * @param string|null $httpMethod + * @param string|null $path + * + * @return mixed + * + * @throws ApiException + * @throws UnexpectedApiException + */ + public static function decodeOrThrow( + Response $response, + $successDescriptors = null, + $errorDescriptors = null, + ?string $httpMethod = null, + ?string $path = null + ) { + $statusCode = $response->getHttpResponseCode(); + if ($statusCode >= 200 && $statusCode < 300) { + return self::decode($response, $successDescriptors); + } + + if (self::hasDescriptorForStatus($errorDescriptors, $statusCode)) { + $decodedErrorBody = self::decode($response, $errorDescriptors); + $message = self::extractErrorMessage($decodedErrorBody, self::defaultErrorMessage($statusCode)); + + throw new ApiException( + $message, + $statusCode, + $decodedErrorBody, + $httpMethod, + $path + ); + } + + $rawErrorBody = $response->getBody(); + $message = self::extractErrorMessage($rawErrorBody, self::defaultUnexpectedErrorMessage($statusCode)); + + throw new UnexpectedApiException( + $message, + $statusCode, + $rawErrorBody, + $httpMethod, + $path, + $response->getHeaders(), + $response->getRawBody() + ); + } + + /** + * Decode a response using the provided descriptor map or class name. + * + * @param Response $response + * @param array|string|null $descriptors Can be a descriptor array, a class name string, or null + * + * @return mixed + */ + public static function decode(Response $response, $descriptors = null) + { + // If a simple class name string is provided, use it directly + if (is_string($descriptors)) { + return Hydrator::hydrate($response->getBody(), $descriptors); + } + + // If null or empty, return raw body + if (empty($descriptors)) { + return $response->getBody(); + } + + // Legacy descriptor array support + $statusCode = (string) $response->getHttpResponseCode(); + $descriptor = null; + if (isset($descriptors[$statusCode])) { + $descriptor = $descriptors[$statusCode]; + } elseif (isset($descriptors['default'])) { + $descriptor = $descriptors['default']; + } + + if ($descriptor === null || !isset($descriptor['type'])) { + return $response->getBody(); + } + + return self::castValue($response->getBody(), $descriptor); + } + + /** + * Convert the payload to the descriptor type. + * + * @param mixed $value + * @param array $descriptor + * + * @return mixed + */ + private static function castValue($value, array $descriptor) + { + switch ($descriptor['type']) { + case 'class': + if (!isset($descriptor['class'])) { + return $value; + } + + return Hydrator::hydrate($value, ltrim($descriptor['class'], '\\')); + case 'array': + if (!is_array($value)) { + $value = $value instanceof \stdClass ? get_object_vars($value) : (array) $value; + } + + if (!isset($descriptor['items']) || empty($descriptor['items'])) { + return $value; + } + + $result = []; + foreach ($value as $key => $item) { + $result[$key] = self::castValue($item, $descriptor['items']); + } + + return $result; + case 'scalar': + return self::castScalar($value, isset($descriptor['scalar']) ? $descriptor['scalar'] : 'mixed'); + case 'object': + if (is_array($value)) { + return $value; + } + + if (is_object($value)) { + return get_object_vars($value); + } + + return []; + case 'void': + return null; + case 'mixed': + default: + return $value; + } + } + + /** + * Cast scalar values to their expected PHP type. + * + * @param mixed $value + * @param string $type + * + * @return mixed + */ + private static function castScalar($value, $type) + { + switch ($type) { + case 'string': + return (string) $value; + case 'int': + return (int) $value; + case 'float': + return (float) $value; + case 'bool': + return (bool) $value; + default: + return $value; + } + } + + /** + * @param mixed $descriptors + * @param int $statusCode + * + * @return bool + */ + private static function hasDescriptorForStatus($descriptors, int $statusCode): bool + { + if (is_string($descriptors)) { + return true; + } + + if (!is_array($descriptors) || empty($descriptors)) { + return false; + } + + $status = (string) $statusCode; + return isset($descriptors[$status]) || isset($descriptors['default']); + } + + /** + * @param int $statusCode + * + * @return string + */ + private static function defaultErrorMessage(int $statusCode): string + { + if ($statusCode >= 500) { + return 'Server error'; + } + + return 'Client error'; + } + + /** + * @param int $statusCode + * + * @return string + */ + private static function defaultUnexpectedErrorMessage(int $statusCode): string + { + return sprintf('Unexpected API response (%d)', $statusCode); + } + + /** + * @param mixed $body + * @param string $defaultMessage + * + * @return string + */ + private static function extractErrorMessage($body, string $defaultMessage): string + { + foreach (['message', 'error_message', 'error_description', 'error'] as $key) { + $value = self::readField($body, $key); + if (is_string($value) && $value !== '') { + return $value; + } + } + + return $defaultMessage; + } + + /** + * @param mixed $payload + * @param string $key + * + * @return mixed + */ + private static function readField($payload, string $key) + { + if (is_array($payload) && array_key_exists($key, $payload)) { + return $payload[$key]; + } + if (is_object($payload) && isset($payload->{$key})) { + return $payload->{$key}; + } + + return null; + } +} diff --git a/vendor_prefixed/sumup-sdk/Roles/Roles.php b/vendor_prefixed/sumup-sdk/Roles/Roles.php new file mode 100644 index 0000000..f1bc7a9 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Roles/Roles.php @@ -0,0 +1,348 @@ +|null + */ + public ?array $metadata = null; + + /** + * User-defined description of the role. + * + * @var string|null + */ + public ?string $description = null; + + /** + * Create request DTO. + * + * @param string $name + * @param string[] $permissions + * @param array|null $metadata + * @param string|null $description + */ + public function __construct( + string $name, + array $permissions, + ?array $metadata = null, + ?string $description = null + ) { + \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Hydrator::hydrate([ + 'name' => $name, + 'permissions' => $permissions, + 'metadata' => $metadata, + 'description' => $description, + ], self::class, $this); + } + + /** + * Create request DTO from an associative array. + * + * @param array $data + */ + public static function fromArray(array $data): self + { + self::assertRequiredFields($data, [ + 'name' => 'name', + 'permissions' => 'permissions', + ]); + + $request = (new \ReflectionClass(self::class))->newInstanceWithoutConstructor(); + \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Hydrator::hydrate($data, self::class, $request); + + return $request; + } + + /** + * @param array $data + * @param array $requiredFields + */ + private static function assertRequiredFields(array $data, array $requiredFields): void + { + foreach ($requiredFields as $serializedName => $propertyName) { + if (!array_key_exists($serializedName, $data) && !array_key_exists($propertyName, $data)) { + throw new \InvalidArgumentException(sprintf('Missing required field "%s".', $serializedName)); + } + } + } + +} + +class RolesUpdateRequest +{ + /** + * User-defined name of the role. + * + * @var string|null + */ + public ?string $name = null; + + /** + * User's permissions. + * + * @var string[]|null + */ + public ?array $permissions = null; + + /** + * User-defined description of the role. + * + * @var string|null + */ + public ?string $description = null; + + /** + * Create request DTO. + * + * @param string|null $name + * @param string[]|null $permissions + * @param string|null $description + */ + public function __construct( + ?string $name = null, + ?array $permissions = null, + ?string $description = null + ) { + \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Hydrator::hydrate([ + 'name' => $name, + 'permissions' => $permissions, + 'description' => $description, + ], self::class, $this); + } + + /** + * Create request DTO from an associative array. + * + * @param array $data + */ + public static function fromArray(array $data): self + { + $request = (new \ReflectionClass(self::class))->newInstanceWithoutConstructor(); + \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Hydrator::hydrate($data, self::class, $request); + + return $request; + } + +} + +class RolesListResponse +{ + /** + * + * @var \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Role[] + */ + public array $items; + +} + +/** + * Class Roles + * + * Endpoints to manage custom roles. Custom roles allow you to tailor roles from individual permissions to match your needs. Once created, you can assign your custom roles to your merchant account members using the memberships. + * + * @package SumUp\Services + */ +class Roles implements SumUpService +{ + /** + * The client for the http communication. + * + * @var HttpClientInterface + */ + protected HttpClientInterface $client; + + /** + * The access token needed for authentication for the services. + * + * @var string + */ + protected string $accessToken; + + /** + * Roles constructor. + * + * @param HttpClientInterface $client + * @param string $accessToken + */ + public function __construct(HttpClientInterface $client, string $accessToken) + { + $this->client = $client; + $this->accessToken = $accessToken; + } + + /** + * Create a role + * + * @param string $merchantCode Short unique identifier for the merchant. + * @param RolesCreateRequest|array $body Required request payload + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Role + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function create(string $merchantCode, RolesCreateRequest|array $body, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Role + { + $path = sprintf('/v0.1/merchants/%s/roles', rawurlencode((string) $merchantCode)); + $payload = []; + $requestBody = $body; + if (is_array($requestBody)) { + $requestBody = RolesCreateRequest::fromArray($requestBody); + } + $payload = RequestEncoder::encode($requestBody); + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('POST', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, [ + '201' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Role::class], + ], [ + '400' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'POST', $path); + } + + /** + * Delete a role + * + * @param string $merchantCode Short unique identifier for the merchant. + * @param string $roleId The ID of the role to retrieve. + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return null + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function delete(string $merchantCode, string $roleId, ?RequestOptions $requestOptions = null): null + { + $path = sprintf('/v0.1/merchants/%s/roles/%s', rawurlencode((string) $merchantCode), rawurlencode((string) $roleId)); + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('DELETE', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, [ + '200' => ['type' => 'void'], + ], [ + '400' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'DELETE', $path); + } + + /** + * Retrieve a role + * + * @param string $merchantCode Short unique identifier for the merchant. + * @param string $roleId The ID of the role to retrieve. + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Role + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function get(string $merchantCode, string $roleId, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Role + { + $path = sprintf('/v0.1/merchants/%s/roles/%s', rawurlencode((string) $merchantCode), rawurlencode((string) $roleId)); + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('GET', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Role::class, [ + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'GET', $path); + } + + /** + * List roles + * + * @param string $merchantCode Short unique identifier for the merchant. + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Services\RolesListResponse + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function list(string $merchantCode, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Services\RolesListResponse + { + $path = sprintf('/v0.1/merchants/%s/roles', rawurlencode((string) $merchantCode)); + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('GET', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Services\RolesListResponse::class, [ + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'GET', $path); + } + + /** + * Update a role + * + * @param string $merchantCode Short unique identifier for the merchant. + * @param string $roleId The ID of the role to retrieve. + * @param RolesUpdateRequest|array $body Required request payload + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Role + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function update(string $merchantCode, string $roleId, RolesUpdateRequest|array $body, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Role + { + $path = sprintf('/v0.1/merchants/%s/roles/%s', rawurlencode((string) $merchantCode), rawurlencode((string) $roleId)); + $payload = []; + $requestBody = $body; + if (is_array($requestBody)) { + $requestBody = RolesUpdateRequest::fromArray($requestBody); + } + $payload = RequestEncoder::encode($requestBody); + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('PATCH', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Role::class, [ + '400' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'PATCH', $path); + } +} diff --git a/vendor_prefixed/sumup-sdk/SdkInfo.php b/vendor_prefixed/sumup-sdk/SdkInfo.php new file mode 100644 index 0000000..6f46720 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/SdkInfo.php @@ -0,0 +1,100 @@ + + */ + public static function getRuntimeHeaders() + { + static $headers = null; + + if ($headers === null) { + $headers = [ + 'X-Sumup-Api-Version' => ApiVersion::CURRENT, + 'X-Sumup-Lang' => 'php', + 'X-Sumup-Package-Version' => self::getVersion(), + 'X-Sumup-Os' => self::getOsName(), + 'X-Sumup-Arch' => self::getArch(), + 'X-Sumup-Runtime' => 'php', + 'X-Sumup-Runtime-Version' => PHP_VERSION, + ]; + } + + return $headers; + } + + /** + * @return string + */ + private static function getOsName() + { + $osRaw = php_uname('s'); + $os = strtolower($osRaw); + if (strpos($os, 'win') === 0) { + return 'windows'; + } + if (strpos($os, 'linux') === 0) { + return 'linux'; + } + if (strpos($os, 'darwin') === 0) { + return 'darwin'; + } + return $os ? $os : 'unknown'; + } + + /** + * @return string + */ + private static function getArch() + { + $archRaw = php_uname('m'); + $arch = strtolower($archRaw); + $map = [ + 'x86_64' => 'x86_64', + 'x64' => 'x86_64', + 'amd64' => 'x86_64', + 'x86' => 'x86', + 'i386' => 'x86', + 'i686' => 'x86', + 'ia32' => 'x86', + 'x32' => 'x86', + 'aarch64' => 'arm64', + 'arm64' => 'arm64', + 'arm' => 'arm', + ]; + if (isset($map[$arch])) { + return $map[$arch]; + } + return $arch ? $arch : 'unknown'; + } +} diff --git a/vendor_prefixed/sumup-sdk/Services/SumUpService.php b/vendor_prefixed/sumup-sdk/Services/SumUpService.php new file mode 100644 index 0000000..cf45a2c --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Services/SumUpService.php @@ -0,0 +1,12 @@ +|null $configOrApiKey + * + * @throws SDKException + */ + public function __construct(string|array|null $configOrApiKey = null) + { + $config = []; + if (is_string($configOrApiKey) && $configOrApiKey !== '') { + $config['api_key'] = $configOrApiKey; + } elseif (is_array($configOrApiKey)) { + $config = $configOrApiKey; + } + $customHttpClient = $config['client'] ?? null; + if (array_key_exists('client', $config)) { + unset($config['client']); + } + + $config = $this->normalizeConfig($config); + if ($customHttpClient instanceof HttpClientInterface) { + $this->client = $customHttpClient; + } else { + $this->client = new CurlClient( + $config['base_uri'], + $config['custom_headers'], + $config['ca_bundle_path'] + ); + } + + // Set access token from config (api_key or access_token) + if (!empty($config['api_key'])) { + $this->accessToken = $config['api_key']; + } elseif (!empty($config['access_token'])) { + $this->accessToken = $config['access_token']; + } + } + + /** + * Returns the default access token. + * + * @return string|null + */ + public function getDefaultAccessToken(): ?string + { + return $this->accessToken; + } + + /** + * Sets the default access token. + * + * @param string $accessToken + * + * @return void + */ + public function setDefaultAccessToken(string $accessToken): void + { + $this->accessToken = $accessToken; + } + + /** + * Send a raw request through the configured HTTP client. + * + * @param string $method + * @param string $path + * @param array $body + * @param RequestOptions|null $requestOptions + * + * @return Response + */ + public function request( + string $method, + string $path, + array $body = [], + ?RequestOptions $requestOptions = null + ): Response { + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + return $this->client->send($method, $path, $body, $headers, $requestOptions); + } + + /** + * Resolve the access token that should be used for a service. + * + * @param string|null $accessToken + * + * @return string + * + * @throws ConfigurationException + */ + protected function resolveAccessToken(?string $accessToken = null): string + { + if (!empty($accessToken)) { + return $accessToken; + } + + if (empty($this->accessToken)) { + throw new ConfigurationException('No access token provided'); + } + + return $this->accessToken; + } + + /** + * Normalize configuration and apply defaults. + * + * @param array $config + * + * @return array + * + * @throws ConfigurationException + */ + private function normalizeConfig(array $config): array + { + $config = array_merge([ + 'api_key' => null, + 'access_token' => null, + 'base_uri' => 'https://api.sumup.com', + 'custom_headers' => [], + 'ca_bundle_path' => null, + ], $config); + + if ($config['api_key'] === null) { + $config['api_key'] = getenv('SUMUP_API_KEY') ?: null; + } + + if ($config['access_token'] === null) { + $config['access_token'] = getenv('SUMUP_ACCESS_TOKEN') ?: null; + } + + $headers = is_array($config['custom_headers']) ? $config['custom_headers'] : []; + $headers['Accept'] = 'application/problem+json, application/json'; + $headers['User-Agent'] = SdkInfo::getUserAgent(); + $config['custom_headers'] = $headers; + + return $config; + } + + /** + * Access the Checkouts API endpoints. + * + * @return Checkouts + */ + public function checkouts(): Checkouts + { + return new Checkouts($this->client, $this->resolveAccessToken()); + } + + /** + * Access the Customers API endpoints. + * + * @return Customers + */ + public function customers(): Customers + { + return new Customers($this->client, $this->resolveAccessToken()); + } + + /** + * Access the Members API endpoints. + * + * @return Members + */ + public function members(): Members + { + return new Members($this->client, $this->resolveAccessToken()); + } + + /** + * Access the Memberships API endpoints. + * + * @return Memberships + */ + public function memberships(): Memberships + { + return new Memberships($this->client, $this->resolveAccessToken()); + } + + /** + * Access the Merchants API endpoints. + * + * @return Merchants + */ + public function merchants(): Merchants + { + return new Merchants($this->client, $this->resolveAccessToken()); + } + + /** + * Access the Payouts API endpoints. + * + * @return Payouts + */ + public function payouts(): Payouts + { + return new Payouts($this->client, $this->resolveAccessToken()); + } + + /** + * Access the Readers API endpoints. + * + * @return Readers + */ + public function readers(): Readers + { + return new Readers($this->client, $this->resolveAccessToken()); + } + + /** + * Access the Receipts API endpoints. + * + * @return Receipts + */ + public function receipts(): Receipts + { + return new Receipts($this->client, $this->resolveAccessToken()); + } + + /** + * Access the Roles API endpoints. + * + * @return Roles + */ + public function roles(): Roles + { + return new Roles($this->client, $this->resolveAccessToken()); + } + + /** + * Access the Transactions API endpoints. + * + * @return Transactions + */ + public function transactions(): Transactions + { + return new Transactions($this->client, $this->resolveAccessToken()); + } +} diff --git a/vendor_prefixed/sumup-sdk/Transactions/Transactions.php b/vendor_prefixed/sumup-sdk/Transactions/Transactions.php new file mode 100644 index 0000000..4120ad7 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Transactions/Transactions.php @@ -0,0 +1,419 @@ + $amount, + ], self::class, $this); + } + + /** + * Create request DTO from an associative array. + * + * @param array $data + */ + public static function fromArray(array $data): self + { + $request = (new \ReflectionClass(self::class))->newInstanceWithoutConstructor(); + \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Hydrator::hydrate($data, self::class, $request); + + return $request; + } + +} + +class TransactionsListResponse +{ + /** + * + * @var \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\TransactionHistory[]|null + */ + public ?array $items = null; + + /** + * + * @var \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\TransactionsHistoryLink[]|null + */ + public ?array $links = null; + +} + +/** + * Query parameters for TransactionsGetParams. + * + * @package SumUp\Services + */ +class TransactionsGetParams +{ + /** + * Retrieves the transaction resource with the specified transaction ID (the `id` parameter in the transaction resource). + * + * @var string|null + */ + public ?string $id = null; + + /** + * Retrieves the transaction resource with the specified transaction code. + * + * @var string|null + */ + public ?string $transactionCode = null; + + /** + * External/foreign transaction id (passed by clients). + * + * @var string|null + */ + public ?string $foreignTransactionId = null; + + /** + * Client transaction id. + * + * @var string|null + */ + public ?string $clientTransactionId = null; + +} + +/** + * Query parameters for TransactionsListParams. + * + * @package SumUp\Services + */ +class TransactionsListParams +{ + /** + * Retrieves the transaction resource with the specified transaction code. + * + * @var string|null + */ + public ?string $transactionCode = null; + + /** + * Specifies the order in which the returned results are displayed. + * + * @var string|null + */ + public ?string $order = null; + + /** + * Specifies the maximum number of results per page. Value must be a positive integer and if not specified, will return 10 results. + * + * @var int|null + */ + public ?int $limit = null; + + /** + * Filters the returned results by user email. + * + * @var string[]|null + */ + public ?array $users = null; + + /** + * Filters the returned results by the specified list of final statuses of the transactions. + * + * @var string[]|null + */ + public ?array $statusesList = null; + + /** + * Filters the returned results by the specified list of payment types used for the transactions. + * + * @var string[]|null + */ + public ?array $paymentTypes = null; + + /** + * Filters the returned results by the specified list of entry modes. + * + * @var string[]|null + */ + public ?array $entryModesList = null; + + /** + * Filters the returned results by the specified list of transaction types. + * + * @var string[]|null + */ + public ?array $types = null; + + /** + * Filters the results by the latest modification time of resources and returns only transactions that are modified *at or after* the specified timestamp (in [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) format). + * + * @var string|null + */ + public ?string $changesSince = null; + + /** + * Filters the results by the creation time of resources and returns only transactions that are created *before* the specified timestamp (in [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) format). + * + * @var string|null + */ + public ?string $newestTime = null; + + /** + * Filters the results by the reference ID of transaction events and returns only transactions with events whose IDs are *smaller* than the specified value. This parameters supersedes the `newest_time` parameter (if both are provided in the request). + * + * @var string|null + */ + public ?string $newestRef = null; + + /** + * Filters the results by the creation time of resources and returns only transactions that are created *at or after* the specified timestamp (in [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) format). + * + * @var string|null + */ + public ?string $oldestTime = null; + + /** + * Filters the results by the reference ID of transaction events and returns only transactions with events whose IDs are *greater* than the specified value. This parameters supersedes the `oldest_time` parameter (if both are provided in the request). + * + * @var string|null + */ + public ?string $oldestRef = null; + +} + +/** + * Class Transactions + * + * Transactions represent completed or attempted payment operations processed for a merchant account. A transaction contains the core payment result, such as the amount, currency, payment method, creation time, and current high-level status. + * + * In addition to the main payment outcome, a transaction can contain related events that describe what happened after the original payment attempt. These events provide visibility into the financial lifecycle of the transaction, for example: + * - `PAYOUT`: the payment being prepared for payout or included in a payout to the merchant + * - `REFUND`: money returned to the payer + * - `CHARGE_BACK`: money reversed after the original payment + * - `PAYOUT_DEDUCTION`: an amount deducted from a payout to cover a refund or chargeback + * + * From an integrator's perspective, transactions are the authoritative record of payment outcomes. Use this tag to: + * - list transactions for reporting, reconciliation, and customer support workflows + * - retrieve a single transaction when you need the latest payment details + * - inspect `simple_status` for the current merchant-facing outcome of the payment + * - inspect `events` or `transaction_events` when you need refund, payout, or chargeback history + * + * Typical workflow: + * - create and process payments through the Checkouts endpoints + * - use the Transactions endpoints to read the resulting payment records + * - use the returned statuses and events to update your own order, accounting, or support systems + * + * @package SumUp\Services + */ +class Transactions implements SumUpService +{ + /** + * The client for the http communication. + * + * @var HttpClientInterface + */ + protected HttpClientInterface $client; + + /** + * The access token needed for authentication for the services. + * + * @var string + */ + protected string $accessToken; + + /** + * Transactions constructor. + * + * @param HttpClientInterface $client + * @param string $accessToken + */ + public function __construct(HttpClientInterface $client, string $accessToken) + { + $this->client = $client; + $this->accessToken = $accessToken; + } + + /** + * Retrieve a transaction + * + * @param string $merchantCode Merchant code of the account whose transaction should be retrieved. + * @param TransactionsGetParams|null $queryParams Optional query string parameters + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\TransactionFull + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function get(string $merchantCode, ?TransactionsGetParams $queryParams = null, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\TransactionFull + { + $path = sprintf('/v2.1/merchants/%s/transactions', rawurlencode((string) $merchantCode)); + if ($queryParams !== null) { + $queryParamsData = []; + if (isset($queryParams->id)) { + $queryParamsData['id'] = $queryParams->id; + } + if (isset($queryParams->transactionCode)) { + $queryParamsData['transaction_code'] = $queryParams->transactionCode; + } + if (isset($queryParams->foreignTransactionId)) { + $queryParamsData['foreign_transaction_id'] = $queryParams->foreignTransactionId; + } + if (isset($queryParams->clientTransactionId)) { + $queryParamsData['client_transaction_id'] = $queryParams->clientTransactionId; + } + if (!empty($queryParamsData)) { + $queryString = http_build_query($queryParamsData); + if (!empty($queryString)) { + $path .= '?' . $queryString; + } + } + } + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('GET', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\TransactionFull::class, [ + '401' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Error::class], + ], 'GET', $path); + } + + /** + * List transactions + * + * @param string $merchantCode Merchant code of the account whose transaction history should be listed. + * @param TransactionsListParams|null $queryParams Optional query string parameters + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Services\TransactionsListResponse + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function list(string $merchantCode, ?TransactionsListParams $queryParams = null, ?RequestOptions $requestOptions = null): \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Services\TransactionsListResponse + { + $path = sprintf('/v2.1/merchants/%s/transactions/history', rawurlencode((string) $merchantCode)); + if ($queryParams !== null) { + $queryParamsData = []; + if (isset($queryParams->transactionCode)) { + $queryParamsData['transaction_code'] = $queryParams->transactionCode; + } + if (isset($queryParams->order)) { + $queryParamsData['order'] = $queryParams->order; + } + if (isset($queryParams->limit)) { + $queryParamsData['limit'] = $queryParams->limit; + } + if (isset($queryParams->users)) { + $queryParamsData['users'] = $queryParams->users; + } + if (isset($queryParams->statusesList)) { + $queryParamsData['statuses[]'] = $queryParams->statusesList; + } + if (isset($queryParams->paymentTypes)) { + $queryParamsData['payment_types'] = $queryParams->paymentTypes; + } + if (isset($queryParams->entryModesList)) { + $queryParamsData['entry_modes[]'] = $queryParams->entryModesList; + } + if (isset($queryParams->types)) { + $queryParamsData['types'] = $queryParams->types; + } + if (isset($queryParams->changesSince)) { + $queryParamsData['changes_since'] = $queryParams->changesSince; + } + if (isset($queryParams->newestTime)) { + $queryParamsData['newest_time'] = $queryParams->newestTime; + } + if (isset($queryParams->newestRef)) { + $queryParamsData['newest_ref'] = $queryParams->newestRef; + } + if (isset($queryParams->oldestTime)) { + $queryParamsData['oldest_time'] = $queryParams->oldestTime; + } + if (isset($queryParams->oldestRef)) { + $queryParamsData['oldest_ref'] = $queryParams->oldestRef; + } + if (!empty($queryParamsData)) { + $queryString = http_build_query($queryParamsData); + if (!empty($queryString)) { + $path .= '?' . $queryString; + } + } + } + $payload = []; + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('GET', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Services\TransactionsListResponse::class, [ + '400' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Error::class], + '401' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Problem::class], + ], 'GET', $path); + } + + /** + * Refund a transaction + * + * @param string $merchantCode Merchant code of the account that owns the payment to refund. + * @param string $id Unique ID of the transaction. + * @param TransactionsRefundRequest|array|null $body Optional request payload + * @param RequestOptions|null $requestOptions Optional typed request options + * + * @return null + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\UnexpectedApiException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\ConnectionException + * @throws \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Exception\SDKException + */ + public function refund(string $merchantCode, string $id, TransactionsRefundRequest|array|null $body = null, ?RequestOptions $requestOptions = null): null + { + $path = sprintf('/v1.0/merchants/%s/payments/%s/refunds', rawurlencode((string) $merchantCode), rawurlencode((string) $id)); + $payload = []; + if ($body !== null) { + $requestBody = $body; + if (is_array($requestBody)) { + $requestBody = TransactionsRefundRequest::fromArray($requestBody); + } + $payload = RequestEncoder::encode($requestBody); + } + $headers = RequestHeaders::build($this->accessToken, $requestOptions); + + $response = $this->client->send('POST', $path, $payload, $headers, $requestOptions); + + return ResponseDecoder::decodeOrThrow($response, [ + '204' => ['type' => 'void'], + ], [ + '404' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Error::class], + '409' => ['type' => 'class', 'class' => \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Types\Error::class], + ], 'POST', $path); + } +} diff --git a/vendor_prefixed/sumup-sdk/Types/Address.php b/vendor_prefixed/sumup-sdk/Types/Address.php new file mode 100644 index 0000000..c4d9b77 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Types/Address.php @@ -0,0 +1,131 @@ +|null + */ + public ?array $payload = null; + +} diff --git a/vendor_prefixed/sumup-sdk/Types/CheckoutCreateRequest.php b/vendor_prefixed/sumup-sdk/Types/CheckoutCreateRequest.php new file mode 100644 index 0000000..4c2d088 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Types/CheckoutCreateRequest.php @@ -0,0 +1,165 @@ + $checkoutReference, + 'amount' => $amount, + 'currency' => $currency, + 'merchant_code' => $merchantCode, + 'description' => $description, + 'return_url' => $returnUrl, + 'customer_id' => $customerId, + 'purpose' => $purpose, + 'valid_until' => $validUntil, + 'redirect_url' => $redirectUrl, + 'hosted_checkout' => $hostedCheckout, + ], self::class, $this); + } + + /** + * Create request DTO from an associative array. + * + * @param array $data + */ + public static function fromArray(array $data): self + { + self::assertRequiredFields($data, [ + 'checkout_reference' => 'checkoutReference', + 'amount' => 'amount', + 'currency' => 'currency', + 'merchant_code' => 'merchantCode', + ]); + + $request = (new \ReflectionClass(self::class))->newInstanceWithoutConstructor(); + \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Hydrator::hydrate($data, self::class, $request); + + return $request; + } + + /** + * @param array $data + * @param array $requiredFields + */ + private static function assertRequiredFields(array $data, array $requiredFields): void + { + foreach ($requiredFields as $serializedName => $propertyName) { + if (!array_key_exists($serializedName, $data) && !array_key_exists($propertyName, $data)) { + throw new \InvalidArgumentException(sprintf('Missing required field "%s".', $serializedName)); + } + } + } + +} diff --git a/vendor_prefixed/sumup-sdk/Types/CheckoutCreateRequestCurrency.php b/vendor_prefixed/sumup-sdk/Types/CheckoutCreateRequestCurrency.php new file mode 100644 index 0000000..b3461cb --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Types/CheckoutCreateRequestCurrency.php @@ -0,0 +1,28 @@ +|null + */ + public ?array $attributes = null; + +} diff --git a/vendor_prefixed/sumup-sdk/Types/CompanyIdentifier.php b/vendor_prefixed/sumup-sdk/Types/CompanyIdentifier.php new file mode 100644 index 0000000..705e1ea --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Types/CompanyIdentifier.php @@ -0,0 +1,23 @@ + $totalAmount, + 'aade' => $aade, + 'affiliate' => $affiliate, + 'card_type' => $cardType, + 'description' => $description, + 'installments' => $installments, + 'return_url' => $returnUrl, + 'tip_rates' => $tipRates, + 'tip_timeout' => $tipTimeout, + ], self::class, $this); + } + + /** + * Create request DTO from an associative array. + * + * @param array $data + */ + public static function fromArray(array $data): self + { + self::assertRequiredFields($data, [ + 'total_amount' => 'totalAmount', + ]); + + $request = (new \ReflectionClass(self::class))->newInstanceWithoutConstructor(); + \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Hydrator::hydrate($data, self::class, $request); + + return $request; + } + + /** + * @param array $data + * @param array $requiredFields + */ + private static function assertRequiredFields(array $data, array $requiredFields): void + { + foreach ($requiredFields as $serializedName => $propertyName) { + if (!array_key_exists($serializedName, $data) && !array_key_exists($propertyName, $data)) { + throw new \InvalidArgumentException(sprintf('Missing required field "%s".', $serializedName)); + } + } + } + +} diff --git a/vendor_prefixed/sumup-sdk/Types/CreateReaderCheckoutRequestAade.php b/vendor_prefixed/sumup-sdk/Types/CreateReaderCheckoutRequestAade.php new file mode 100644 index 0000000..24b1388 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Types/CreateReaderCheckoutRequestAade.php @@ -0,0 +1,35 @@ +|null + */ + public ?array $tags = null; + +} diff --git a/vendor_prefixed/sumup-sdk/Types/CreateReaderCheckoutRequestCardType.php b/vendor_prefixed/sumup-sdk/Types/CreateReaderCheckoutRequestCardType.php new file mode 100644 index 0000000..8c99476 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Types/CreateReaderCheckoutRequestCardType.php @@ -0,0 +1,16 @@ + + */ + public array $errors; + +} diff --git a/vendor_prefixed/sumup-sdk/Types/CreateReaderTerminateError.php b/vendor_prefixed/sumup-sdk/Types/CreateReaderTerminateError.php new file mode 100644 index 0000000..6f94acf --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Types/CreateReaderTerminateError.php @@ -0,0 +1,18 @@ + + */ + public array $errors; + +} diff --git a/vendor_prefixed/sumup-sdk/Types/Customer.php b/vendor_prefixed/sumup-sdk/Types/Customer.php new file mode 100644 index 0000000..2436b06 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Types/Customer.php @@ -0,0 +1,72 @@ + $customerId, + 'personal_details' => $personalDetails, + ], self::class, $this); + } + + /** + * Create request DTO from an associative array. + * + * @param array $data + */ + public static function fromArray(array $data): self + { + self::assertRequiredFields($data, [ + 'customer_id' => 'customerId', + ]); + + $request = (new \ReflectionClass(self::class))->newInstanceWithoutConstructor(); + \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Hydrator::hydrate($data, self::class, $request); + + return $request; + } + + /** + * @param array $data + * @param array $requiredFields + */ + private static function assertRequiredFields(array $data, array $requiredFields): void + { + foreach ($requiredFields as $serializedName => $propertyName) { + if (!array_key_exists($serializedName, $data) && !array_key_exists($propertyName, $data)) { + throw new \InvalidArgumentException(sprintf('Missing required field "%s".', $serializedName)); + } + } + } + +} diff --git a/vendor_prefixed/sumup-sdk/Types/DetailsError.php b/vendor_prefixed/sumup-sdk/Types/DetailsError.php new file mode 100644 index 0000000..440d0f6 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Types/DetailsError.php @@ -0,0 +1,40 @@ +[]|null + */ + public ?array $failedConstraints = null; + +} diff --git a/vendor_prefixed/sumup-sdk/Types/Device.php b/vendor_prefixed/sumup-sdk/Types/Device.php new file mode 100644 index 0000000..e06dd7a --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Types/Device.php @@ -0,0 +1,47 @@ +|null + */ + public ?array $metadata = null; + + /** + * Object attributes that are modifiable only by SumUp applications. + * + * @var array|null + */ + public ?array $attributes = null; + +} diff --git a/vendor_prefixed/sumup-sdk/Types/MemberStatus.php b/vendor_prefixed/sumup-sdk/Types/MemberStatus.php new file mode 100644 index 0000000..4f6658b --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Types/MemberStatus.php @@ -0,0 +1,17 @@ +|null + */ + public ?array $metadata = null; + + /** + * Object attributes that are modifiable only by SumUp applications. + * + * @var array|null + */ + public ?array $attributes = null; + + /** + * Information about the resource the membership is in. + * + * @var MembershipResource + */ + public MembershipResource $resource; + +} diff --git a/vendor_prefixed/sumup-sdk/Types/MembershipResource.php b/vendor_prefixed/sumup-sdk/Types/MembershipResource.php new file mode 100644 index 0000000..3a5e2f8 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Types/MembershipResource.php @@ -0,0 +1,64 @@ +|null + */ + public ?array $attributes = null; + +} diff --git a/vendor_prefixed/sumup-sdk/Types/MembershipStatus.php b/vendor_prefixed/sumup-sdk/Types/MembershipStatus.php new file mode 100644 index 0000000..c45a46b --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Types/MembershipStatus.php @@ -0,0 +1,17 @@ +|null + */ + public ?array $meta = null; + + /** + * + * @var ClassicMerchantIdentifiers|null + */ + public ?ClassicMerchantIdentifiers $classic = null; + + /** + * The version of the resource. The version reflects a specific change submitted to the API via one of the `PATCH` endpoints. + * + * @var string|null + */ + public ?string $version = null; + + /** + * Reflects the status of changes submitted through the `PATCH` endpoints for the merchant or persons. If some changes have not been applied yet, the status will be `pending`. If all changes have been applied, the status `done`. + * The status is only returned after write operations or on read endpoints when the `version` query parameter is provided. + * + * @var string|null + */ + public ?string $changeStatus = null; + + /** + * The date and time when the resource was created. This is a string as defined in [RFC 3339, section 5.6](https://datatracker.ietf.org/doc/html/rfc3339#section-5.6). + * + * @var string + */ + public string $createdAt; + + /** + * The date and time when the resource was last updated. This is a string as defined in [RFC 3339, section 5.6](https://datatracker.ietf.org/doc/html/rfc3339#section-5.6). + * + * @var string + */ + public string $updatedAt; + +} diff --git a/vendor_prefixed/sumup-sdk/Types/NotFound.php b/vendor_prefixed/sumup-sdk/Types/NotFound.php new file mode 100644 index 0000000..8e3ebe3 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Types/NotFound.php @@ -0,0 +1,18 @@ +|null + */ + public ?array $googlePay = null; + + /** + * Raw payment token object received from Apple Pay. Send the Apple Pay response payload as-is. + * + * @var array|null + */ + public ?array $applePay = null; + + /** + * Saved-card token to use instead of raw card details when processing with a previously stored payment instrument. + * + * @var string|null + */ + public ?string $token = null; + + /** + * Customer identifier associated with the saved payment instrument. Required when `token` is provided. + * + * @var string|null + */ + public ?string $customerId = null; + + /** + * Personal details for the customer. + * + * @var PersonalDetails|null + */ + public ?PersonalDetails $personalDetails = null; + + /** + * Create request DTO. + * + * @param ProcessCheckoutPaymentType|string $paymentType + * @param int|null $installments + * @param MandatePayload|null $mandate + * @param Card|null $card + * @param array|null $googlePay + * @param array|null $applePay + * @param string|null $token + * @param string|null $customerId + * @param PersonalDetails|null $personalDetails + */ + public function __construct( + ProcessCheckoutPaymentType|string $paymentType, + ?int $installments = null, + ?MandatePayload $mandate = null, + ?Card $card = null, + ?array $googlePay = null, + ?array $applePay = null, + ?string $token = null, + ?string $customerId = null, + ?PersonalDetails $personalDetails = null + ) { + \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Hydrator::hydrate([ + 'payment_type' => $paymentType, + 'installments' => $installments, + 'mandate' => $mandate, + 'card' => $card, + 'google_pay' => $googlePay, + 'apple_pay' => $applePay, + 'token' => $token, + 'customer_id' => $customerId, + 'personal_details' => $personalDetails, + ], self::class, $this); + } + + /** + * Create request DTO from an associative array. + * + * @param array $data + */ + public static function fromArray(array $data): self + { + self::assertRequiredFields($data, [ + 'payment_type' => 'paymentType', + ]); + + $request = (new \ReflectionClass(self::class))->newInstanceWithoutConstructor(); + \WCPOS\WooCommercePOS\SumUpTerminal\Vendor\SumUpSdk\SumUp\Hydrator::hydrate($data, self::class, $request); + + return $request; + } + + /** + * @param array $data + * @param array $requiredFields + */ + private static function assertRequiredFields(array $data, array $requiredFields): void + { + foreach ($requiredFields as $serializedName => $propertyName) { + if (!array_key_exists($serializedName, $data) && !array_key_exists($propertyName, $data)) { + throw new \InvalidArgumentException(sprintf('Missing required field "%s".', $serializedName)); + } + } + } + +} diff --git a/vendor_prefixed/sumup-sdk/Types/ProcessCheckoutPaymentType.php b/vendor_prefixed/sumup-sdk/Types/ProcessCheckoutPaymentType.php new file mode 100644 index 0000000..06970cf --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Types/ProcessCheckoutPaymentType.php @@ -0,0 +1,19 @@ +|null + */ + public ?array $metadata = null; + + /** + * Identifier of the system-managed service account associated with this reader. + * Present only for readers that are already paired. + * This field is currently in beta and may change. + * + * @var string|null + */ + public ?string $serviceAccountId = null; + + /** + * The timestamp of when the reader was created. + * + * @var string + */ + public string $createdAt; + + /** + * The timestamp of when the reader was last updated. + * + * @var string + */ + public string $updatedAt; + +} diff --git a/vendor_prefixed/sumup-sdk/Types/ReaderDevice.php b/vendor_prefixed/sumup-sdk/Types/ReaderDevice.php new file mode 100644 index 0000000..6953b6c --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Types/ReaderDevice.php @@ -0,0 +1,26 @@ +|null + */ + public ?array $emvData = null; + + /** + * Acquirer-specific metadata related to the card authorization. + * + * @var ReceiptAcquirerData|null + */ + public ?ReceiptAcquirerData $acquirerData = null; + +} diff --git a/vendor_prefixed/sumup-sdk/Types/ReceiptAcquirerData.php b/vendor_prefixed/sumup-sdk/Types/ReceiptAcquirerData.php new file mode 100644 index 0000000..c6c4dde --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Types/ReceiptAcquirerData.php @@ -0,0 +1,36 @@ +[]|null + */ + public ?array $products = null; + + /** + * Vat rates. + * + * @var array[]|null + */ + public ?array $vatRates = null; + + /** + * Events + * + * @var ReceiptEvent[]|null + */ + public ?array $events = null; + + /** + * Receipt number + * + * @var string|null + */ + public ?string $receiptNo = null; + +} diff --git a/vendor_prefixed/sumup-sdk/Types/ReceiptTransactionProcessAs.php b/vendor_prefixed/sumup-sdk/Types/ReceiptTransactionProcessAs.php new file mode 100644 index 0000000..c46c360 --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Types/ReceiptTransactionProcessAs.php @@ -0,0 +1,14 @@ +|null + */ + public ?array $metadata = null; + + /** + * The timestamp of when the role was created. + * + * @var string + */ + public string $createdAt; + + /** + * The timestamp of when the role was last updated. + * + * @var string + */ + public string $updatedAt; + +} diff --git a/vendor_prefixed/sumup-sdk/Types/StatusResponse.php b/vendor_prefixed/sumup-sdk/Types/StatusResponse.php new file mode 100644 index 0000000..d0f723a --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Types/StatusResponse.php @@ -0,0 +1,18 @@ +[]|null + */ + public ?array $vatRates = null; + + /** + * Detailed list of events related to the transaction. + * + * @var TransactionEvent[]|null + */ + public ?array $transactionEvents = null; + + /** + * High-level status of the transaction from the merchant's perspective. + * - `PENDING`: The payment has been initiated and is still being processed. A final outcome is not available yet. + * - `SUCCESSFUL`: The payment was completed successfully. + * - `PAID_OUT`: The payment was completed successfully and the funds have already been included in a payout to the merchant. + * - `FAILED`: The payment did not complete successfully. + * - `CANCELLED`: The payment was cancelled or reversed and is no longer payable or payable to the merchant. + * - `CANCEL_FAILED`: An attempt to cancel or reverse the payment was not completed successfully. + * - `REFUNDED`: The payment was refunded in full or in part. + * - `REFUND_FAILED`: An attempt to refund the payment was not completed successfully. + * - `CHARGEBACK`: The payment was subject to a chargeback. + * - `NON_COLLECTION`: The amount could not be collected from the merchant after a chargeback or related adjustment. + * + * @var TransactionFullSimpleStatus|null + */ + public ?TransactionFullSimpleStatus $simpleStatus = null; + + /** + * List of hyperlinks for accessing related resources. + * + * @var Link[]|null + */ + public ?array $links = null; + + /** + * Compact list of events related to the transaction. + * + * @var Event[]|null + */ + public ?array $events = null; + + /** + * Details of the payment location as received from the payment terminal. + * + * @var TransactionFullLocation|null + */ + public ?TransactionFullLocation $location = null; + + /** + * Indicates whether tax deduction is enabled for the transaction. + * + * @var bool|null + */ + public ?bool $taxEnabled = null; + +} diff --git a/vendor_prefixed/sumup-sdk/Types/TransactionFullLocation.php b/vendor_prefixed/sumup-sdk/Types/TransactionFullLocation.php new file mode 100644 index 0000000..fbcd1ab --- /dev/null +++ b/vendor_prefixed/sumup-sdk/Types/TransactionFullLocation.php @@ -0,0 +1,33 @@ +