Basic functionality

This commit is contained in:
Mark Pinkster 2025-12-31 11:41:37 +01:00
parent fcc8adacf8
commit ea7d6ff9b2
663 changed files with 44977 additions and 0 deletions

137
api.php Normal file
View File

@ -0,0 +1,137 @@
<?php
/**
* TELVERO BACKOFFICE - API PROXY (FINAL VERSION WITH ATTRIBUTION FIX)
*/
ini_set('display_errors', 0);
error_reporting(E_ALL);
require __DIR__ . '/vendor/autoload.php';
use Automattic\WooCommerce\Client;
use Mollie\Api\MollieApiClient;
header('Content-Type: application/json');
// --- CONFIGURATIE ---
$postcode_tech_key = '7d9136fb-3633-446d-b5d2-fe26a999bd62';
$mollie_api_key = 'test_sfUxawrmbxWSTdqhmg5vm3hc7yvmNP';
$site_url = 'https://telvero.nl';
$woocommerce = new Client(
$site_url,
'ck_f20c3d254df090816aa552dd312998cffac41866',
'cs_6d2df33dc9e003a3804d20f8079dead853b8e689',
['version' => 'wc/v3', 'timeout' => 400, 'verify_ssl' => false]
);
$action = $_GET['action'] ?? '';
// 1. POSTCODE CHECK
if ($action === 'postcode_check') {
$postcode = str_replace(' ', '', $_GET['postcode']);
$url = "https://postcode.tech/api/v1/postcode?postcode={$postcode}&number=" . $_GET['number'];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer {$postcode_tech_key}"]);
echo curl_exec($ch); curl_close($ch); exit;
}
// 2. PRODUCTEN OPHALEN (Inclusief Variatie Details)
if ($action === 'get_products') {
try {
$products = $woocommerce->get('products', ['status' => 'publish', 'per_page' => 100]);
$enriched = [];
foreach ($products as $product) {
$p = (array)$product;
if ($product->type === 'variable') {
$p['variation_details'] = $woocommerce->get("products/{$product->id}/variations", ['per_page' => 100]);
} else { $p['variation_details'] = []; }
$enriched[] = $p;
}
echo json_encode($enriched);
} catch (Exception $e) { echo json_encode(['error' => $e->getMessage()]); }
exit;
}
// 3. ORDER AANMAKEN
if ($action === 'create_order') {
$input = json_decode(file_get_contents('php://input'), true);
try {
$method_input = $input['payment_method'];
$mediacode = $input['mediacode_internal'] ?? 'Geen';
// Map methoden naar officiële Mollie plugin IDs
if ($method_input === 'mollie_methods_ideal') {
$wc_gateway_id = 'mollie_wc_gateway_ideal';
$mollie_id = 'ideal';
} elseif ($method_input === 'rve_riverty') {
$wc_gateway_id = 'mollie_wc_gateway_riverty';
$mollie_id = 'riverty';
} elseif ($method_input === 'mollie_methods_creditcard') {
$wc_gateway_id = 'mollie_wc_gateway_creditcard';
$mollie_id = 'creditcard';
}
// --- ATTRIBUTION DATA & ORDER INITIALISATIE ---
$input['payment_method'] = $wc_gateway_id;
$input['payment_method_title'] = 'iDEAL (via Mollie)';
$input['customer_note'] = "Bron: Sales Panel | Mediacode: " . $mediacode;
// Metadata toevoegen bij creatie (Direct zichtbaar in Custom Fields)
$input['meta_data'][] = ['key' => 'Mediacode', 'value' => $mediacode];
$input['meta_data'][] = ['key' => 'Origin', 'value' => 'Sales Panel'];
$input['meta_data'][] = ['key' => '_wc_order_attribution_utm_campaign', 'value' => $mediacode];
$input['meta_data'][] = ['key' => '_wc_order_attribution_utm_source', 'value' => 'SalesPanel'];
// A. WooCommerce Order aanmaken
$order = $woocommerce->post('orders', $input);
if (!$order || !isset($order->id)) throw new Exception("WooCommerce order mislukt.");
// B. Mollie Setup
$mollie = new MollieApiClient();
$mollie->setApiKey($mollie_api_key);
$is_sub = (stripos(json_encode($order->line_items), 'abonnement') !== false);
$payment_value = number_format((float)$order->total, 2, '.', '');
if ($mollie_id === 'ideal' && $is_sub) $payment_value = "0.01";
// C. Webhook & Redirect URLs
$webhookUrl = "{$site_url}/wc-api/mollie_wc_gateway_ideal?order_id={$order->id}&key={$order->order_key}&filter_flag=1";
$redirectUrl = "{$site_url}/checkout/order-received/{$order->id}/?key={$order->order_key}&order_id={$order->id}&filter_flag=onMollieReturn&utm_campaign={$mediacode}";
$paymentData = [
"amount" => ["currency" => "EUR", "value" => $payment_value],
"description" => "Order #" . $order->id . " [" . $mediacode . "]",
"redirectUrl" => $redirectUrl,
"webhookUrl" => $webhookUrl,
"method" => $mollie_id,
"metadata" => ["order_id" => (string)$order->id, "mediacode" => $mediacode]
];
$payment = $mollie->payments->create($paymentData);
// D. MATCHING & PERMANENTE MEDIACODE (Update met Mollie ID)
$woocommerce->put("orders/" . $order->id, [
'meta_data' => [
['key' => '_mollie_payment_id', 'value' => $payment->id],
['key' => '_transaction_id', 'value' => $payment->id],
['key' => '_payment_method', 'value' => $wc_gateway_id],
['key' => 'Mediacode', 'value' => $mediacode],
['key' => '_mediacode', 'value' => $mediacode]
]
]);
// E. Email trigger (Customer Note)
$woocommerce->post("orders/{$order->id}/notes", [
'note' => "Betaallink voor uw bestelling: " . $payment->getCheckoutUrl(),
'customer_note' => true
]);
echo json_encode(['id' => $order->id, 'payment_url' => $payment->getCheckoutUrl()]);
} catch (Exception $e) {
http_response_code(422);
echo json_encode(['error' => $e->getMessage()]);
}
exit;
}

6
composer.json Normal file
View File

@ -0,0 +1,6 @@
{
"require": {
"automattic/woocommerce": "^3.1",
"mollie/mollie-api-php": "^3.7"
}
}

478
composer.lock generated Normal file
View File

@ -0,0 +1,478 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "7cef855c8b424b0d2fadd4ba6b160e3f",
"packages": [
{
"name": "automattic/woocommerce",
"version": "3.1.0",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/wc-api-php.git",
"reference": "d3b292f04c0b3b21dced691ebad8be073a83b4ad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/wc-api-php/zipball/d3b292f04c0b3b21dced691ebad8be073a83b4ad",
"reference": "d3b292f04c0b3b21dced691ebad8be073a83b4ad",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"php": ">= 7.1.0"
},
"require-dev": {
"overtrue/phplint": "7.4.x-dev",
"phpunit/phpunit": "^8",
"squizlabs/php_codesniffer": "3.*"
},
"type": "library",
"autoload": {
"psr-4": {
"Automattic\\WooCommerce\\": [
"src/WooCommerce"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Claudio Sanches",
"email": "claudio.sanches@automattic.com"
}
],
"description": "A PHP wrapper for the WooCommerce REST API",
"keywords": [
"api",
"woocommerce"
],
"support": {
"issues": "https://github.com/woocommerce/wc-api-php/issues",
"source": "https://github.com/woocommerce/wc-api-php/tree/3.1.0"
},
"time": "2022-03-18T21:46:17+00:00"
},
{
"name": "composer/ca-bundle",
"version": "1.5.10",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
"reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/961a5e4056dd2e4a2eedcac7576075947c28bf63",
"reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"ext-pcre": "*",
"php": "^7.2 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^8 || ^9",
"psr/log": "^1.0 || ^2.0 || ^3.0",
"symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\CaBundle\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.",
"keywords": [
"cabundle",
"cacert",
"certificate",
"ssl",
"tls"
],
"support": {
"irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/ca-bundle/issues",
"source": "https://github.com/composer/ca-bundle/tree/1.5.10"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
}
],
"time": "2025-12-08T15:06:51+00:00"
},
{
"name": "mollie/mollie-api-php",
"version": "v3.7.0",
"source": {
"type": "git",
"url": "https://github.com/mollie/mollie-api-php.git",
"reference": "55f843fd3944c4c9ed08ac1d5929084305b82b8b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mollie/mollie-api-php/zipball/55f843fd3944c4c9ed08ac1d5929084305b82b8b",
"reference": "55f843fd3944c4c9ed08ac1d5929084305b82b8b",
"shasum": ""
},
"require": {
"composer/ca-bundle": "^1.4",
"ext-curl": "*",
"ext-json": "*",
"ext-openssl": "*",
"nyholm/psr7": "^1.8",
"php": "^7.4|^8.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.1",
"psr/http-message": "^1.1|^2.0"
},
"require-dev": {
"brianium/paratest": "^6.11",
"guzzlehttp/guzzle": "^7.6",
"phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^9.6",
"symfony/var-dumper": "^5.4|^6.4|^7.2"
},
"suggest": {
"mollie/oauth2-mollie-php": "Use OAuth to authenticate with the Mollie API. This is needed for some endpoints. Visit https://docs.mollie.com/ for more information."
},
"type": "library",
"autoload": {
"psr-4": {
"Mollie\\Api\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Mollie B.V.",
"email": "info@mollie.com"
}
],
"description": "Mollie API client library for PHP. Mollie is a European Payment Service provider and offers international payment methods such as Mastercard, VISA, American Express and PayPal, and local payment methods such as iDEAL, Bancontact, SOFORT Banking, SEPA direct debit, Belfius Direct Net, KBC Payment Button and various gift cards such as Podiumcadeaukaart and fashioncheque.",
"homepage": "https://www.mollie.com/en/developers",
"keywords": [
"Apple Pay",
"CBC",
"Przelewy24",
"api",
"bancontact",
"banktransfer",
"belfius",
"belfius direct net",
"charges",
"creditcard",
"direct debit",
"fashioncheque",
"gateway",
"gift cards",
"ideal",
"inghomepay",
"intersolve",
"kbc",
"klarna",
"mistercash",
"mollie",
"paylater",
"payment",
"payments",
"paypal",
"paysafecard",
"podiumcadeaukaart",
"recurring",
"refunds",
"sepa",
"service",
"sliceit",
"sofort",
"sofortbanking",
"subscriptions"
],
"support": {
"issues": "https://github.com/mollie/mollie-api-php/issues",
"source": "https://github.com/mollie/mollie-api-php/tree/v3.7.0"
},
"time": "2025-12-01T09:03:27+00:00"
},
{
"name": "nyholm/psr7",
"version": "1.8.2",
"source": {
"type": "git",
"url": "https://github.com/Nyholm/psr7.git",
"reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3",
"reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3",
"shasum": ""
},
"require": {
"php": ">=7.2",
"psr/http-factory": "^1.0",
"psr/http-message": "^1.1 || ^2.0"
},
"provide": {
"php-http/message-factory-implementation": "1.0",
"psr/http-factory-implementation": "1.0",
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"http-interop/http-factory-tests": "^0.9",
"php-http/message-factory": "^1.0",
"php-http/psr7-integration-tests": "^1.0",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.4",
"symfony/error-handler": "^4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.8-dev"
}
},
"autoload": {
"psr-4": {
"Nyholm\\Psr7\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com"
},
{
"name": "Martijn van der Ven",
"email": "martijn@vanderven.se"
}
],
"description": "A fast PHP7 implementation of PSR-7",
"homepage": "https://tnyholm.se",
"keywords": [
"psr-17",
"psr-7"
],
"support": {
"issues": "https://github.com/Nyholm/psr7/issues",
"source": "https://github.com/Nyholm/psr7/tree/1.8.2"
},
"funding": [
{
"url": "https://github.com/Zegnat",
"type": "github"
},
{
"url": "https://github.com/nyholm",
"type": "github"
}
],
"time": "2024-09-09T07:06:30+00:00"
},
{
"name": "psr/http-client",
"version": "1.0.3",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-client.git",
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
"shasum": ""
},
"require": {
"php": "^7.0 || ^8.0",
"psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP clients",
"homepage": "https://github.com/php-fig/http-client",
"keywords": [
"http",
"http-client",
"psr",
"psr-18"
],
"support": {
"source": "https://github.com/php-fig/http-client"
},
"time": "2023-09-23T14:17:50+00:00"
},
{
"name": "psr/http-factory",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-factory.git",
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"shasum": ""
},
"require": {
"php": ">=7.1",
"psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
"keywords": [
"factory",
"http",
"message",
"psr",
"psr-17",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-factory"
},
"time": "2024-04-15T12:06:14+00:00"
},
{
"name": "psr/http-message",
"version": "2.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-message/tree/2.0"
},
"time": "2023-04-04T09:54:51+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {},
"platform-dev": {},
"plugin-api-version": "2.6.0"
}

204
index.html Normal file
View File

@ -0,0 +1,204 @@
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<title>Telvero Sales Dashboard</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/alpinejs" defer></script>
<style>[x-cloak] { display: none !important; }</style>
</head>
<body class="bg-slate-100 min-h-screen font-sans" x-data="salesApp()" x-init="initData()">
<div class="max-w-[1400px] mx-auto p-6">
<header class="flex justify-between items-center mb-8 bg-white p-6 rounded-2xl shadow-sm border-b-4 border-blue-600">
<h1 class="text-2xl font-black text-slate-800 tracking-tighter italic">TELVERO <span class="text-blue-600">ORDER ENTRY</span></h1>
<div class="flex items-center gap-3">
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest">Bron:</span>
<select x-model="meta.mediacode" :class="meta.mediacode ? 'border-blue-500 bg-blue-50' : 'border-red-500 bg-red-50'" class="border-2 rounded-lg font-bold px-4 py-1 text-blue-700 outline-none transition-colors">
<option value="">-- KIES MEDIACODE --</option>
<option value="TELVERO-NET5">TELVERO-NET5</option>
<option value="TELVERO-SBS6">TELVERO-SBS6</option>
</select>
</div>
</header>
<div class="grid grid-cols-12 gap-6">
<div class="col-span-12 lg:col-span-4 bg-white p-6 rounded-2xl shadow-sm border border-slate-200">
<h2 class="font-bold mb-6 text-slate-400 uppercase text-[10px] tracking-widest border-b pb-2 italic">1. Klantgegevens</h2>
<div class="space-y-4">
<div class="grid grid-cols-2 gap-3">
<input type="text" x-model="form.initials" @blur="formatInitials()" placeholder="Voorletters" class="border p-2.5 rounded-xl w-full outline-none focus:border-blue-400 bg-slate-50">
<input type="text" x-model="form.lastname" @blur="formatLastname()" placeholder="Achternaam" class="border p-2.5 rounded-xl w-full outline-none focus:border-blue-400 bg-slate-50">
</div>
<div class="grid grid-cols-3 gap-2">
<input type="text" x-model="form.postcode" placeholder="Postcode" class="border p-2.5 rounded-xl w-full uppercase font-mono">
<input type="text" x-model="form.houseno" @blur="lookupAddress()" placeholder="Nr." class="border p-2.5 rounded-xl w-full outline-none focus:border-blue-400 bg-slate-50">
<input type="text" x-model="form.suffix" placeholder="Toev." class="border p-2.5 rounded-xl w-full">
</div>
<input type="text" x-model="form.street" placeholder="Straat" class="w-full border p-2.5 rounded-xl bg-slate-200 font-bold text-xs shadow-inner" readonly>
<input type="text" x-model="form.city" placeholder="Stad" class="w-full border p-2.5 rounded-xl bg-slate-200 font-bold text-xs shadow-inner" readonly>
<input type="email" x-model="form.email" placeholder="E-mail (Verplicht voor link)" class="border-2 border-amber-300 p-2.5 rounded-xl w-full outline-none focus:border-blue-500 shadow-sm">
<input type="text" x-model="form.dob" @blur="formatDOB()" placeholder="Geboortedatum (DDMMYYYY)" class="border p-2.5 rounded-xl w-full font-mono">
</div>
</div>
<div class="col-span-12 lg:col-span-5 bg-white p-6 rounded-2xl shadow-sm border border-slate-200">
<h2 class="font-bold mb-6 text-slate-400 uppercase text-[10px] tracking-widest border-b pb-2 italic">2. Productselectie</h2>
<select x-model="selectedProductId" @change="selectProduct()" class="w-full border-2 border-slate-100 p-4 rounded-xl font-black text-slate-700 mb-6 focus:border-blue-500 outline-none bg-slate-50">
<option value="">-- Kies Hoofdproduct --</option>
<template x-for="p in products" :key="p.id">
<option :value="p.id" x-text="p.name"></option>
</template>
</select>
<div x-show="variations && variations.length > 0" x-cloak class="mb-6 p-4 bg-blue-50 rounded-2xl border border-blue-100 shadow-inner">
<select x-model="selectedVariationId" @change="selectVariation()" class="w-full border-2 border-white p-3 rounded-xl font-bold bg-white text-slate-700">
<option value="">-- Kies Optie --</option>
<template x-for="v in variations" :key="v.id">
<option :value="v.id" x-text="getVarName(v) + ' (€' + v.price + ')'"></option>
</template>
</select>
</div>
<div x-show="upsellOptions.length > 0" x-cloak class="space-y-3">
<p class="text-[10px] font-black text-red-500 uppercase tracking-widest italic">Aanbevolen Extra's:</p>
<template x-for="u in upsellOptions" :key="u.id">
<div class="flex items-center justify-between p-3 border rounded-xl bg-slate-50">
<span class="text-xs font-bold" x-text="u.name + ' (€' + u.price + ')'"></span>
<button @click="toggleUpsell(u)" :class="isInCart(u.id) ? 'bg-red-500' : 'bg-green-600'" class="text-white px-5 py-1.5 rounded-xl text-[10px] font-black shadow-md uppercase transition-transform active:scale-95" x-text="isInCart(u.id) ? 'Verwijder' : 'Voeg toe'"></button>
</div>
</template>
</div>
</div>
<div class="col-span-12 lg:col-span-3">
<div class="bg-slate-900 text-white p-7 rounded-[2rem] shadow-2xl sticky top-6 border border-slate-800">
<h2 class="font-bold mb-6 border-b border-slate-800 pb-2 text-[10px] uppercase text-slate-500 tracking-widest">Winkelmand</h2>
<div class="space-y-4 mb-8 min-h-[100px]">
<template x-for="item in cart" :key="item.id + '-' + (item.variation_id || 0)">
<div class="flex justify-between text-[11px] items-start">
<span x-text="item.name" class="opacity-80 leading-tight pr-4"></span>
<span x-text="'€' + item.price" class="font-bold text-blue-400 whitespace-nowrap"></span>
</div>
</template>
</div>
<div class="mb-8 pt-4 border-t border-slate-800 space-y-2">
<button @click="payment_method = 'mollie_methods_ideal'" :class="payment_method === 'mollie_methods_ideal' ? 'bg-blue-600 border-blue-400' : 'bg-slate-800 border-slate-700'" class="w-full text-left p-3 rounded-xl border text-[10px] font-bold">SEPA INCASSO (0.01)</button>
<button @click="payment_method = 'rve_riverty'" :class="payment_method === 'rve_riverty' ? 'bg-orange-600 border-orange-400' : 'bg-slate-800 border-slate-700'" class="w-full text-left p-3 rounded-xl border text-[10px] font-bold uppercase">Riverty Achteraf</button>
</div>
<div class="flex justify-between items-center mb-8 pt-4 border-t border-slate-800">
<span class="text-xs font-bold uppercase tracking-widest text-slate-400">Totaal</span>
<span class="text-3xl font-black text-green-400" x-text="'€' + total"></span>
</div>
<button @click="submitOrder()"
:disabled="submitting || !form.email || !meta.mediacode || cart.length === 0"
:class="(!form.email || !meta.mediacode || cart.length === 0) ? 'bg-slate-700 opacity-50 cursor-not-allowed' : 'bg-blue-600 hover:bg-blue-500 shadow-blue-500/20 shadow-lg'"
class="w-full p-5 rounded-2xl font-black text-lg transition active:scale-95 uppercase tracking-tighter">
<span x-text="submitting ? 'ORDER WORDT VERSTUURD...' : 'ORDER BEVESTIGEN'"></span>
</button>
<p x-show="!meta.mediacode" class="text-[9px] text-red-400 mt-4 text-center font-bold">Vergeet de Mediacode niet!</p>
</div>
</div>
</div>
</div>
<script>
function salesApp() {
return {
products: [], upsellOptions: [], cart: [], activeProduct: null,
selectedProductId: '', selectedVariationId: '', variations: [],
payment_method: 'mollie_methods_ideal', submitting: false, addressLoading: false,
form: { initials: '', lastname: '', postcode: '', houseno: '', suffix: '', street: '', city: '', email: '', dob: '' },
meta: { mediacode: '' },
async initData() {
const res = await fetch('api.php?action=get_products');
this.products = await res.json();
},
async lookupAddress() {
if (this.form.postcode.length >= 6 && this.form.houseno) {
this.addressLoading = true;
const res = await fetch(`api.php?action=postcode_check&postcode=${this.form.postcode}&number=${this.form.houseno}`);
const data = await res.json();
if (data.street) { this.form.street = data.street.toUpperCase(); this.form.city = data.city.toUpperCase(); }
this.addressLoading = false;
}
},
selectProduct() {
const p = this.products.find(x => x.id == this.selectedProductId);
if(!p) return;
this.activeProduct = p; this.variations = []; this.cart = []; this.upsellOptions = []; this.selectedVariationId = '';
if (p.type === 'variable' && p.variation_details) {
this.variations = p.variation_details;
} else {
this.cart = [{ id: parseInt(p.id), name: p.name, price: p.price }];
if (p.upsell_ids) this.upsellOptions = this.products.filter(x => p.upsell_ids.includes(x.id));
}
},
selectVariation() {
const v = this.variations.find(x => x.id == this.selectedVariationId);
if(!v) return;
this.cart = [{
id: parseInt(this.activeProduct.id), variation_id: parseInt(v.id),
name: this.activeProduct.name + ' (' + v.attributes.map(a => a.option).join(' ') + ')', price: v.price
}];
if (this.activeProduct.upsell_ids) this.upsellOptions = this.products.filter(x => this.activeProduct.upsell_ids.includes(x.id));
},
getVarName(v) { return v.attributes.map(a => a.option).join(' '); },
toggleUpsell(u) {
const idx = this.cart.findIndex(i => i.id === u.id);
idx > -1 ? this.cart.splice(idx, 1) : this.cart.push({ id: parseInt(u.id), name: u.name, price: u.price });
},
isInCart(id) { return this.cart.some(i => i.id === id); },
get total() { return this.cart.reduce((s, i) => s + parseFloat(i.price), 0).toFixed(2); },
formatInitials() {
let v = this.form.initials.replace(/[^a-z]/gi, '').toUpperCase();
this.form.initials = v.split('').join('.') + (v ? '.' : '');
},
formatLastname() { this.form.lastname = this.form.lastname.charAt(0).toUpperCase() + this.form.lastname.slice(1); },
formatDOB() {
let d = this.form.dob.replace(/[^0-9]/g, '');
if(d.length === 8) this.form.dob = `${d.slice(0,2)}-${d.slice(2,4)}-${d.slice(4,8)}`;
},
async submitOrder() {
this.submitting = true;
const payload = {
payment_method: this.payment_method,
mediacode_internal: this.meta.mediacode,
billing: {
first_name: this.form.initials || '', last_name: this.form.lastname || '',
address_1: (this.form.street + ' ' + this.form.houseno + ' ' + (this.form.suffix || '')).trim(),
city: this.form.city || '', postcode: this.form.postcode || '', country: 'NL', email: this.form.email
},
line_items: this.cart.map(i => ({ product_id: parseInt(i.id), variation_id: parseInt(i.variation_id) || 0, quantity: 1 })),
meta_data: [
{ key: '_mediacode', value: this.meta.mediacode },
{ key: '_billing_birthdate', value: this.form.dob }
]
};
try {
const res = await fetch('api.php?action=create_order', { method: 'POST', body: JSON.stringify(payload) });
const result = await res.json();
if(result.payment_url) {
alert("SUCCESVOL! De bestelling is geplaatst en de link is gemaild.");
window.location.reload();
} else { alert("Fout: " + result.error); }
} catch(e) { alert("Systeemfout: " + e.message); }
this.submitting = false;
}
}
}
</script>
</body>
</html>

22
vendor/autoload.php vendored Normal file
View File

@ -0,0 +1,22 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
throw new RuntimeException($err);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInitcd53d4cc421fe0b70226223f299ac0ad::getLoader();

21
vendor/automattic/woocommerce/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016, Automattic (https://automattic.com/)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

169
vendor/automattic/woocommerce/README.md vendored Normal file
View File

@ -0,0 +1,169 @@
# WooCommerce API - PHP Client
A PHP wrapper for the WooCommerce REST API. Easily interact with the WooCommerce REST API securely using this library. If using a HTTPS connection this library uses BasicAuth, else it uses Oauth to provide a secure connection to WooCommerce.
[![CI status](https://github.com/woocommerce/wc-api-php/actions/workflows/ci.yml/badge.svg?branch=trunk)](https://github.com/woocommerce/wc-api-php/actions/workflows/ci.yml)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/woocommerce/wc-api-php/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/woocommerce/wc-api-php/?branch=master)
[![PHP version](https://badge.fury.io/ph/automattic%2Fwoocommerce.svg)](https://packagist.org/packages/automattic/woocommerce)
## Installation
```
composer require automattic/woocommerce
```
## Getting started
Generate API credentials (Consumer Key & Consumer Secret) following this instructions <http://docs.woocommerce.com/document/woocommerce-rest-api/>
.
Check out the WooCommerce API endpoints and data that can be manipulated in <https://woocommerce.github.io/woocommerce-rest-api-docs/>.
## Setup
Setup for the new WP REST API integration (WooCommerce 2.6 or later):
```php
require __DIR__ . '/vendor/autoload.php';
use Automattic\WooCommerce\Client;
$woocommerce = new Client(
'http://example.com',
'ck_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
'cs_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
[
'version' => 'wc/v3',
]
);
```
## Client class
```php
$woocommerce = new Client($url, $consumer_key, $consumer_secret, $options);
```
### Options
| Option | Type | Required | Description |
| ----------------- | -------- | -------- | ------------------------------------------ |
| `url` | `string` | yes | Your Store URL, example: http://woo.dev/ |
| `consumer_key` | `string` | yes | Your API consumer key |
| `consumer_secret` | `string` | yes | Your API consumer secret |
| `options` | `array` | no | Extra arguments (see client options table) |
#### Client options
| Option | Type | Required | Description |
| ------------------------ | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `version` | `string` | no | API version, default is `wc/v3` |
| `timeout` | `int` | no | Request timeout, default is `15` |
| `verify_ssl` | `bool` | no | Verify SSL when connect, use this option as `false` when need to test with self-signed certificates, default is `true` |
| `follow_redirects` | `bool` | no | Allow the API call to follow redirects |
| `query_string_auth` | `bool` | no | Force Basic Authentication as query string when `true` and using under HTTPS, default is `false` |
| `oauth_timestamp` | `string` | no | Custom oAuth timestamp, default is `time()` |
| `oauth_only` | `bool` | no | Only use oauth for requests, it will disable Basic Auth, default is `false` |
| `user_agent` | `string` | no | Custom user-agent, default is `WooCommerce API Client-PHP` |
| `wp_api_prefix` | `string` | no | Custom WP REST API URL prefix, used to support custom prefixes created with the `rest_url_prefix` filter |
| `wp_api` | `bool` | no | Set to `false` in order to use the legacy WooCommerce REST API (deprecated and not recommended) |
| `method_override_query` | `bool` | no | If true will mask all non-GET/POST methods by using POST method with added query parameter `?_method=METHOD` into URL |
| `method_override_header` | `bool` | no | If true will mask all non-GET/POST methods (PUT/DELETE/etc.) by using POST method with added `X-HTTP-Method-Override: METHOD` HTTP header into request |
## Client methods
### GET
```php
$woocommerce->get($endpoint, $parameters = []);
```
### POST
```php
$woocommerce->post($endpoint, $data);
```
### PUT
```php
$woocommerce->put($endpoint, $data);
```
### DELETE
```php
$woocommerce->delete($endpoint, $parameters = []);
```
### OPTIONS
```php
$woocommerce->options($endpoint);
```
#### Arguments
| Params | Type | Description |
| ------------ | -------- | ------------------------------------------------------------ |
| `endpoint` | `string` | WooCommerce API endpoint, example: `customers` or `order/12` |
| `data` | `array` | Only for POST and PUT, data that will be converted to JSON |
| `parameters` | `array` | Only for GET and DELETE, request query string |
#### Response
All methods will return arrays on success or throwing `HttpClientException` errors on failure.
```php
use Automattic\WooCommerce\HttpClient\HttpClientException;
try {
// Array of response results.
$results = $woocommerce->get('customers');
// Example: ['customers' => [[ 'id' => 8, 'created_at' => '2015-05-06T17:43:51Z', 'email' => ...
echo '<pre><code>' . print_r($results, true) . '</code><pre>'; // JSON output.
// Last request data.
$lastRequest = $woocommerce->http->getRequest();
echo '<pre><code>' . print_r($lastRequest->getUrl(), true) . '</code><pre>'; // Requested URL (string).
echo '<pre><code>' .
print_r($lastRequest->getMethod(), true) .
'</code><pre>'; // Request method (string).
echo '<pre><code>' .
print_r($lastRequest->getParameters(), true) .
'</code><pre>'; // Request parameters (array).
echo '<pre><code>' .
print_r($lastRequest->getHeaders(), true) .
'</code><pre>'; // Request headers (array).
echo '<pre><code>' . print_r($lastRequest->getBody(), true) . '</code><pre>'; // Request body (JSON).
// Last response data.
$lastResponse = $woocommerce->http->getResponse();
echo '<pre><code>' . print_r($lastResponse->getCode(), true) . '</code><pre>'; // Response code (int).
echo '<pre><code>' .
print_r($lastResponse->getHeaders(), true) .
'</code><pre>'; // Response headers (array).
echo '<pre><code>' . print_r($lastResponse->getBody(), true) . '</code><pre>'; // Response body (JSON).
} catch (HttpClientException $e) {
echo '<pre><code>' . print_r($e->getMessage(), true) . '</code><pre>'; // Error message.
echo '<pre><code>' . print_r($e->getRequest(), true) . '</code><pre>'; // Last request data.
echo '<pre><code>' . print_r($e->getResponse(), true) . '</code><pre>'; // Last response data.
}
```
## Release History
- 2022-03-18 - 3.1.0 - Added new options to support `_method` and `X-HTTP-Method-Override` from WP, supports 7+, dropped support to PHP 5.
- 2019-01-16 - 3.0.0 - Legacy API turned off by default, and improved JSON error handler.
- 2018-03-29 - 2.0.1 - Fixed fatal errors on `lookForErrors`.
- 2018-01-12 - 2.0.0 - Responses changes from arrays to `stdClass` objects. Added `follow_redirects` option.
- 2017-06-06 - 1.3.0 - Remove BOM before decoding and added support for multi-dimensional arrays for oAuth1.0a.
- 2017-03-15 - 1.2.0 - Added `user_agent` option.
- 2016-12-14 - 1.1.4 - Fixed WordPress 4.7 compatibility.
- 2016-10-26 - 1.1.3 - Allow set `oauth_timestamp` and improved how is handled the response headers.
- 2016-09-30 - 1.1.2 - Added `wp_api_prefix` option to allow custom WP REST API URL prefix.
- 2016-05-10 - 1.1.1 - Fixed oAuth and error handler for WP REST API.
- 2016-05-09 - 1.1.0 - Added support for WP REST API, added method `Automattic\WooCommerce\Client::options` and fixed multiple headers responses.
- 2016-01-25 - 1.0.2 - Fixed an error when getting data containing non-latin characters.
- 2016-01-21 - 1.0.1 - Sort all oAuth parameters before build request URLs.
- 2016-01-11 - 1.0.0 - Stable release.

View File

@ -0,0 +1,38 @@
{
"name": "automattic/woocommerce",
"description": "A PHP wrapper for the WooCommerce REST API",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Claudio Sanches",
"email": "claudio.sanches@automattic.com"
}
],
"minimum-stability": "dev",
"keywords": [
"API",
"WooCommerce"
],
"require": {
"php": ">= 7.1.0",
"ext-curl": "*",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^8",
"squizlabs/php_codesniffer": "3.*",
"overtrue/phplint": "7.4.x-dev"
},
"autoload": {
"psr-4": {
"Automattic\\WooCommerce\\": ["src/WooCommerce"]
}
},
"autoload-dev": {
"psr-4": {
"Automattic\\WooCommerce\\LegacyTests\\": "tests/legacy-php/WooCommerce/Tests",
"Automattic\\WooCommerce\\Tests\\": "tests/php/WooCommerce/Tests"
}
}
}

View File

@ -0,0 +1,109 @@
<?php
/**
* WooCommerce REST API Client
*
* @category Client
* @package Automattic/WooCommerce
*/
namespace Automattic\WooCommerce;
use Automattic\WooCommerce\HttpClient\HttpClient;
/**
* REST API Client class.
*
* @package Automattic/WooCommerce
*/
class Client
{
/**
* WooCommerce REST API Client version.
*/
public const VERSION = '3.1.0';
/**
* HttpClient instance.
*
* @var HttpClient
*/
public $http;
/**
* Initialize client.
*
* @param string $url Store URL.
* @param string $consumerKey Consumer key.
* @param string $consumerSecret Consumer secret.
* @param array $options Options (version, timeout, verify_ssl, oauth_only).
*/
public function __construct($url, $consumerKey, $consumerSecret, $options = [])
{
$this->http = new HttpClient($url, $consumerKey, $consumerSecret, $options);
}
/**
* POST method.
*
* @param string $endpoint API endpoint.
* @param array $data Request data.
*
* @return \stdClass
*/
public function post($endpoint, $data)
{
return $this->http->request($endpoint, 'POST', $data);
}
/**
* PUT method.
*
* @param string $endpoint API endpoint.
* @param array $data Request data.
*
* @return \stdClass
*/
public function put($endpoint, $data)
{
return $this->http->request($endpoint, 'PUT', $data);
}
/**
* GET method.
*
* @param string $endpoint API endpoint.
* @param array $parameters Request parameters.
*
* @return \stdClass
*/
public function get($endpoint, $parameters = [])
{
return $this->http->request($endpoint, 'GET', [], $parameters);
}
/**
* DELETE method.
*
* @param string $endpoint API endpoint.
* @param array $parameters Request parameters.
*
* @return \stdClass
*/
public function delete($endpoint, $parameters = [])
{
return $this->http->request($endpoint, 'DELETE', [], $parameters);
}
/**
* OPTIONS method.
*
* @param string $endpoint API endpoint.
*
* @return \stdClass
*/
public function options($endpoint)
{
return $this->http->request($endpoint, 'OPTIONS', [], []);
}
}

View File

@ -0,0 +1,96 @@
<?php
/**
* WooCommerce Basic Authentication
*
* @category HttpClient
* @package Automattic/WooCommerce
*/
namespace Automattic\WooCommerce\HttpClient;
/**
* Basic Authentication class.
*
* @package Automattic/WooCommerce
*/
class BasicAuth
{
/**
* cURL handle.
*
* @var resource
*/
protected $ch;
/**
* Consumer key.
*
* @var string
*/
protected $consumerKey;
/**
* Consumer secret.
*
* @var string
*/
protected $consumerSecret;
/**
* Do query string auth.
*
* @var bool
*/
protected $doQueryString;
/**
* Request parameters.
*
* @var array
*/
protected $parameters;
/**
* Initialize Basic Authentication class.
*
* @param resource $ch cURL handle.
* @param string $consumerKey Consumer key.
* @param string $consumerSecret Consumer Secret.
* @param bool $doQueryString Do or not query string auth.
* @param array $parameters Request parameters.
*/
public function __construct($ch, $consumerKey, $consumerSecret, $doQueryString, $parameters = [])
{
$this->ch = $ch;
$this->consumerKey = $consumerKey;
$this->consumerSecret = $consumerSecret;
$this->doQueryString = $doQueryString;
$this->parameters = $parameters;
$this->processAuth();
}
/**
* Process auth.
*/
protected function processAuth()
{
if ($this->doQueryString) {
$this->parameters['consumer_key'] = $this->consumerKey;
$this->parameters['consumer_secret'] = $this->consumerSecret;
} else {
\curl_setopt($this->ch, CURLOPT_USERPWD, $this->consumerKey . ':' . $this->consumerSecret);
}
}
/**
* Get parameters.
*
* @return array
*/
public function getParameters()
{
return $this->parameters;
}
}

View File

@ -0,0 +1,487 @@
<?php
/**
* WooCommerce REST API HTTP Client
*
* @category HttpClient
* @package Automattic/WooCommerce
*/
namespace Automattic\WooCommerce\HttpClient;
use Automattic\WooCommerce\Client;
use Automattic\WooCommerce\HttpClient\BasicAuth;
use Automattic\WooCommerce\HttpClient\HttpClientException;
use Automattic\WooCommerce\HttpClient\OAuth;
use Automattic\WooCommerce\HttpClient\Options;
use Automattic\WooCommerce\HttpClient\Request;
use Automattic\WooCommerce\HttpClient\Response;
/**
* REST API HTTP Client class.
*
* @package Automattic/WooCommerce
*/
class HttpClient
{
/**
* cURL handle.
*
* @var resource
*/
protected $ch;
/**
* Store API URL.
*
* @var string
*/
protected $url;
/**
* Consumer key.
*
* @var string
*/
protected $consumerKey;
/**
* Consumer secret.
*
* @var string
*/
protected $consumerSecret;
/**
* Client options.
*
* @var Options
*/
protected $options;
/**
* The custom cURL options to use in the requests.
*
* @var array
*/
private $customCurlOptions = [];
/**
* Request.
*
* @var Request
*/
private $request;
/**
* Response.
*
* @var Response
*/
private $response;
/**
* Response headers.
*
* @var string
*/
private $responseHeaders;
/**
* Initialize HTTP client.
*
* @param string $url Store URL.
* @param string $consumerKey Consumer key.
* @param string $consumerSecret Consumer Secret.
* @param array $options Client options.
*/
public function __construct($url, $consumerKey, $consumerSecret, $options)
{
if (!\function_exists('curl_version')) {
throw new HttpClientException('cURL is NOT installed on this server', -1, new Request(), new Response());
}
$this->options = new Options($options);
$this->url = $this->buildApiUrl($url);
$this->consumerKey = $consumerKey;
$this->consumerSecret = $consumerSecret;
}
/**
* Check if is under SSL.
*
* @return bool
*/
protected function isSsl()
{
return 'https://' === \substr($this->url, 0, 8);
}
/**
* Build API URL.
*
* @param string $url Store URL.
*
* @return string
*/
protected function buildApiUrl($url)
{
$api = $this->options->isWPAPI() ? $this->options->apiPrefix() : '/wc-api/';
return \rtrim($url, '/') . $api . $this->options->getVersion() . '/';
}
/**
* Build URL.
*
* @param string $url URL.
* @param array $parameters Query string parameters.
*
* @return string
*/
protected function buildUrlQuery($url, $parameters = [])
{
if (!empty($parameters)) {
if (false !== strpos($url, '?')) {
$url .= '&' . \http_build_query($parameters);
} else {
$url .= '?' . \http_build_query($parameters);
}
}
return $url;
}
/**
* Authenticate.
*
* @param string $url Request URL.
* @param string $method Request method.
* @param array $parameters Request parameters.
*
* @return array
*/
protected function authenticate($url, $method, $parameters = [])
{
// Setup authentication.
if (!$this->options->isOAuthOnly() && $this->isSsl()) {
$basicAuth = new BasicAuth(
$this->ch,
$this->consumerKey,
$this->consumerSecret,
$this->options->isQueryStringAuth(),
$parameters
);
$parameters = $basicAuth->getParameters();
} else {
$oAuth = new OAuth(
$url,
$this->consumerKey,
$this->consumerSecret,
$this->options->getVersion(),
$method,
$parameters,
$this->options->oauthTimestamp()
);
$parameters = $oAuth->getParameters();
}
return $parameters;
}
/**
* Setup method.
*
* @param string $method Request method.
*/
protected function setupMethod($method)
{
if ('POST' == $method) {
\curl_setopt($this->ch, CURLOPT_POST, true);
} elseif (\in_array($method, ['PUT', 'DELETE', 'OPTIONS'])) {
\curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $method);
}
}
/**
* Get request headers.
*
* @param bool $sendData If request send data or not.
*
* @return array
*/
protected function getRequestHeaders($sendData = false)
{
$headers = [
'Accept' => 'application/json',
'User-Agent' => $this->options->userAgent() . '/' . Client::VERSION,
];
if ($sendData) {
$headers['Content-Type'] = 'application/json;charset=utf-8';
}
return $headers;
}
/**
* Create request.
*
* @param string $endpoint Request endpoint.
* @param string $method Request method.
* @param array $data Request data.
* @param array $parameters Request parameters.
*
* @return Request
*/
protected function createRequest($endpoint, $method, $data = [], $parameters = [])
{
$body = '';
$url = $this->url . $endpoint;
$hasData = !empty($data);
$headers = $this->getRequestHeaders($hasData);
// HTTP method override feature which masks PUT and DELETE HTTP methods as POST method with added
// ?_method=PUT query parameter and/or X-HTTP-Method-Override HTTP header.
if (!in_array($method, ['GET', 'POST'])) {
$usePostMethod = false;
if ($this->options->isMethodOverrideQuery()) {
$parameters = array_merge(['_method' => $method], $parameters);
$usePostMethod = true;
}
if ($this->options->isMethodOverrideHeader()) {
$headers['X-HTTP-Method-Override'] = $method;
$usePostMethod = true;
}
if ($usePostMethod) {
$method = 'POST';
}
}
// Setup authentication.
$parameters = $this->authenticate($url, $method, $parameters);
// Setup method.
$this->setupMethod($method);
// Include post fields.
if ($hasData) {
$body = \json_encode($data);
\curl_setopt($this->ch, CURLOPT_POSTFIELDS, $body);
}
$this->request = new Request(
$this->buildUrlQuery($url, $parameters),
$method,
$parameters,
$headers,
$body
);
return $this->getRequest();
}
/**
* Get response headers.
*
* @return array
*/
protected function getResponseHeaders()
{
$headers = [];
$lines = \explode("\n", $this->responseHeaders);
$lines = \array_filter($lines, 'trim');
foreach ($lines as $index => $line) {
// Remove HTTP/xxx params.
if (strpos($line, ': ') === false) {
continue;
}
list($key, $value) = \explode(': ', $line);
$headers[$key] = isset($headers[$key]) ? $headers[$key] . ', ' . trim($value) : trim($value);
}
return $headers;
}
/**
* Create response.
*
* @return Response
*/
protected function createResponse()
{
// Set response headers.
$this->responseHeaders = '';
\curl_setopt($this->ch, CURLOPT_HEADERFUNCTION, function ($_, $headers) {
$this->responseHeaders .= $headers;
return \strlen($headers);
});
// Get response data.
$body = \curl_exec($this->ch);
$code = \curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
$headers = $this->getResponseHeaders();
// Register response.
$this->response = new Response($code, $headers, $body);
return $this->getResponse();
}
/**
* Set default cURL settings.
*/
protected function setDefaultCurlSettings()
{
$verifySsl = $this->options->verifySsl();
$timeout = $this->options->getTimeout();
$followRedirects = $this->options->getFollowRedirects();
\curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, $verifySsl);
if (!$verifySsl) {
\curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, $verifySsl);
}
if ($followRedirects) {
\curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, true);
}
\curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, $timeout);
\curl_setopt($this->ch, CURLOPT_TIMEOUT, $timeout);
\curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
\curl_setopt($this->ch, CURLOPT_HTTPHEADER, $this->request->getRawHeaders());
\curl_setopt($this->ch, CURLOPT_URL, $this->request->getUrl());
foreach ($this->customCurlOptions as $customCurlOptionKey => $customCurlOptionValue) {
\curl_setopt($this->ch, $customCurlOptionKey, $customCurlOptionValue);
}
}
/**
* Look for errors in the request.
*
* @param array $parsedResponse Parsed body response.
*/
protected function lookForErrors($parsedResponse)
{
// Any non-200/201/202 response code indicates an error.
if (!\in_array($this->response->getCode(), ['200', '201', '202'])) {
$errors = isset($parsedResponse->errors) ? $parsedResponse->errors : $parsedResponse;
$errorMessage = '';
$errorCode = '';
if (is_array($errors)) {
$errorMessage = $errors[0]->message;
$errorCode = $errors[0]->code;
} elseif (isset($errors->message, $errors->code)) {
$errorMessage = $errors->message;
$errorCode = $errors->code;
}
throw new HttpClientException(
\sprintf('Error: %s [%s]', $errorMessage, $errorCode),
$this->response->getCode(),
$this->request,
$this->response
);
}
}
/**
* Process response.
*
* @return \stdClass
*/
protected function processResponse()
{
$body = $this->response->getBody();
// Look for UTF-8 BOM and remove.
if (0 === strpos(bin2hex(substr($body, 0, 4)), 'efbbbf')) {
$body = substr($body, 3);
}
$parsedResponse = \json_decode($body);
// Test if return a valid JSON.
if (JSON_ERROR_NONE !== json_last_error()) {
$message = function_exists('json_last_error_msg') ? json_last_error_msg() : 'Invalid JSON returned';
throw new HttpClientException(
sprintf('JSON ERROR: %s', $message),
$this->response->getCode(),
$this->request,
$this->response
);
}
$this->lookForErrors($parsedResponse);
return $parsedResponse;
}
/**
* Make requests.
*
* @param string $endpoint Request endpoint.
* @param string $method Request method.
* @param array $data Request data.
* @param array $parameters Request parameters.
*
* @return \stdClass
*/
public function request($endpoint, $method, $data = [], $parameters = [])
{
// Initialize cURL.
$this->ch = \curl_init();
// Set request args.
$request = $this->createRequest($endpoint, $method, $data, $parameters);
// Default cURL settings.
$this->setDefaultCurlSettings();
// Get response.
$response = $this->createResponse();
// Check for cURL errors.
if (\curl_errno($this->ch)) {
throw new HttpClientException('cURL Error: ' . \curl_error($this->ch), 0, $request, $response);
}
\curl_close($this->ch);
return $this->processResponse();
}
/**
* Get request data.
*
* @return Request
*/
public function getRequest()
{
return $this->request;
}
/**
* Get response data.
*
* @return Response
*/
public function getResponse()
{
return $this->response;
}
/**
* Set custom cURL options to use in requests.
*
* @param array $curlOptions
*/
public function setCustomCurlOptions(array $curlOptions)
{
$this->customCurlOptions = $curlOptions;
}
}

View File

@ -0,0 +1,71 @@
<?php
/**
* WooCommerce REST API HTTP Client Exception
*
* @category HttpClient
* @package Automattic/WooCommerce
*/
namespace Automattic\WooCommerce\HttpClient;
use Automattic\WooCommerce\HttpClient\Request;
use Automattic\WooCommerce\HttpClient\Response;
/**
* REST API HTTP Client Exception class.
*
* @package Automattic/WooCommerce
*/
class HttpClientException extends \Exception
{
/**
* Request.
*
* @var Request
*/
private $request;
/**
* Response.
*
* @var Response
*/
private $response;
/**
* Initialize exception.
*
* @param string $message Error message.
* @param int $code Error code.
* @param Request $request Request data.
* @param Response $response Response data.
*/
public function __construct($message, $code, Request $request, Response $response)
{
parent::__construct($message, $code);
$this->request = $request;
$this->response = $response;
}
/**
* Get request data.
*
* @return Request
*/
public function getRequest()
{
return $this->request;
}
/**
* Get response data.
*
* @return Response
*/
public function getResponse()
{
return $this->response;
}
}

View File

@ -0,0 +1,268 @@
<?php
/**
* WooCommerce oAuth1.0
*
* @category HttpClient
* @package Automattic/WooCommerce
*/
namespace Automattic\WooCommerce\HttpClient;
/**
* oAuth1.0 class.
*
* @package Automattic/WooCommerce
*/
class OAuth
{
/**
* OAuth signature method algorithm.
*/
public const HASH_ALGORITHM = 'SHA256';
/**
* API endpoint URL.
*
* @var string
*/
protected $url;
/**
* Consumer key.
*
* @var string
*/
protected $consumerKey;
/**
* Consumer secret.
*
* @var string
*/
protected $consumerSecret;
/**
* API version.
*
* @var string
*/
protected $apiVersion;
/**
* Request method.
*
* @var string
*/
protected $method;
/**
* Request parameters.
*
* @var array
*/
protected $parameters;
/**
* Timestamp.
*
* @var string
*/
protected $timestamp;
/**
* Initialize oAuth class.
*
* @param string $url Store URL.
* @param string $consumerKey Consumer key.
* @param string $consumerSecret Consumer Secret.
* @param string $method Request method.
* @param string $apiVersion API version.
* @param array $parameters Request parameters.
* @param string $timestamp Timestamp.
*/
public function __construct(
$url,
$consumerKey,
$consumerSecret,
$apiVersion,
$method,
$parameters = [],
$timestamp = ''
) {
$this->url = $url;
$this->consumerKey = $consumerKey;
$this->consumerSecret = $consumerSecret;
$this->apiVersion = $apiVersion;
$this->method = $method;
$this->parameters = $parameters;
$this->timestamp = $timestamp;
}
/**
* Encode according to RFC 3986.
*
* @param string|array $value Value to be normalized.
*
* @return string
*/
protected function encode($value)
{
if (is_array($value)) {
return array_map([$this, 'encode'], $value);
} else {
return str_replace(['+', '%7E'], [' ', '~'], rawurlencode($value));
}
}
/**
* Normalize parameters.
*
* @param array $parameters Parameters to normalize.
*
* @return array
*/
protected function normalizeParameters($parameters)
{
$normalized = [];
foreach ($parameters as $key => $value) {
// Percent symbols (%) must be double-encoded.
$key = $this->encode($key);
$value = $this->encode($value);
$normalized[$key] = $value;
}
return $normalized;
}
/**
* Process filters.
*
* @param array $parameters Request parameters.
*
* @return array
*/
protected function processFilters($parameters)
{
if (isset($parameters['filter'])) {
$filters = $parameters['filter'];
unset($parameters['filter']);
foreach ($filters as $filter => $value) {
$parameters['filter[' . $filter . ']'] = $value;
}
}
return $parameters;
}
/**
* Get secret.
*
* @return string
*/
protected function getSecret()
{
$secret = $this->consumerSecret;
// Fix secret for v3 or later.
if (!\in_array($this->apiVersion, ['v1', 'v2'])) {
$secret .= '&';
}
return $secret;
}
/**
* Generate oAuth1.0 signature.
*
* @param array $parameters Request parameters including oauth.
*
* @return string
*/
protected function generateOauthSignature($parameters)
{
$baseRequestUri = \rawurlencode($this->url);
// Extract filters.
$parameters = $this->processFilters($parameters);
// Normalize parameter key/values and sort them.
$parameters = $this->normalizeParameters($parameters);
$parameters = $this->getSortedParameters($parameters);
// Set query string.
$queryString = \implode('%26', $this->joinWithEqualsSign($parameters)); // Join with ampersand.
$stringToSign = $this->method . '&' . $baseRequestUri . '&' . $queryString;
$secret = $this->getSecret();
return \base64_encode(\hash_hmac(self::HASH_ALGORITHM, $stringToSign, $secret, true));
}
/**
* Creates an array of urlencoded strings out of each array key/value pairs.
*
* @param array $params Array of parameters to convert.
* @param array $queryParams Array to extend.
* @param string $key Optional Array key to append
* @return string Array of urlencoded strings
*/
protected function joinWithEqualsSign($params, $queryParams = [], $key = '')
{
foreach ($params as $paramKey => $paramValue) {
if ($key) {
$paramKey = $key . '%5B' . $paramKey . '%5D'; // Handle multi-dimensional array.
}
if (is_array($paramValue)) {
$queryParams = $this->joinWithEqualsSign($paramValue, $queryParams, $paramKey);
} else {
$string = $paramKey . '=' . $paramValue; // Join with equals sign.
$queryParams[] = $this->encode($string);
}
}
return $queryParams;
}
/**
* Sort parameters.
*
* @param array $parameters Parameters to sort in byte-order.
*
* @return array
*/
protected function getSortedParameters($parameters)
{
\uksort($parameters, 'strcmp');
foreach ($parameters as $key => $value) {
if (\is_array($value)) {
\uksort($parameters[$key], 'strcmp');
}
}
return $parameters;
}
/**
* Get oAuth1.0 parameters.
*
* @return string
*/
public function getParameters()
{
$parameters = \array_merge($this->parameters, [
'oauth_consumer_key' => $this->consumerKey,
'oauth_timestamp' => $this->timestamp,
'oauth_nonce' => \sha1(\microtime()),
'oauth_signature_method' => 'HMAC-' . self::HASH_ALGORITHM,
]);
// The parameters above must be included in the signature generation.
$parameters['oauth_signature'] = $this->generateOauthSignature($parameters);
return $this->getSortedParameters($parameters);
}
}

View File

@ -0,0 +1,182 @@
<?php
/**
* WooCommerce REST API HTTP Client Options
*
* @category HttpClient
* @package Automattic/WooCommerce
*/
namespace Automattic\WooCommerce\HttpClient;
/**
* REST API HTTP Client Options class.
*
* @package Automattic/WooCommerce
*/
class Options
{
/**
* Default WooCommerce REST API version.
*
* @var string
*/
public const VERSION = 'wc/v3';
/**
* Default request timeout.
*/
public const TIMEOUT = 15;
/**
* Default WP API prefix.
* Including leading and trailing slashes.
*/
public const WP_API_PREFIX = '/wp-json/';
/**
* Default User Agent.
* No version number.
*/
public const USER_AGENT = 'WooCommerce API Client-PHP';
/**
* Options.
*
* @var array
*/
private $options;
/**
* Initialize HTTP client options.
*
* @param array $options Client options.
*/
public function __construct($options)
{
$this->options = $options;
}
/**
* Get API version.
*
* @return string
*/
public function getVersion()
{
return isset($this->options['version']) ? $this->options['version'] : self::VERSION;
}
/**
* Check if need to verify SSL.
*
* @return bool
*/
public function verifySsl()
{
return isset($this->options['verify_ssl']) ? (bool) $this->options['verify_ssl'] : true;
}
/**
* Only use OAuth.
*
* @return bool
*/
public function isOAuthOnly()
{
return isset($this->options['oauth_only']) ? (bool) $this->options['oauth_only'] : false;
}
/**
* Get timeout.
*
* @return int
*/
public function getTimeout()
{
return isset($this->options['timeout']) ? (int) $this->options['timeout'] : self::TIMEOUT;
}
/**
* Basic Authentication as query string.
* Some old servers are not able to use CURLOPT_USERPWD.
*
* @return bool
*/
public function isQueryStringAuth()
{
return isset($this->options['query_string_auth']) ? (bool) $this->options['query_string_auth'] : false;
}
/**
* Check if is WP REST API.
*
* @return bool
*/
public function isWPAPI()
{
return isset($this->options['wp_api']) ? (bool) $this->options['wp_api'] : true;
}
/**
* Custom API Prefix for WP API.
*
* @return string
*/
public function apiPrefix()
{
return isset($this->options['wp_api_prefix']) ? $this->options['wp_api_prefix'] : self::WP_API_PREFIX;
}
/**
* oAuth timestamp.
*
* @return string
*/
public function oauthTimestamp()
{
return isset($this->options['oauth_timestamp']) ? $this->options['oauth_timestamp'] : \time();
}
/**
* Custom user agent.
*
* @return string
*/
public function userAgent()
{
return isset($this->options['user_agent']) ? $this->options['user_agent'] : self::USER_AGENT;
}
/**
* Get follow redirects.
*
* @return bool
*/
public function getFollowRedirects()
{
return isset($this->options['follow_redirects']) ? (bool) $this->options['follow_redirects'] : false;
}
/**
* Check is it needed to mask all non-GET/POST methods (PUT/DELETE/etc.) by using POST method with added
* query parameter ?_method=METHOD into URL.
*
* @return bool
*/
public function isMethodOverrideQuery()
{
return isset($this->options['method_override_query']) && $this->options['method_override_query'];
}
/**
* Check is it needed to mask all non-GET/POST methods (PUT/DELETE/etc.) by using POST method with added
* "X-HTTP-Method-Override: METHOD" HTTP header into request.
*
* @return bool
*/
public function isMethodOverrideHeader()
{
return isset($this->options['method_override_header']) && $this->options['method_override_header'];
}
}

View File

@ -0,0 +1,187 @@
<?php
/**
* WooCommerce REST API HTTP Client Request
*
* @category HttpClient
* @package Automattic/WooCommerce
*/
namespace Automattic\WooCommerce\HttpClient;
/**
* REST API HTTP Client Request class.
*
* @package Automattic/WooCommerce
*/
class Request
{
/**
* Request url.
*
* @var string
*/
private $url;
/**
* Request method.
*
* @var string
*/
private $method;
/**
* Request paramenters.
*
* @var array
*/
private $parameters;
/**
* Request headers.
*
* @var array
*/
private $headers;
/**
* Request body.
*
* @var string
*/
private $body;
/**
* Initialize request.
*
* @param string $url Request url.
* @param string $method Request method.
* @param array $parameters Request paramenters.
* @param array $headers Request headers.
* @param string $body Request body.
*/
public function __construct($url = '', $method = 'POST', $parameters = [], $headers = [], $body = '')
{
$this->url = $url;
$this->method = $method;
$this->parameters = $parameters;
$this->headers = $headers;
$this->body = $body;
}
/**
* Set url.
*
* @param string $url Request url.
*/
public function setUrl($url)
{
$this->url = $url;
}
/**
* Set method.
*
* @param string $method Request method.
*/
public function setMethod($method)
{
$this->method = $method;
}
/**
* Set parameters.
*
* @param array $parameters Request paramenters.
*/
public function setParameters($parameters)
{
$this->parameters = $parameters;
}
/**
* Set headers.
*
* @param array $headers Request headers.
*/
public function setHeaders($headers)
{
$this->headers = $headers;
}
/**
* Set body.
*
* @param string $body Request body.
*/
public function setBody($body)
{
$this->body = $body;
}
/**
* Get url.
*
* @return string
*/
public function getUrl()
{
return $this->url;
}
/**
* Get method.
*
* @return string
*/
public function getMethod()
{
return $this->method;
}
/**
* Get parameters.
*
* @return array
*/
public function getParameters()
{
return $this->parameters;
}
/**
* Get headers.
*
* @return array
*/
public function getHeaders()
{
return $this->headers;
}
/**
* Get raw headers.
*
* @return array
*/
public function getRawHeaders()
{
$headers = [];
foreach ($this->headers as $key => $value) {
$headers[] = $key . ': ' . $value;
}
return $headers;
}
/**
* Get body.
*
* @return string
*/
public function getBody()
{
return $this->body;
}
}

View File

@ -0,0 +1,127 @@
<?php
/**
* WooCommerce REST API HTTP Client Response
*
* @category HttpClient
* @package Automattic/WooCommerce
*/
namespace Automattic\WooCommerce\HttpClient;
/**
* REST API HTTP Client Response class.
*
* @package Automattic/WooCommerce
*/
class Response
{
/**
* Response code.
*
* @var int
*/
private $code;
/**
* Response headers.
*
* @var array
*/
private $headers;
/**
* Response body.
*
* @var string
*/
private $body;
/**
* Initialize response.
*
* @param int $code Response code.
* @param array $headers Response headers.
* @param string $body Response body.
*/
public function __construct($code = 0, $headers = [], $body = '')
{
$this->code = $code;
$this->headers = $headers;
$this->body = $body;
}
/**
* To string.
*
* @return string
*/
public function __toString()
{
return \json_encode([
'code' => $this->code,
'headers' => $this->headers,
'body' => $this->body,
]);
}
/**
* Set code.
*
* @param int $code Response code.
*/
public function setCode($code)
{
$this->code = (int) $code;
}
/**
* Set headers.
*
* @param array $headers Response headers.
*/
public function setHeaders($headers)
{
$this->headers = $headers;
}
/**
* Set body.
*
* @param string $body Response body.
*/
public function setBody($body)
{
$this->body = $body;
}
/**
* Get code.
*
* @return int
*/
public function getCode()
{
return $this->code;
}
/**
* Get headers.
*
* @return array $headers Response headers.
*/
public function getHeaders()
{
return $this->headers;
}
/**
* Get body.
*
* @return string $body Response body.
*/
public function getBody()
{
return $this->body;
}
}

579
vendor/composer/ClassLoader.php vendored Normal file
View File

@ -0,0 +1,579 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var string|null */
private $vendorDir;
// PSR-4
/**
* @var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array<string, list<string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* List of PSR-0 prefixes
*
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
*
* @var array<string, array<string, list<string>>>
*/
private $prefixesPsr0 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var array<string, bool>
*/
private $missingClasses = array();
/** @var string|null */
private $apcuPrefix;
/**
* @var array<string, self>
*/
private static $registeredLoaders = array();
/**
* @param string|null $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return list<string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return list<string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return array<string, string> Array of classname => path
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array<string, string> $classMap Class to filename map
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
$paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
$paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
$paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
$paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
$includeFile = self::$includeFile;
$includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders keyed by their corresponding vendor directories.
*
* @return array<string, self>
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
/**
* @return void
*/
private static function initializeIncludeClosure()
{
if (self::$includeFile !== null) {
return;
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = \Closure::bind(static function($file) {
include $file;
}, null, null);
}
}

396
vendor/composer/InstalledVersions.php vendored Normal file
View File

@ -0,0 +1,396 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
* @internal
*/
private static $selfDir = null;
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool
*/
private static $installedIsLocalDir;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
// when using reload, we disable the duplicate protection to ensure that self::$installed data is
// always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
// so we have to assume it does not, and that may result in duplicate data being returned when listing
// all installed packages for example
self::$installedIsLocalDir = false;
}
/**
* @return string
*/
private static function getSelfDir()
{
if (self::$selfDir === null) {
self::$selfDir = strtr(__DIR__, '\\', '/');
}
return self::$selfDir;
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
$copiedLocalDir = false;
if (self::$canGetVendors) {
$selfDir = self::getSelfDir();
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
$vendorDir = strtr($vendorDir, '\\', '/');
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
self::$installedByVendor[$vendorDir] = $required;
$installed[] = $required;
if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
self::$installed = $required;
self::$installedIsLocalDir = true;
}
}
if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
$copiedLocalDir = true;
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require __DIR__ . '/installed.php';
self::$installed = $required;
} else {
self::$installed = array();
}
}
if (self::$installed !== array() && !$copiedLocalDir) {
$installed[] = self::$installed;
}
return $installed;
}
}

21
vendor/composer/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

10
vendor/composer/autoload_classmap.php vendored Normal file
View File

@ -0,0 +1,10 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
);

View File

@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
);

15
vendor/composer/autoload_psr4.php vendored Normal file
View File

@ -0,0 +1,15 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
'Nyholm\\Psr7\\' => array($vendorDir . '/nyholm/psr7/src'),
'Mollie\\Api\\' => array($vendorDir . '/mollie/mollie-api-php/src'),
'Composer\\CaBundle\\' => array($vendorDir . '/composer/ca-bundle/src'),
'Automattic\\WooCommerce\\' => array($vendorDir . '/automattic/woocommerce/src/WooCommerce'),
);

38
vendor/composer/autoload_real.php vendored Normal file
View File

@ -0,0 +1,38 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInitcd53d4cc421fe0b70226223f299ac0ad
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInitcd53d4cc421fe0b70226223f299ac0ad', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInitcd53d4cc421fe0b70226223f299ac0ad', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitcd53d4cc421fe0b70226223f299ac0ad::getInitializer($loader));
$loader->register(true);
return $loader;
}
}

74
vendor/composer/autoload_static.php vendored Normal file
View File

@ -0,0 +1,74 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInitcd53d4cc421fe0b70226223f299ac0ad
{
public static $prefixLengthsPsr4 = array (
'P' =>
array (
'Psr\\Http\\Message\\' => 17,
'Psr\\Http\\Client\\' => 16,
),
'N' =>
array (
'Nyholm\\Psr7\\' => 12,
),
'M' =>
array (
'Mollie\\Api\\' => 11,
),
'C' =>
array (
'Composer\\CaBundle\\' => 18,
),
'A' =>
array (
'Automattic\\WooCommerce\\' => 23,
),
);
public static $prefixDirsPsr4 = array (
'Psr\\Http\\Message\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-factory/src',
1 => __DIR__ . '/..' . '/psr/http-message/src',
),
'Psr\\Http\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-client/src',
),
'Nyholm\\Psr7\\' =>
array (
0 => __DIR__ . '/..' . '/nyholm/psr7/src',
),
'Mollie\\Api\\' =>
array (
0 => __DIR__ . '/..' . '/mollie/mollie-api-php/src',
),
'Composer\\CaBundle\\' =>
array (
0 => __DIR__ . '/..' . '/composer/ca-bundle/src',
),
'Automattic\\WooCommerce\\' =>
array (
0 => __DIR__ . '/..' . '/automattic/woocommerce/src/WooCommerce',
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInitcd53d4cc421fe0b70226223f299ac0ad::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitcd53d4cc421fe0b70226223f299ac0ad::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInitcd53d4cc421fe0b70226223f299ac0ad::$classMap;
}, null, ClassLoader::class);
}
}

19
vendor/composer/ca-bundle/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (C) 2016 Composer
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

85
vendor/composer/ca-bundle/README.md vendored Normal file
View File

@ -0,0 +1,85 @@
composer/ca-bundle
==================
Small utility library that lets you find a path to the system CA bundle,
and includes a fallback to the Mozilla CA bundle.
Originally written as part of [composer/composer](https://github.com/composer/composer),
now extracted and made available as a stand-alone library.
Installation
------------
Install the latest version with:
```bash
$ composer require composer/ca-bundle
```
Requirements
------------
* PHP 5.3.2 is required but using the latest version of PHP is highly recommended.
Basic usage
-----------
### `Composer\CaBundle\CaBundle`
- `CaBundle::getSystemCaRootBundlePath()`: Returns the system CA bundle path, or a path to the bundled one as fallback
- `CaBundle::getBundledCaBundlePath()`: Returns the path to the bundled CA file
- `CaBundle::validateCaFile($filename)`: Validates a CA file using openssl_x509_parse only if it is safe to use
- `CaBundle::isOpensslParseSafe()`: Test if it is safe to use the PHP function openssl_x509_parse()
- `CaBundle::reset()`: Resets the static caches
#### To use with curl
```php
$curl = curl_init("https://example.org/");
$caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath();
if (is_dir($caPathOrFile)) {
curl_setopt($curl, CURLOPT_CAPATH, $caPathOrFile);
} else {
curl_setopt($curl, CURLOPT_CAINFO, $caPathOrFile);
}
$result = curl_exec($curl);
```
#### To use with php streams
```php
$opts = array(
'http' => array(
'method' => "GET"
)
);
$caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath();
if (is_dir($caPathOrFile)) {
$opts['ssl']['capath'] = $caPathOrFile;
} else {
$opts['ssl']['cafile'] = $caPathOrFile;
}
$context = stream_context_create($opts);
$result = file_get_contents('https://example.com', false, $context);
```
#### To use with Guzzle
```php
$client = new \GuzzleHttp\Client([
\GuzzleHttp\RequestOptions::VERIFY => \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath()
]);
```
License
-------
composer/ca-bundle is licensed under the MIT License, see the LICENSE file for details.

54
vendor/composer/ca-bundle/composer.json vendored Normal file
View File

@ -0,0 +1,54 @@
{
"name": "composer/ca-bundle",
"description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.",
"type": "library",
"license": "MIT",
"keywords": [
"cabundle",
"cacert",
"certificate",
"ssl",
"tls"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"support": {
"irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/ca-bundle/issues"
},
"require": {
"ext-openssl": "*",
"ext-pcre": "*",
"php": "^7.2 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^8 || ^9",
"phpstan/phpstan": "^1.10",
"psr/log": "^1.0 || ^2.0 || ^3.0",
"symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0"
},
"autoload": {
"psr-4": {
"Composer\\CaBundle\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Composer\\CaBundle\\": "tests"
}
},
"extra": {
"branch-alias": {
"dev-main": "1.x-dev"
}
},
"scripts": {
"test": "@php phpunit",
"phpstan": "@php phpstan analyse"
}
}

3511
vendor/composer/ca-bundle/res/cacert.pem vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,322 @@
<?php
/*
* This file is part of composer/ca-bundle.
*
* (c) Composer <https://github.com/composer>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Composer\CaBundle;
use Psr\Log\LoggerInterface;
use Symfony\Component\Process\PhpProcess;
/**
* @author Chris Smith <chris@cs278.org>
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class CaBundle
{
/** @var string|null */
private static $caPath;
/** @var array<string, bool> */
private static $caFileValidity = array();
/**
* Returns the system CA bundle path, or a path to the bundled one
*
* This method was adapted from Sslurp.
* https://github.com/EvanDotPro/Sslurp
*
* (c) Evan Coury <me@evancoury.com>
*
* For the full copyright and license information, please see below:
*
* Copyright (c) 2013, Evan Coury
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @param LoggerInterface $logger optional logger for information about which CA files were loaded
* @return string path to a CA bundle file or directory
*/
public static function getSystemCaRootBundlePath(?LoggerInterface $logger = null)
{
if (self::$caPath !== null) {
return self::$caPath;
}
$caBundlePaths = array();
// If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that.
// This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
$caBundlePaths[] = self::getEnvVariable('SSL_CERT_FILE');
// If SSL_CERT_DIR env variable points to a valid certificate/bundle, use that.
// This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
$caBundlePaths[] = self::getEnvVariable('SSL_CERT_DIR');
$caBundlePaths[] = ini_get('openssl.cafile');
$caBundlePaths[] = ini_get('openssl.capath');
$otherLocations = array(
'/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem', // Fedora, RHEL, CentOS (ca-certificates package) - NEW
'/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package) - Deprecated
'/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package)
'/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package)
'/usr/ssl/certs/ca-bundle.crt', // Cygwin
'/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package
'/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option)
'/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat?
'/etc/ssl/cert.pem', // OpenBSD
'/usr/local/etc/openssl/cert.pem', // OS X homebrew, openssl package
'/usr/local/etc/openssl@1.1/cert.pem', // OS X homebrew, openssl@1.1 package
'/opt/homebrew/etc/openssl@3/cert.pem', // macOS silicon homebrew, openssl@3 package
'/opt/homebrew/etc/openssl@1.1/cert.pem', // macOS silicon homebrew, openssl@1.1 package
'/etc/pki/tls/certs',
'/etc/ssl/certs', // FreeBSD
);
$caBundlePaths = array_merge($caBundlePaths, $otherLocations);
foreach ($caBundlePaths as $caBundle) {
if ($caBundle && self::caFileUsable($caBundle, $logger)) {
return self::$caPath = $caBundle;
}
if ($caBundle && self::caDirUsable($caBundle, $logger)) {
return self::$caPath = $caBundle;
}
}
return self::$caPath = static::getBundledCaBundlePath(); // Bundled CA file, last resort
}
/**
* Returns the path to the bundled CA file
*
* In case you don't want to trust the user or the system, you can use this directly
*
* @return string path to a CA bundle file
*/
public static function getBundledCaBundlePath()
{
$caBundleFile = __DIR__.'/../res/cacert.pem';
// cURL does not understand 'phar://' paths
// see https://github.com/composer/ca-bundle/issues/10
if (0 === strpos($caBundleFile, 'phar://')) {
$tempCaBundleFile = tempnam(sys_get_temp_dir(), 'openssl-ca-bundle-');
if (false === $tempCaBundleFile) {
throw new \RuntimeException('Could not create a temporary file to store the bundled CA file');
}
file_put_contents(
$tempCaBundleFile,
file_get_contents($caBundleFile)
);
register_shutdown_function(function() use ($tempCaBundleFile) {
@unlink($tempCaBundleFile);
});
$caBundleFile = $tempCaBundleFile;
}
return $caBundleFile;
}
/**
* Validates a CA file using opensl_x509_parse only if it is safe to use
*
* @param string $filename
* @param LoggerInterface $logger optional logger for information about which CA files were loaded
*
* @return bool
*/
public static function validateCaFile($filename, ?LoggerInterface $logger = null)
{
static $warned = false;
if (isset(self::$caFileValidity[$filename])) {
return self::$caFileValidity[$filename];
}
$contents = file_get_contents($filename);
if (is_string($contents) && strlen($contents) > 0) {
$contents = preg_replace("/^(\\-+(?:BEGIN|END))\\s+TRUSTED\\s+(CERTIFICATE\\-+)\$/m", '$1 $2', $contents);
if (null === $contents) {
// regex extraction failed
$isValid = false;
} else {
$isValid = (bool) openssl_x509_parse($contents);
}
} else {
$isValid = false;
}
if ($logger) {
$logger->debug('Checked CA file '.realpath($filename).': '.($isValid ? 'valid' : 'invalid'));
}
return self::$caFileValidity[$filename] = $isValid;
}
/**
* Test if it is safe to use the PHP function openssl_x509_parse().
*
* This checks if OpenSSL extensions is vulnerable to remote code execution
* via the exploit documented as CVE-2013-6420.
*
* @return bool
*/
public static function isOpensslParseSafe()
{
return true;
}
/**
* Resets the static caches
* @return void
*/
public static function reset()
{
self::$caFileValidity = array();
self::$caPath = null;
}
/**
* @param string $name
* @return string|false
*/
private static function getEnvVariable($name)
{
if (isset($_SERVER[$name])) {
return (string) $_SERVER[$name];
}
if (PHP_SAPI === 'cli' && ($value = getenv($name)) !== false && $value !== null) {
return (string) $value;
}
return false;
}
/**
* @param string|false $certFile
* @param LoggerInterface|null $logger
* @return bool
*/
private static function caFileUsable($certFile, ?LoggerInterface $logger = null)
{
return $certFile
&& self::isFile($certFile, $logger)
&& self::isReadable($certFile, $logger)
&& self::validateCaFile($certFile, $logger);
}
/**
* @param string|false $certDir
* @param LoggerInterface|null $logger
* @return bool
*/
private static function caDirUsable($certDir, ?LoggerInterface $logger = null)
{
return $certDir
&& self::isDir($certDir, $logger)
&& self::isReadable($certDir, $logger)
&& self::glob($certDir . '/*', $logger);
}
/**
* @param string $certFile
* @param LoggerInterface|null $logger
* @return bool
*/
private static function isFile($certFile, ?LoggerInterface $logger = null)
{
$isFile = @is_file($certFile);
if (!$isFile && $logger) {
$logger->debug(sprintf('Checked CA file %s does not exist or it is not a file.', $certFile));
}
return $isFile;
}
/**
* @param string $certDir
* @param LoggerInterface|null $logger
* @return bool
*/
private static function isDir($certDir, ?LoggerInterface $logger = null)
{
$isDir = @is_dir($certDir);
if (!$isDir && $logger) {
$logger->debug(sprintf('Checked directory %s does not exist or it is not a directory.', $certDir));
}
return $isDir;
}
/**
* @param string $certFileOrDir
* @param LoggerInterface|null $logger
* @return bool
*/
private static function isReadable($certFileOrDir, ?LoggerInterface $logger = null)
{
$isReadable = @is_readable($certFileOrDir);
if (!$isReadable && $logger) {
$logger->debug(sprintf('Checked file or directory %s is not readable.', $certFileOrDir));
}
return $isReadable;
}
/**
* @param string $pattern
* @param LoggerInterface|null $logger
* @return bool
*/
private static function glob($pattern, ?LoggerInterface $logger = null)
{
$certs = glob($pattern);
if ($certs === false) {
if ($logger) {
$logger->debug(sprintf("An error occurred while trying to find certificates for pattern: %s", $pattern));
}
return false;
}
if (count($certs) === 0) {
if ($logger) {
$logger->debug(sprintf("No CA files found for pattern: %s", $pattern));
}
return false;
}
return true;
}
}

486
vendor/composer/installed.json vendored Normal file
View File

@ -0,0 +1,486 @@
{
"packages": [
{
"name": "automattic/woocommerce",
"version": "3.1.0",
"version_normalized": "3.1.0.0",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/wc-api-php.git",
"reference": "d3b292f04c0b3b21dced691ebad8be073a83b4ad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/wc-api-php/zipball/d3b292f04c0b3b21dced691ebad8be073a83b4ad",
"reference": "d3b292f04c0b3b21dced691ebad8be073a83b4ad",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"php": ">= 7.1.0"
},
"require-dev": {
"overtrue/phplint": "7.4.x-dev",
"phpunit/phpunit": "^8",
"squizlabs/php_codesniffer": "3.*"
},
"time": "2022-03-18T21:46:17+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Automattic\\WooCommerce\\": [
"src/WooCommerce"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Claudio Sanches",
"email": "claudio.sanches@automattic.com"
}
],
"description": "A PHP wrapper for the WooCommerce REST API",
"keywords": [
"api",
"woocommerce"
],
"support": {
"issues": "https://github.com/woocommerce/wc-api-php/issues",
"source": "https://github.com/woocommerce/wc-api-php/tree/3.1.0"
},
"install-path": "../automattic/woocommerce"
},
{
"name": "composer/ca-bundle",
"version": "1.5.10",
"version_normalized": "1.5.10.0",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
"reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/961a5e4056dd2e4a2eedcac7576075947c28bf63",
"reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"ext-pcre": "*",
"php": "^7.2 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^8 || ^9",
"psr/log": "^1.0 || ^2.0 || ^3.0",
"symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0"
},
"time": "2025-12-08T15:06:51+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Composer\\CaBundle\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.",
"keywords": [
"cabundle",
"cacert",
"certificate",
"ssl",
"tls"
],
"support": {
"irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/ca-bundle/issues",
"source": "https://github.com/composer/ca-bundle/tree/1.5.10"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
}
],
"install-path": "./ca-bundle"
},
{
"name": "mollie/mollie-api-php",
"version": "v3.7.0",
"version_normalized": "3.7.0.0",
"source": {
"type": "git",
"url": "https://github.com/mollie/mollie-api-php.git",
"reference": "55f843fd3944c4c9ed08ac1d5929084305b82b8b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mollie/mollie-api-php/zipball/55f843fd3944c4c9ed08ac1d5929084305b82b8b",
"reference": "55f843fd3944c4c9ed08ac1d5929084305b82b8b",
"shasum": ""
},
"require": {
"composer/ca-bundle": "^1.4",
"ext-curl": "*",
"ext-json": "*",
"ext-openssl": "*",
"nyholm/psr7": "^1.8",
"php": "^7.4|^8.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.1",
"psr/http-message": "^1.1|^2.0"
},
"require-dev": {
"brianium/paratest": "^6.11",
"guzzlehttp/guzzle": "^7.6",
"phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^9.6",
"symfony/var-dumper": "^5.4|^6.4|^7.2"
},
"suggest": {
"mollie/oauth2-mollie-php": "Use OAuth to authenticate with the Mollie API. This is needed for some endpoints. Visit https://docs.mollie.com/ for more information."
},
"time": "2025-12-01T09:03:27+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Mollie\\Api\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Mollie B.V.",
"email": "info@mollie.com"
}
],
"description": "Mollie API client library for PHP. Mollie is a European Payment Service provider and offers international payment methods such as Mastercard, VISA, American Express and PayPal, and local payment methods such as iDEAL, Bancontact, SOFORT Banking, SEPA direct debit, Belfius Direct Net, KBC Payment Button and various gift cards such as Podiumcadeaukaart and fashioncheque.",
"homepage": "https://www.mollie.com/en/developers",
"keywords": [
"Apple Pay",
"CBC",
"Przelewy24",
"api",
"bancontact",
"banktransfer",
"belfius",
"belfius direct net",
"charges",
"creditcard",
"direct debit",
"fashioncheque",
"gateway",
"gift cards",
"ideal",
"inghomepay",
"intersolve",
"kbc",
"klarna",
"mistercash",
"mollie",
"paylater",
"payment",
"payments",
"paypal",
"paysafecard",
"podiumcadeaukaart",
"recurring",
"refunds",
"sepa",
"service",
"sliceit",
"sofort",
"sofortbanking",
"subscriptions"
],
"support": {
"issues": "https://github.com/mollie/mollie-api-php/issues",
"source": "https://github.com/mollie/mollie-api-php/tree/v3.7.0"
},
"install-path": "../mollie/mollie-api-php"
},
{
"name": "nyholm/psr7",
"version": "1.8.2",
"version_normalized": "1.8.2.0",
"source": {
"type": "git",
"url": "https://github.com/Nyholm/psr7.git",
"reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3",
"reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3",
"shasum": ""
},
"require": {
"php": ">=7.2",
"psr/http-factory": "^1.0",
"psr/http-message": "^1.1 || ^2.0"
},
"provide": {
"php-http/message-factory-implementation": "1.0",
"psr/http-factory-implementation": "1.0",
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"http-interop/http-factory-tests": "^0.9",
"php-http/message-factory": "^1.0",
"php-http/psr7-integration-tests": "^1.0",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.4",
"symfony/error-handler": "^4.4"
},
"time": "2024-09-09T07:06:30+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.8-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Nyholm\\Psr7\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com"
},
{
"name": "Martijn van der Ven",
"email": "martijn@vanderven.se"
}
],
"description": "A fast PHP7 implementation of PSR-7",
"homepage": "https://tnyholm.se",
"keywords": [
"psr-17",
"psr-7"
],
"support": {
"issues": "https://github.com/Nyholm/psr7/issues",
"source": "https://github.com/Nyholm/psr7/tree/1.8.2"
},
"funding": [
{
"url": "https://github.com/Zegnat",
"type": "github"
},
{
"url": "https://github.com/nyholm",
"type": "github"
}
],
"install-path": "../nyholm/psr7"
},
{
"name": "psr/http-client",
"version": "1.0.3",
"version_normalized": "1.0.3.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-client.git",
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
"shasum": ""
},
"require": {
"php": "^7.0 || ^8.0",
"psr/http-message": "^1.0 || ^2.0"
},
"time": "2023-09-23T14:17:50+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Psr\\Http\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP clients",
"homepage": "https://github.com/php-fig/http-client",
"keywords": [
"http",
"http-client",
"psr",
"psr-18"
],
"support": {
"source": "https://github.com/php-fig/http-client"
},
"install-path": "../psr/http-client"
},
{
"name": "psr/http-factory",
"version": "1.1.0",
"version_normalized": "1.1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-factory.git",
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"shasum": ""
},
"require": {
"php": ">=7.1",
"psr/http-message": "^1.0 || ^2.0"
},
"time": "2024-04-15T12:06:14+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
"keywords": [
"factory",
"http",
"message",
"psr",
"psr-17",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-factory"
},
"install-path": "../psr/http-factory"
},
{
"name": "psr/http-message",
"version": "2.0",
"version_normalized": "2.0.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"time": "2023-04-04T09:54:51+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-message/tree/2.0"
},
"install-path": "../psr/http-message"
}
],
"dev": true,
"dev-package-names": []
}

104
vendor/composer/installed.php vendored Normal file
View File

@ -0,0 +1,104 @@
<?php return array(
'root' => array(
'name' => '__root__',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => 'fcc8adacf8bb9c105b4b60530c87773c07741996',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev' => true,
),
'versions' => array(
'__root__' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => 'fcc8adacf8bb9c105b4b60530c87773c07741996',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev_requirement' => false,
),
'automattic/woocommerce' => array(
'pretty_version' => '3.1.0',
'version' => '3.1.0.0',
'reference' => 'd3b292f04c0b3b21dced691ebad8be073a83b4ad',
'type' => 'library',
'install_path' => __DIR__ . '/../automattic/woocommerce',
'aliases' => array(),
'dev_requirement' => false,
),
'composer/ca-bundle' => array(
'pretty_version' => '1.5.10',
'version' => '1.5.10.0',
'reference' => '961a5e4056dd2e4a2eedcac7576075947c28bf63',
'type' => 'library',
'install_path' => __DIR__ . '/./ca-bundle',
'aliases' => array(),
'dev_requirement' => false,
),
'mollie/mollie-api-php' => array(
'pretty_version' => 'v3.7.0',
'version' => '3.7.0.0',
'reference' => '55f843fd3944c4c9ed08ac1d5929084305b82b8b',
'type' => 'library',
'install_path' => __DIR__ . '/../mollie/mollie-api-php',
'aliases' => array(),
'dev_requirement' => false,
),
'nyholm/psr7' => array(
'pretty_version' => '1.8.2',
'version' => '1.8.2.0',
'reference' => 'a71f2b11690f4b24d099d6b16690a90ae14fc6f3',
'type' => 'library',
'install_path' => __DIR__ . '/../nyholm/psr7',
'aliases' => array(),
'dev_requirement' => false,
),
'php-http/message-factory-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/http-client' => array(
'pretty_version' => '1.0.3',
'version' => '1.0.3.0',
'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-client',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-factory' => array(
'pretty_version' => '1.1.0',
'version' => '1.1.0.0',
'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-factory',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-factory-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/http-message' => array(
'pretty_version' => '2.0',
'version' => '2.0.0.0',
'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-message',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-message-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
),
);

25
vendor/composer/platform_check.php vendored Normal file
View File

@ -0,0 +1,25 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 70400)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
throw new \RuntimeException(
'Composer detected issues in your platform: ' . implode(' ', $issues)
);
}

View File

@ -0,0 +1,214 @@
# Changelog
Starting with v3, all notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased](https://github.com/mollie/mollie-api-php/compare/v3.6.0...HEAD)
## [v3.6.0](https://github.com/mollie/mollie-api-php/compare/v3.5.0...v3.6.0) - 2025-11-05
### What's Changed
* Feat/add balance transfer webhook events by @sandervanhooft in https://github.com/mollie/mollie-api-php/pull/842
* Fixed webhook docs typo and explained next-gen webhook focus by @sandervanhooft in https://github.com/mollie/mollie-api-php/pull/841
**Full Changelog**: https://github.com/mollie/mollie-api-php/compare/v3.5.0...v3.6.0
## [v3.5.0](https://github.com/mollie/mollie-api-php/compare/v3.4.0...v3.5.0) - 2025-10-28
### Added
* Feat/add retry logic by @Naoray in https://github.com/mollie/mollie-api-php/pull/826
* Feat/add fake retain requests option by @Naoray in https://github.com/mollie/mollie-api-php/pull/830
* feat: add isEInvoice param and add support for testmode in all sales-… by @Naoray in https://github.com/mollie/mollie-api-php/pull/832
* feat: add customerId and mandateId to create sales invoice request by @Naoray in https://github.com/mollie/mollie-api-php/pull/834
* Feat/add balance transfer endpoint by @Naoray in https://github.com/mollie/mollie-api-php/pull/831
* Feat/add webhook mapping and events by @Naoray in https://github.com/mollie/mollie-api-php/pull/829
- global Config that serves as a lookup map to easily map resources to their respective collection keys
- `MockEvent` to easily test event handling
- `Str` utility class
- `classBasename` to `Utility`
- `WebhookEntity` to serve as Container for Resource data received through webhooks (-> can be transformed into BaseResource)
- Webhook Events that are instanced via the `WebhookEventMapper`
### Changed
- Feat/make sequence mock responses consume callables by @Naoray in https://github.com/mollie/mollie-api-php/pull/833
### Fixed
* Change include to embed just like in GetPaginatedChargebacksRequest.php #837 by @Naoray in https://github.com/mollie/mollie-api-php/pull/838
* Allow description on CreatePaymentRefundRequest to be empty by @Naoray in https://github.com/mollie/mollie-api-php/pull/839
**Full Changelog**: https://github.com/mollie/mollie-api-php/compare/v3.4.0...v3.5.0
## [v3.4.0](https://github.com/mollie/mollie-api-php/compare/v3.3.3...v3.4.0) - 2025-08-13
### What's Changed
* Feat/add new payment route endpoints by @Naoray in https://github.com/mollie/mollie-api-php/pull/825
**Full Changelog**: https://github.com/mollie/mollie-api-php/compare/v3.3.3...v3.4.0
## [v3.3.3](https://github.com/mollie/mollie-api-php/compare/v3.3.2...v3.3.3) - 2025-08-12
## What's Changed
* Fix/823 inconsistencies on payment link request by @Naoray in https://github.com/mollie/mollie-api-php/pull/824
**Full Changelog**: https://github.com/mollie/mollie-api-php/compare/v3.3.2...v3.3.3
## [v3.3.2](https://github.com/mollie/mollie-api-php/compare/v3.3.1...v3.3.2) - 2025-07-25
## What's Changed
* Fix/819 signature date invalid by @Naoray in https://github.com/mollie/mollie-api-php/pull/820
**Full Changelog**: https://github.com/mollie/mollie-api-php/compare/v3.3.1...v3.3.2
## [v3.3.1](https://github.com/mollie/mollie-api-php/compare/v3.3.0...v3.3.1) - 2025-07-25
### What's Changed
* fix: signature validator handling null signatures by @Naoray in https://github.com/mollie/mollie-api-php/pull/822
**Full Changelog**: https://github.com/mollie/mollie-api-php/compare/v3.3.0...v3.3.1
## [v3.3.0](https://github.com/mollie/mollie-api-php/compare/v3.2.0...v3.3.0) - 2025-07-25
## What's Changed
* Feat/expose webhook signature header by @Naoray in https://github.com/mollie/mollie-api-php/pull/821
* Feat/expose webhook signature creation by @Naoray
## [v3.2.0](https://github.com/mollie/mollie-api-php/compare/v3.1.5...v3.2.0) - 2025-07-23
### What's Changed
* Feat/add create webhook endpoint by @Naoray in https://github.com/mollie/mollie-api-php/pull/812
* Feat/webhook signature verification by @Naoray in https://github.com/mollie/mollie-api-php/pull/813
**Full Changelog**: https://github.com/mollie/mollie-api-php/compare/v3.1.5...v3.2.0
## [v3.1.5](https://github.com/mollie/mollie-api-php/compare/v3.1.4...v3.1.5) - 2025-07-10
### What's Changed
* Fix: allow array of payment methods when creating a payment by @jockri in https://github.com/mollie/mollie-api-php/pull/811
* Sandervanhooft fix/recipe classes by @Naoray in https://github.com/mollie/mollie-api-php/pull/815
* Fix class references on recipes by @sandervanhooft in https://github.com/mollie/mollie-api-php/pull/814
* Fix payment links expiresAt by @sandervanhooft in https://github.com/mollie/mollie-api-php/pull/817
* Change "include" into "embed" on GetPaginatedChargebacksRequest by @sandervanhooft in https://github.com/mollie/mollie-api-php/pull/818
### New Contributors
* @jockri made their first contribution in https://github.com/mollie/mollie-api-php/pull/811
**Full Changelog**: https://github.com/mollie/mollie-api-php/compare/v3.1.4...v3.1.5
## [v3.1.4](https://github.com/mollie/mollie-api-php/compare/v3.1.3...v3.1.4) - 2025-06-11
## What's Changed
* Fix 400 Bad Request on DELETE when store array is empty by @cswiers in https://github.com/mollie/mollie-api-php/pull/810
## New Contributors
* @cswiers made their first contribution in https://github.com/mollie/mollie-api-php/pull/810
**Full Changelog**: https://github.com/mollie/mollie-api-php/compare/v3.1.3...v3.1.4
## [v3.1.3](https://github.com/mollie/mollie-api-php/compare/v3.1.2...v3.1.3) - 2025-06-11
**Full Changelog**: https://github.com/mollie/mollie-api-php/compare/v3.1.3...v3.1.3
## [v3.1.2](https://github.com/mollie/mollie-api-php/compare/v3.1.1...v3.1.2) - 2025-06-10
**Full Changelog**: https://github.com/mollie/mollie-api-php/compare/v3.1.1...v3.1.2
## [v3.1.1](https://github.com/mollie/mollie-api-php/compare/v3.1.0...v3.1.1) - 2025-06-10
## What's Changed
* Fix/include resources by @Naoray in https://github.com/mollie/mollie-api-php/pull/808
**Full Changelog**: https://github.com/mollie/mollie-api-php/compare/v3.1.0...v3.1.1
## [v3.1.0](https://github.com/mollie/mollie-api-php/compare/v3.0.6...v3.1.0) - 2025-06-05
### What's Changed
* Main by @Naoray in https://github.com/mollie/mollie-api-php/pull/804
* feat: add status reason to payment resource by @Naoray in https://github.com/mollie/mollie-api-php/pull/806
**Full Changelog**: https://github.com/mollie/mollie-api-php/compare/v3.0.6...v3.1.0
## [v3.0.6](https://github.com/mollie/mollie-api-php/compare/v1.0.0-test...v3.0.6) - 2025-06-02
### What's Changed
* Amend capturable recipe by @fjbender in https://github.com/mollie/mollie-api-php/pull/796
* fix: exchange wrong request name by @Naoray in https://github.com/mollie/mollie-api-php/pull/797
* Removes nullability from delete() method, as it cannot return null by @Sjustein in https://github.com/mollie/mollie-api-php/pull/802
* fix: use payload instead of query params for testmode by @Naoray in https://github.com/mollie/mollie-api-php/pull/803
### New Contributors
* @Sjustein made their first contribution in https://github.com/mollie/mollie-api-php/pull/802
**Full Changelog**: https://github.com/mollie/mollie-api-php/compare/v3.0.5...v3.0.6
## [v1.0.0-test](https://github.com/mollie/mollie-api-php/compare/v3.0.5...v1.0.0-test) - 2025-06-02
### What's Changed
* Amend capturable recipe by @fjbender in https://github.com/mollie/mollie-api-php/pull/796
* fix: exchange wrong request name by @Naoray in https://github.com/mollie/mollie-api-php/pull/797
* Removes nullability from delete() method, as it cannot return null by @Sjustein in https://github.com/mollie/mollie-api-php/pull/802
* fix: use payload instead of query params for testmode by @Naoray in https://github.com/mollie/mollie-api-php/pull/803
### New Contributors
* @Sjustein made their first contribution in https://github.com/mollie/mollie-api-php/pull/802
**Full Changelog**: https://github.com/mollie/mollie-api-php/compare/v3.0.5...v1.0.0-test
## [v3.0.5](https://github.com/mollie/mollie-api-php/compare/v3.0.4...v3.0.5) - 2025-04-27
### What's Changed
* Fix/791 data types may mess up property order by @Naoray in https://github.com/mollie/mollie-api-php/pull/794
**Full Changelog**: https://github.com/mollie/mollie-api-php/compare/v3.0.4...v3.0.5
## [v3.0.4](https://github.com/mollie/mollie-api-php/compare/v3.0.3...v3.0.4) - 2025-04-25
### What's Changed
* Chore/allow psr message v1 by @Naoray in https://github.com/mollie/mollie-api-php/pull/793
* Fix/789 remove overhault to resource calls by @Naoray in https://github.com/mollie/mollie-api-php/pull/792
**Full Changelog**: https://github.com/mollie/mollie-api-php/compare/v3.0.3...v3.0.4
## [v3.0.3](https://github.com/mollie/mollie-api-php/compare/v3.0.2...v3.0.3) - 2025-04-23
### What's Changed
* Fixed docs links by @sandervanhooft in https://github.com/mollie/mollie-api-php/pull/787
* Feat/small improvements by @Naoray in https://github.com/mollie/mollie-api-php/pull/788
* make `MockResponse` serializable
* add changed `$metadata` handling to upgrade guide
**Full Changelog**: https://github.com/mollie/mollie-api-php/compare/v3.0.2...v3.0.3
## [v3.0.2](https://github.com/mollie/mollie-api-php/compare/v3.0.0...v3.0.2) - 2025-04-17
### What's Changed
* handle nullable 422 exception field by @sandervanhooft in https://github.com/mollie/mollie-api-php/pull/786
**Full Changelog**: https://github.com/mollie/mollie-api-php/compare/v3.0.1...v3.0.2

8
vendor/mollie/mollie-api-php/LICENSE vendored Normal file
View File

@ -0,0 +1,8 @@
Copyright (c) 2013-2016, Mollie B.V.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

117
vendor/mollie/mollie-api-php/README.md vendored Normal file
View File

@ -0,0 +1,117 @@
<p align="center">
<img src="https://github.com/mollie/mollie-api-php/assets/7265703/140510a5-ede5-41bf-9d77-0d09b906e8f4" width="128" height="128"/>
</p>
<h1 align="center">Mollie API client for PHP</h1>
<div align="center">
[![Build Status](https://github.com/mollie/mollie-api-php/workflows/tests/badge.svg)](https://github.com/mollie/mollie-api-php/actions)
[![Latest Stable Version](https://poser.pugx.org/mollie/mollie-api-php/v/stable)](https://packagist.org/packages/mollie/mollie-api-php)
[![Total Downloads](https://poser.pugx.org/mollie/mollie-api-php/downloads)](https://packagist.org/packages/mollie/mollie-api-php)
[![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/mollie/mollie-api-php/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/mollie/mollie-api-php/actions?query=workflow%3A"Fix+Code+Style"+branch%3Amain)
</div>
Accepting [iDEAL](https://www.mollie.com/payments/ideal/), [Apple Pay](https://www.mollie.com/payments/apple-pay), [Google Pay](https://www.mollie.com/payments/googlepay), [Creditcard](https://www.mollie.com/payments/credit-card/), [Bancontact](https://www.mollie.com/payments/bancontact/), [SOFORT Banking](https://www.mollie.com/payments/sofort/), [SEPA Bank transfer](https://www.mollie.com/payments/bank-transfer/), [SEPA Direct debit](https://www.mollie.com/payments/direct-debit/), [PayPal](https://www.mollie.com/payments/paypal/), [Belfius Direct Net](https://www.mollie.com/payments/belfius/), [KBC/CBC](https://www.mollie.com/payments/kbc-cbc/), [paysafecard](https://www.mollie.com/payments/paysafecard/), [ING Home'Pay](https://www.mollie.com/payments/ing-homepay/), [Giropay](https://www.mollie.com/payments/giropay/), [EPS](https://www.mollie.com/payments/eps/), [Przelewy24](https://www.mollie.com/payments/przelewy24/), [Postepay](https://www.mollie.com/en/payments/postepay), [In3](https://www.mollie.com/payments/in3/), [Klarna](https://www.mollie.com/payments/klarna-pay-later/) ([Pay now](https://www.mollie.com/payments/klarna-pay-now/), [Pay later](https://www.mollie.com/payments/klarna-pay-later/), [Slice it](https://www.mollie.com/payments/klarna-slice-it/), [Pay in 3](https://www.mollie.com/payments/klarna-pay-in-3/)), [Giftcard](https://www.mollie.com/payments/gift-cards/) and [Voucher](https://www.mollie.com/en/payments/meal-eco-gift-vouchers) online payments without fixed monthly costs or any punishing registration procedures. Just use the Mollie API to receive payments directly on your website or easily refund transactions to your customers.
## Requirements ##
To use the Mollie API client, the following things are required:
+ Get yourself a free [Mollie account](https://www.mollie.com/signup). No sign up costs.
+ Now you're ready to use the Mollie API client in test mode.
+ Follow [a few steps](https://www.mollie.com/dashboard/?modal=onboarding) to enable payment methods in live mode, and let us handle the rest.
+ PHP >= 7.4
+ cUrl >= 7.19.4
+ Up-to-date OpenSSL (or other SSL/TLS toolkit)
For leveraging [Mollie Connect](https://docs.mollie.com/oauth/overview) (advanced use cases only), we recommend also installing our [OAuth2 client](https://github.com/mollie/oauth2-mollie-php).
## Installation ##
### Using Composer ###
The easiest way to install the Mollie API client is by using [Composer](http://getcomposer.org/doc/00-intro.md). You can require it with the following command:
```bash
composer require mollie/mollie-api-php
```
## Usage ##
Initializing the Mollie API client, and setting your API key.
```php
$mollie = new \Mollie\Api\MollieApiClient();
$mollie->setApiKey("test_dHar4XY7LxsDOtmnkVtjNVWXLSlXsM");
```
Find our full documentation online on [docs.mollie.com](https://docs.mollie.com).
#### Example usage ####
```php
use Mollie\Api\Http\Data\Money;
use Mollie\Api\Http\Requests\CreatePaymentRequest;
/** @var Mollie\Api\Resources\Payment $payment */
$payment = $mollie->send(new CreatePaymentRequest(
description: 'My first API payment',
amount: new Money('EUR', '10.00'),
redirectUrl: 'https://webshop.example.org/order/12345/',
webhookUrl: 'https://webshop.example.org/mollie-webhook/'
));
```
## Documentation
For an in-depth understanding of our API, please explore the [Mollie Developer Portal](https://www.mollie.com/developers). Our API documentation is available in English.
For detailed documentation about using this PHP client, see the following guides:
- [Endpoint Collections](docs/endpoint-collections.md) - Learn how to interact with all available API endpoints.
- [HTTP Adapters](docs/http-adapters.md) - Information on customizing HTTP communication.
- [Idempotency](docs/idempotency.md) - Best practices and setup for idempotent requests.
- [Payments](docs/payments.md) - Comprehensive guide on handling payments.
- [Requests](docs/requests.md) - Overview and usage of request objects in the API client.
- [Responses](docs/responses.md) - Handling and understanding responses from the API.
- [Testing](docs/testing.md) - Guidelines for testing with the Mollie API client.
- [Debugging](docs/debugging.md) - How to debug API requests and responses safely.
- [Webhooks](docs/webhooks.md) - How to process Webhook requests
These guides provide in-depth explanations and examples for advanced usage of the client.
## Recipes
The Mollie API client comes with a variety of recipes to help you understand how to implement various API features. These recipes are a great resource for learning how to integrate Mollie payments into your application.
Here are some of the key recipes included:
- **Payments**: Demonstrates how to handle various payment scenarios.
- [Create a payment](docs/recipes/payments/create-payment.md)
- [Create a capturable payment](docs/recipes/payments/create-capturable-payment.md)
- [Handle webhooks](docs/recipes/payments/handle-webhook.md)
- [Refund payments](docs/recipes/payments/refund-payment.md)
- **Customers**: Shows how to manage customers and their payments.
- [Manage customers](docs/recipes/customers/manage-customers.md)
- [Customer payments](docs/recipes/customers/customer-payments.md)
- **Subscriptions and Recurring Payments**:
- [Manage mandates](docs/recipes/mandates/manage-mandates.md)
- [Manage subscriptions](docs/recipes/subscriptions/manage-subscriptions.md)
For a full list of recipes, please refer to the [recipes directory](docs/recipes/).
These recipes are designed to help you integrate Mollie into your application. Make sure to use your test API keys when testing the integration.
## Upgrading
Please see [UPGRADING](UPGRADING.md) for details.
## Contributing to Our API Client ##
Would you like to contribute to improving our API client? We welcome [pull requests](https://github.com/mollie/mollie-api-php/pulls?utf8=%E2%9C%93&q=is%3Apr). But, if you're interested in contributing to a technology-focused organization, Mollie is actively recruiting developers and system engineers. Discover our current [job openings](https://jobs.mollie.com/) or [reach out](mailto:personeel@mollie.com).
## License ##
[BSD (Berkeley Software Distribution) License](https://opensource.org/licenses/bsd-license.php).
Copyright (c) 2013-2018, Mollie B.V.
## Support ##
Contact: [www.mollie.com](https://www.mollie.com) — info@mollie.com — +31 20 820 20 70

View File

@ -0,0 +1,204 @@
# Upgrading from v2 to v3
Welcome to Mollie API PHP v3! This guide will help you smoothly transition from v2 to v3.
The codebase has significant improvements, focusing on modern PHP practices, enhanced type safety, and offers a more intuitive developer experience.
## Breaking Changes
### Deprecations
#### MethodEndpointCollection.allActive()
The method `MethodEndpointCollection.allActive()` has been removed. Use `MethodEndpointCollection.allEnabled()` instead.
#### Order endpoint
Orders: Mollie is deprecating the Order and Shipment endpoints so these have been removed from `mollie-api-php`. The same functionality is now available through the Payment endpoint as well. So, use the Payment endpoint instead.
- All `/orders/*` endpoints and related classes (`Order*Endpoint`)
- Removed `MollieApiClient` properties:
```php
$client->orderPayments; // Removed
$client->orderRefunds; // Removed
$client->orderLines; // Removed
$client->shipments; // Removed
```
#### Integration code examples
To prevent misuse the code samples in `/examples` were replaced by markdown "recipes", which can be found in `/docs/recipes`.
### Metadata Type Restriction
In v2, when making API requests, the metadata parameter accepted any type (string, array, object, etc.). In v3, metadata in request payloads is restricted to only accept arrays. Make sure to update your code to provide metadata as arrays when making API requests.
```php
// Before (v2) - Using legacy array approach
$client->payments->create([
"amount" => [
"currency" => "EUR",
"value" => "10.00"
],
"metadata" => "some string" // Worked in v2
]);
// After (v3) - Using legacy array approach
$client->payments->create([
"amount" => [
"currency" => "EUR",
"value" => "10.00"
],
"metadata" => ["key" => "value"] // Only arrays are accepted in v3
]);
// After (v3) - Using request class
use Mollie\Api\Http\Data\Money;
use Mollie\Api\Http\Requests\CreatePaymentRequest;
$request = new CreatePaymentRequest(
description: "My payment",
amount: new Money("EUR", "10.00"),
metadata: ["key" => "value"] // Only arrays are accepted
);
$payment = $client->send($request);
```
### Class & Method Renames
#### Endpoint Class Changes
| Old Class | New Class | Method Changes |
|------------------------------|----------------------------------------|------------------------------------------------------------|
| `MethodEndpoint` | `MethodEndpointCollection` | `allAvailable()``all()`<br>`all()``allEnabled()` |
| `BalanceTransactionEndpoint` | `BalanceTransactionEndpointCollection` | `listFor()``pageFor()`<br>`listForId()``pageForId()` |
| `CustomerPaymentsEndpoint` | `CustomerPaymentsEndpointCollection` | `listFor()``pageFor()`<br>`listForId()``pageForId()` |
| `MandateEndpoint` | `MandateEndpointCollection` | `listFor()``pageFor()`<br>`listForId()``pageForId()` |
| `PaymentRefundEndpoint` | `PaymentRefundEndpointCollection` | `listFor()``pageFor()`<br>`listForId()``pageForId()` |
| `OnboardingEndpoint` | `OnboardingEndpointCollection` | `get()``status()` |
| `SubscriptionEndpoint` | `SubscriptionEndpointCollection` | `page()``allFor()` |
#### Signature Changes
You can now use named parameters for improved readability and flexibility:
```php
// Before (v2)
$mandates = $client->mandates->listFor($customer, 0, 10);
// After (v3)
$mandates = $client->mandates->pageForCustomer(
$customer,
from: null,
limit: 10,
testmode: false
);
```
### Constant & Collection Changes
- **Streamlined constants** - Redundant prefixes have been removed for a cleaner API:
```php
// Before
Payment::STATUS_PAID;
// After
Payment::PAID;
```
- **Simplified SequenceType constants**:
```php
// Before
SequenceType::SEQUENCETYPE_FIRST;
// After
SequenceType::FIRST;
```
- **Cleaner collection initialization**:
```php
// Before
new PaymentCollection(10, $payments);
// After
new PaymentCollection($payments);
```
### Test Mode Handling
- **Automatic detection** with API keys
- **Explicit parameter** for organization credentials:
```php
// Get payment link in test mode
$link = $client->paymentLinks->get('pl_123', testmode: true);
```
Read the full testing documentation [here](docs/testing.md).
### Removed Collections
- `OrganizationCollection`
- `RouteCollection`
## New Features
### Modern HTTP Handling
#### PSR-18 Support
You can now use any PSR-18 compatible HTTP client:
```php
use Mollie\Api\HttpAdapter\PSR18MollieHttpAdapter;
$adapter = new PSR18MollieHttpAdapter(
new GuzzleClient(),
new Psr17Factory(),
new Psr17Factory()
);
$client = new MollieApiClient($adapter);
```
### Enhanced Request Handling
#### Typed Request Objects
You can now say goodbye to array-based payloads and hello to type-safe request objects:
```php
use Mollie\Api\Http\Requests\CreatePaymentRequest;
$request = new CreatePaymentRequest(
amount: new Money('EUR', '10.00'),
description: 'Order 123',
redirectUrl: 'https://example.com/redirect'
);
$payment = $client->send($request);
```
Read the full request documentation [here](docs/requests.md).
### Collection Improvements
Collections now feature powerful functional methods for more expressive code:
```php
// New methods
$activePayments = $payments->filter(fn($p) => $p->isActive());
$hasRefunds = $payments->contains(fn($p) => $p->hasRefunds());
```
### Method Issuer Contracts
You can now easily manage method issuers with optional contract IDs:
```php
$client->methodIssuers->enable(
profileId: 'pfl_123',
methodId: 'voucher',
issuerId: 'iss_456',
contractId: 'contract_789' // Optional
);
```
## Further reading
Usage guides for the PHP client can be found in the [docs](docs). For more information on the Mollie API, check out [the official Mollie docs](https://docs.mollie.com).

158
vendor/mollie/mollie-api-php/bin/release vendored Executable file
View File

@ -0,0 +1,158 @@
#!/bin/bash
VERSION=$1
# Check if version is provided
if [ -z "$VERSION" ]; then
echo "❌ Error: Version number is required"
echo "Usage: ./bin/release <version>"
echo "Example: ./bin/release 1.0.0"
exit 1
fi
# Check if gh CLI is installed
if ! command -v gh &> /dev/null; then
echo "❌ Error: GitHub CLI (gh) is not installed"
echo "Please install it from: https://cli.github.com/"
exit 1
fi
# Check if we're in a git repository
if ! git rev-parse --git-dir > /dev/null 2>&1; then
echo "❌ Error: Not in a git repository"
exit 1
fi
# Check if there are uncommitted changes
if ! git diff-index --quiet HEAD --; then
echo "❌ Error: There are uncommitted changes. Please commit or stash them first."
exit 1
fi
# Get default branch from GitHub with better error handling
echo "🔍 Getting default branch from GitHub..."
DEFAULT_BRANCH_JSON=$(gh repo view --json defaultBranchRef 2>/dev/null)
if [ $? -ne 0 ]; then
echo "❌ Error: Failed to get repository information from GitHub"
echo " Please check your GitHub CLI authentication and repository access"
exit 1
fi
DEFAULT_BRANCH=$(echo "$DEFAULT_BRANCH_JSON" | grep -o '"name":"[^"]*"' | cut -d'"' -f4)
if [ -z "$DEFAULT_BRANCH" ]; then
echo "⚠️ Warning: Could not determine default branch, using 'main'"
DEFAULT_BRANCH="main"
fi
echo "📌 Local HEAD: $(git rev-parse HEAD)"
echo "📌 GitHub default branch: $DEFAULT_BRANCH"
# Fetch latest changes from remote
echo "🔄 Fetching latest changes from remote..."
git fetch origin
# Check if local branch is up-to-date with remote
LOCAL_HEAD=$(git rev-parse HEAD)
REMOTE_HEAD=$(git rev-parse origin/$DEFAULT_BRANCH 2>/dev/null)
if [ -z "$REMOTE_HEAD" ]; then
echo "❌ Error: Could not find remote branch origin/$DEFAULT_BRANCH"
exit 1
fi
if [ "$LOCAL_HEAD" != "$REMOTE_HEAD" ]; then
echo "❌ Error: Local branch is not up-to-date with remote"
echo " Local HEAD: $LOCAL_HEAD"
echo " Remote HEAD: $REMOTE_HEAD"
echo " Please pull the latest changes: git pull origin $DEFAULT_BRANCH"
exit 1
fi
echo "✅ Local repository is up-to-date with remote"
# Define the path to MollieApiClient.php
MOLLIE_CLIENT_FILE="src/MollieApiClient.php"
# Check if MollieApiClient.php exists
if [ ! -f "$MOLLIE_CLIENT_FILE" ]; then
echo "❌ Error: MollieApiClient.php not found at $MOLLIE_CLIENT_FILE"
exit 1
fi
# Check if tag already exists
if git tag -l | grep -q "^v$VERSION$"; then
echo "❌ Error: Tag v$VERSION already exists"
exit 1
fi
echo "🔄 Updating CLIENT_VERSION to $VERSION in MollieApiClient.php..."
# Update the CLIENT_VERSION constant in MollieApiClient.php
if sed -i.bak "s/public const CLIENT_VERSION = '[^']*'/public const CLIENT_VERSION = '$VERSION'/g" "$MOLLIE_CLIENT_FILE"; then
echo "✅ CLIENT_VERSION updated successfully"
# Remove backup file created by sed
rm "${MOLLIE_CLIENT_FILE}.bak"
else
echo "❌ Error: Failed to update CLIENT_VERSION"
exit 1
fi
# Check if the version was actually updated
if grep -q "CLIENT_VERSION = '$VERSION'" "$MOLLIE_CLIENT_FILE"; then
echo "✅ Version update verified"
else
echo "❌ Error: Version update verification failed"
exit 1
fi
# Commit the version update
echo "📝 Committing version update..."
git add "$MOLLIE_CLIENT_FILE"
git commit -m "Update CLIENT_VERSION to $VERSION"
echo "🏷️ Creating tag v$VERSION..."
# Create tag and push
git tag -a "v$VERSION" -m "Release $VERSION"
git push origin "v$VERSION"
echo "⏱️ Waiting for GitHub to recognize the new tag..."
sleep 3
# Verify tag exists on GitHub
echo "🔍 Verifying tag exists on GitHub..."
TAG_CHECK=$(gh release list --repo mollie/mollie-api-php 2>/dev/null | grep "v$VERSION" || echo "")
if [ -n "$TAG_CHECK" ]; then
echo "❌ Error: Release v$VERSION already exists on GitHub"
exit 1
fi
# Double-check that we can see the tag
GH_TAG_CHECK=$(gh api repos/mollie/mollie-api-php/git/refs/tags/v$VERSION 2>/dev/null || echo "not_found")
if [ "$GH_TAG_CHECK" = "not_found" ]; then
echo "⚠️ Warning: Tag not immediately visible on GitHub, waiting longer..."
sleep 5
fi
echo "🚀 Creating GitHub release..."
# Generate release from current HEAD with better error handling
RELEASE_OUTPUT=$(gh release create "v$VERSION" \
--target "$(git rev-parse HEAD)" \
--latest \
--generate-notes 2>&1)
if [ $? -eq 0 ]; then
echo "✅ Release v$VERSION created successfully!"
echo "$RELEASE_OUTPUT"
else
echo "❌ Error: Failed to create GitHub release"
echo "$RELEASE_OUTPUT"
echo ""
echo "🔍 Troubleshooting tips:"
echo " 1. Check if you have write permissions to the repository"
echo " 2. Verify your GitHub CLI authentication: gh auth status"
echo " 3. Try creating the release manually on GitHub.com"
echo " 4. Check if the tag was created successfully: git tag -l | grep v$VERSION"
exit 1
fi

View File

@ -0,0 +1,87 @@
{
"name": "mollie/mollie-api-php",
"description": "Mollie API client library for PHP. Mollie is a European Payment Service provider and offers international payment methods such as Mastercard, VISA, American Express and PayPal, and local payment methods such as iDEAL, Bancontact, SOFORT Banking, SEPA direct debit, Belfius Direct Net, KBC Payment Button and various gift cards such as Podiumcadeaukaart and fashioncheque.",
"keywords": [
"mollie",
"payment",
"service",
"ideal",
"creditcard",
"apple pay",
"mistercash",
"bancontact",
"sofort",
"sofortbanking",
"sepa",
"paypal",
"paysafecard",
"podiumcadeaukaart",
"przelewy24",
"banktransfer",
"direct debit",
"belfius",
"belfius direct net",
"refunds",
"api",
"payments",
"gateway",
"subscriptions",
"recurring",
"charges",
"kbc",
"cbc",
"gift cards",
"intersolve",
"fashioncheque",
"inghomepay",
"klarna",
"paylater",
"sliceit"
],
"homepage": "https://www.mollie.com/en/developers",
"license": "BSD-2-Clause",
"authors": [
{
"name": "Mollie B.V.",
"email": "info@mollie.com"
}
],
"require": {
"php": "^7.4|^8.0",
"ext-curl": "*",
"ext-json": "*",
"ext-openssl": "*",
"composer/ca-bundle": "^1.4",
"nyholm/psr7": "^1.8",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.1",
"psr/http-message": "^1.1|^2.0"
},
"require-dev": {
"brianium/paratest": "^6.11",
"guzzlehttp/guzzle": "^7.6",
"phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^9.6",
"symfony/var-dumper": "^5.4|^6.4|^7.2"
},
"suggest": {
"mollie/oauth2-mollie-php": "Use OAuth to authenticate with the Mollie API. This is needed for some endpoints. Visit https://docs.mollie.com/ for more information."
},
"config": {
"sort-packages": true
},
"autoload": {
"psr-4": {
"Mollie\\Api\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/",
"Tests\\Mollie\\Api\\": "tests/Mollie/API/"
}
},
"scripts": {
"test": "./vendor/bin/paratest"
}
}

View File

@ -0,0 +1,103 @@
# Debugging with Mollie API Client
## Overview
The Mollie API client provides powerful debugging capabilities to help you inspect and troubleshoot API requests and responses. The debugging functionality is implemented through middleware and automatically sanitizes sensitive data to prevent accidental exposure of credentials.
## Basic Usage
### Enable All Debugging
To enable both request and response debugging:
```php
$mollie = new \Mollie\Api\MollieApiClient();
$mollie->debug(); // Enables both request and response debugging
```
### Enable on Request
To enable debugging for a specific request:
```php
$request = new CreatePaymentRequest(...);
// enable output for request and response
$mollie->send($request->debug());
// only debug request
$mollie->send($request->debugRequest(die: true));
// only debug response
$mollie->send($request->debugResponse(die: true));
```
### Debug Specific Parts
You can choose to debug only requests or only responses:
```php
// Debug only requests
$mollie->debugRequest();
// Debug only responses
$mollie->debugResponse();
```
## Custom Debuggers
You can provide your own debugging functions to customize how debugging information is displayed:
```php
// Custom request debugger
$mollie->debugRequest(function($pendingRequest, $psrRequest) {
// Your custom debugging logic here
});
// Custom response debugger
$mollie->debugResponse(function($response, $psrResponse) {
// Your custom debugging logic here
});
```
## Security Features
### Automatic Sanitization
When debugging is enabled, the client automatically:
- Removes sensitive headers (Authorization, User-Agent, etc.)
- Sanitizes request data to prevent credential exposure
- Handles exceptions safely by removing sensitive data
### Die After Debug
For development purposes, you can halt execution after debugging output:
```php
$mollie->debug(die: true); // Will stop execution after debugging output
```
## Best Practices
1. **Development Only**: Never enable debugging in production environments
2. **Custom Debuggers**: When implementing custom debuggers, ensure they handle sensitive data appropriately
3. **Exception Handling**: Debug mode works with exceptions, helping you troubleshoot API errors safely
## Example Usage
```php
try {
$mollie = new \Mollie\Api\MollieApiClient();
$mollie->setApiKey("test_dHar4XY7LxsDOtmnkVtjNVWXLSlXsM");
// Enable debugging for development
$mollie->debug();
// Your API calls here
$payment = $mollie->payments->create([...]);
} catch (\Mollie\Api\Exceptions\ApiException $e) {
// Exception will include sanitized debug information
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```

View File

@ -0,0 +1,730 @@
# Introduction
Endpoint collections provide a fluent interface for interacting with Mollie's API. Each collection manages specific resource types like payments, customers, and subscriptions, offering methods to create, retrieve, update, and delete these resources.
## Setup
1. **Initialize the Mollie Client:**
```php
$mollie = new \Mollie\Api\MollieApiClient();
$mollie->setApiKey("test_*************************");
```
2. **Access Endpoints via the Client:**
```php
// Payments endpoint
$mollie->payments->...
// Customers endpoint
$mollie->customers->...
// Other endpoints
$mollie->balances->...
$mollie->orders->...
```
3. **Call methods**
**Simple: Using arrays**
This approach is direct but provides less type safety:
```php
$payment = $mollie->payments->create([
'amount' => [
'currency' => 'EUR',
'value' => '10.00'
],
'description' => 'My first API payment'
]);
```
**Advanced: Using typed array params**
You can use dedicated `Data` objects to add partial type safety to your parameters.
```php
use Mollie\Api\Http\Data\Money;
use Mollie\Api\Http\Data\CreatePaymentPayload;
$payment = $mollie->payments->create([
'description' => 'My first API payment',
'amount' => new Money('EUR', '10.00')
]);
```
If you're starting with an array and need to convert it into a structured request, you can use a specific factory designed for this purpose.
```php
use Mollie\Api\Http\Data\Money;
use Mollie\Api\Factories\CreatePaymentRequestFactory;
// Fully untyped data
$createPaymentRequest = CreatePaymentRequestFactory::new([
'amount' => [
'currency' => 'EUR',
'value' => '10.00'
],
'description' => 'My first API payment'
]);
// Partially untyped
$createPaymentRequest = CreatePaymentRequestFactory::new([
'amount' => new Money('EUR', '10.00'),
'description' => 'My first API payment'
]);
```
This method lets you effortlessly transform arrays into typed objects, thereby enhancing IDE support and increasing type safety while handling your data.
# Available Endpoints
## Balances
[Official Documentation](https://docs.mollie.com/reference/v2/balances-api/get-balance)
### Balance Information
```php
// Get primary balance
$balance = $mollie->balances->primary();
// Get specific balance
$balance = $mollie->balances->get('bal_12345');
// List balances
$balances = $mollie->balances->page();
```
## Balance Reports
[Official Documentation](https://docs.mollie.com/reference/v2/balance-reports-api/get-balance-report)
### Balance Report Details
```php
// Get report for specific balance
$report = $mollie->balanceReports->getForId('bal_12345', [
'from' => '2024-01-01',
'until' => '2024-01-31',
'grouping' => 'transaction-categories'
]);
```
## Balance Transactions
[Official Documentation](https://docs.mollie.com/reference/v2/balance-transactions-api/list-balance-transactions)
### Balance Transaction Management
```php
// Get transactions for a balance
$transactions = $mollie->balanceTransactions->pageFor($balance);
// Use iterator for all transactions
foreach ($mollie->balanceTransactions->iteratorFor($balance) as $transaction) {
echo $transaction->id;
}
```
## Chargebacks
[Official Documentation](https://docs.mollie.com/reference/v2/chargebacks-api/get-chargeback)
### Chargeback Management
```php
// Get all chargebacks
$chargebacks = $mollie->chargebacks->page();
// Use iterator for all chargebacks
foreach ($mollie->chargebacks->iterator() as $chargeback) {
echo $chargeback->id;
}
```
## Clients
[Official Documentation](https://docs.mollie.com/reference/v2/clients-api/get-client)
### Client Management
```php
// Get a specific client
$client = $mollie->clients->get('org_12345678', [
'testmode' => true
]);
// List all clients
$clients = $mollie->clients->page(
from: 'org_12345678',
limit: 50
);
```
## Customers
[Official Documentation](https://docs.mollie.com/reference/v2/customers-api/create-customer)
### Customer Management
```php
// Create a customer
$customer = $mollie->customers->create([
'name' => 'John Doe',
'email' => 'john@example.org',
]);
// Get a customer
$customer = $mollie->customers->get('cst_8wmqcHMN4U');
// Update a customer
$customer = $mollie->customers->update('cst_8wmqcHMN4U', [
'name' => 'Updated Name'
]);
// Delete a customer
$mollie->customers->delete('cst_8wmqcHMN4U');
// List customers
$customers = $mollie->customers->page();
```
## Invoices
[Official Documentation](https://docs.mollie.com/reference/v2/invoices-api/get-invoice)
### Invoice Management
```php
// Get a specific invoice
$invoice = $mollie->invoices->get('inv_xBEbP9rvAq');
// List all invoices
$invoices = $mollie->invoices->page(
from: 'inv_xBEbP9rvAq',
limit: 50
);
```
## Mandates
[Official Documentation](https://docs.mollie.com/reference/v2/mandates-api/create-mandate)
### Mandate Management
```php
// Create a mandate for a customer
$mandate = $mollie->mandates->createFor($customer, [
'method' => \Mollie\Api\Types\PaymentMethod::DIRECTDEBIT,
'consumerName' => 'John Doe',
'consumerAccount' => 'NL55INGB0000000000',
'consumerBic' => 'INGBNL2A',
'signatureDate' => '2024-01-01',
'mandateReference' => 'YOUR-COMPANY-MD13804'
]);
// Get a mandate
$mandate = $mollie->mandates->getFor($customer, 'mdt_h3gAaD5zP');
// Revoke a mandate
$mollie->mandates->revokeFor($customer, 'mdt_h3gAaD5zP');
// List mandates
$mandates = $mollie->mandates->pageFor($customer);
```
## Methods
[Official Documentation](https://docs.mollie.com/reference/v2/methods-api/get-method)
### Payment Methods
```php
// Get a method
$method = $mollie->methods->get(\Mollie\Api\Types\PaymentMethod::IDEAL);
// List all methods
$methods = $mollie->methods->all();
// List enabled methods
$methods = $mollie->methods->allEnabled([
'amount' => [
'currency' => 'EUR',
'value' => '100.00'
]
]);
```
### Method Issuers
```php
// Enable an issuer
$issuer = $mollie->methodIssuers->enable(
'pfl_v9hTwCvYqw',
\Mollie\Api\Types\PaymentMethod::IDEAL,
'ideal_INGBNL2A'
);
// Disable an issuer
$mollie->methodIssuers->disable(
'pfl_v9hTwCvYqw',
\Mollie\Api\Types\PaymentMethod::IDEAL,
'ideal_INGBNL2A'
);
```
## Onboarding
[Official Documentation](https://docs.mollie.com/reference/v2/onboarding-api/get-onboarding-status)
### Onboarding Management
```php
// Get onboarding status
$onboarding = $mollie->onboarding->status();
```
## Organizations
[Official Documentation](https://docs.mollie.com/reference/v2/organizations-api/get-organization)
### Organization Management
```php
// Get an organization
$organization = $mollie->organizations->get('org_12345678');
// Get current organization
$organization = $mollie->organizations->current();
// Get partner status
$partner = $mollie->organizations->partnerStatus();
```
## Permissions
[Official Documentation](https://docs.mollie.com/reference/v2/permissions-api/get-permission)
### Permission Management
```php
// Get a permission
$permission = $mollie->permissions->get('payments.read');
// List all permissions
$permissions = $mollie->permissions->list();
```
## Payments
[Official Documentation](https://docs.mollie.com/reference/v2/payments-api/create-payment)
### Payment Management
```php
// Create a payment
$payment = $mollie->payments->create([
'description' => 'Order #12345',
'amount' => [
'currency' => 'EUR',
'value' => '10.00'
],
'redirectUrl' => 'https://webshop.example.org/order/12345/',
'webhookUrl' => 'https://webshop.example.org/mollie-webhook/'
]);
// Get a payment
$payment = $mollie->payments->get('tr_7UhSN1zuXS');
// Update a payment
$payment = $mollie->payments->update('tr_7UhSN1zuXS', [
'description' => 'Updated description'
]);
// Cancel a payment
$mollie->payments->cancel('tr_7UhSN1zuXS');
// List payments
$payments = $mollie->payments->page();
```
### Payment Links
[Official Documentation](https://docs.mollie.com/reference/v2/payment-links-api/create-payment-link)
### Payment Captures
[Official Documentation](https://docs.mollie.com/reference/v2/captures-api/get-capture)
### Payment Chargebacks
```php
// Get a chargeback
$chargeback = $mollie->paymentChargebacks->getFor($payment, 'chb_n9z0tp');
// List chargebacks for payment
$chargebacks = $mollie->paymentChargebacks->pageFor($payment);
```
### Payment Routes
```php
// Create a delayed route
$route = $mollie->paymentRoutes->createFor(
$payment,
['value' => '10.00', 'currency' => 'EUR'],
['type' => 'organization', 'organizationId' => 'org_12345'],
'2025-01-01' // optional release date
);
// List payment routes
$routes = $mollie->paymentRoutes->listFor($payment);
// Update release date for a route
$route = $mollie->paymentRoutes->updateReleaseDateFor(
$payment,
'rt_abc123',
'2024-01-01'
);
```
## Profiles
[Official Documentation](https://docs.mollie.com/reference/v2/profiles-api/create-profile)
### Profile Management
```php
// Create a profile
$profile = $mollie->profiles->create([
'name' => 'My Test Profile',
'website' => 'https://example.org',
'email' => 'info@example.org',
'phone' => '+31612345678',
'mode' => 'test'
]);
// Get a profile
$profile = $mollie->profiles->get('pfl_v9hTwCvYqw');
// Get current profile
$profile = $mollie->profiles->getCurrent();
// Update a profile
$profile = $mollie->profiles->update('pfl_v9hTwCvYqw', [
'name' => 'Updated Profile Name'
]);
// Delete a profile
$mollie->profiles->delete('pfl_v9hTwCvYqw');
// List profiles
$profiles = $mollie->profiles->page();
```
### Profile Methods
```php
// Enable a method
$method = $mollie->profileMethods->enable(
'pfl_v9hTwCvYqw',
\Mollie\Api\Types\PaymentMethod::IDEAL
);
// Disable a method
$mollie->profileMethods->disable(
'pfl_v9hTwCvYqw',
\Mollie\Api\Types\PaymentMethod::IDEAL
);
```
## Refunds
[Official Documentation](https://docs.mollie.com/reference/v2/refunds-api/create-refund)
### Refund Management
```php
// Create a refund for a payment
$refund = $mollie->refunds->createForPayment($paymentId, [
'amount' => [
'currency' => 'EUR',
'value' => '15.00'
],
'description' => 'Refund for returned item'
]);
// List refunds
$refunds = $mollie->refunds->page();
```
## Sales Invoices
[Official Documentation TBA]
### Sales Invoice Management
```php
use Mollie\Api\Types\VatMode;
use Mollie\Api\Types\VatScheme;
use Mollie\Api\Types\PaymentTerm;
use Mollie\Api\Types\RecipientType;
use Mollie\Api\Types\RecipientType;
use Mollie\Api\Types\SalesInvoiceStatus;
// Create a sales invoice
$salesInvoice = $mollie->salesInvoices->create([
'currency' => 'EUR',
'status' => SalesInvoiceStatus::DRAFT,
'vatScheme' => VatScheme::STANDARD,
'vatMode' => VatMode::INCLUSIVE,
'paymentTerm' => PaymentTerm::DAYS_30,
'recipientIdentifier' => 'XXXXX',
'recipient' => [
'type' => RecipientType::CONSUMER,
'email' => 'darth@vader.deathstar',
'streetAndNumber' => 'Sample Street 12b',
'postalCode' => '2000 AA',
'city' => 'Amsterdam',
'country' => 'NL',
'locale' => 'nl_NL'
],
'lines' => [
[
'description' => 'Monthly subscription fee',
'quantity' => 1,
'vatRate' => '21',
'unitPrice' => [
'currency' => 'EUR',
'value' => '10,00'
]
]
]
]);
// Get a sales invoice
$salesInvoice = $mollie->salesInvoices->get('invoice_12345');
// Update a sales invoice
$salesInvoice = $mollie->salesInvoices->update('invoice_12345', [
'description' => 'Updated description'
]);
// Delete a sales invoice
$mollie->salesInvoices->delete('invoice_12345');
// List sales invoices
$salesInvoices = $mollie->salesInvoices->page();
```
## Sessions
[Official Documentation](https://docs.mollie.com/reference/v2/sessions-api/create-session)
### Session Management
```php
// Create a session
$session = $mollie->sessions->create([
'amount' => [
'currency' => 'EUR',
'value' => '100.00'
],
'description' => 'Session for service'
]);
// Get a session
$session = $mollie->sessions->get('sessionId');
```
## Settlements
[Official Documentation](https://docs.mollie.com/reference/v2/settlements-api/get-settlement)
### Settlement Management
```php
// Get a settlement
$settlement = $mollie->settlements->get('settlementId');
// List settlements
$settlements = $mollie->settlements->page();
```
### Settlement Captures
```php
// Get captures for a settlement
$captures = $mollie->settlementCaptures->pageFor($settlement);
// Use iterator
foreach ($mollie->settlementCaptures->iteratorFor($settlement) as $capture) {
echo $capture->id;
}
```
### Settlement Chargebacks
```php
// List chargebacks for a settlement
$chargebacks = $mollie->settlementChargebacks->pageFor($settlement);
// Use iterator
foreach ($mollie->settlementChargebacks->iteratorFor($settlement) as $chargeback) {
echo $chargeback->id;
}
```
### Settlement Payments
```php
// List payments in a settlement
$payments = $mollie->settlementPayments->pageFor($settlement);
// Use iterator
foreach ($mollie->settlementPayments->iteratorFor($settlement) as $payment) {
echo $payment->id;
}
```
## Subscriptions
[Official Documentation](https://docs.mollie.com/reference/v2/subscriptions-api/create-subscription)
### Subscription Management
```php
// Create a subscription
$subscription = $mollie->subscriptions->createForCustomer('customerId', [
'amount' => [
'currency' => 'EUR',
'value' => '25.00'
],
'interval' => '1 month',
'description' => 'Monthly subscription'
]);
// List subscriptions
$subscriptions = $mollie->subscriptions->pageForCustomer('customerId');
```
## Terminals
[Official Documentation](https://docs.mollie.com/reference/v2/terminals-api/get-terminal)
### Terminal Management
```php
// Get a terminal
$terminal = $mollie->terminals->get('terminalId');
// List terminals
$terminals = $mollie->terminals->page();
```
## Wallets
[Official Documentation](https://docs.mollie.com/reference/v2/wallets-api/request-apple-pay-payment-session)
### Wallet Management
```php
// Request an Apple Pay payment session
$session = $mollie->wallets->requestApplePayPaymentSession([
'domainName' => 'example.com',
'validationUrl' => 'https://apple-pay-gateway.apple.com/paymentservices/startSession'
]);
```
## Webhooks
[Official Documentation](https://docs.mollie.com/reference/v2/webhooks-api/create-webhook)
### Webhook Management
```php
use Mollie\Api\Types\WebhookEventType;
// Create a webhook
$webhook = $mollie->webhooks->create([
'url' => 'https://example.com/webhook',
'description' => 'Payment notifications',
'events' => [
WebhookEventType::PAYMENT_LINK_PAID,
WebhookEventType::PROFILE_VERIFIED
],
'secret' => 'my-secret-key-123'
]);
// Get a webhook
$webhook = $mollie->webhooks->get('wh_4KgGJJSZpH');
// Update a webhook
$webhook = $mollie->webhooks->update('wh_4KgGJJSZpH', [
'url' => 'https://updated-example.com/webhook',
'description' => 'Updated description'
]);
// Delete a webhook
$mollie->webhooks->delete('wh_4KgGJJSZpH');
// Test a webhook
$mollie->webhooks->test('wh_4KgGJJSZpH');
// List webhooks
$webhooks = $mollie->webhooks->page();
// Using convenience methods on webhook resource
$webhook = $mollie->webhooks->get('wh_4KgGJJSZpH');
$webhook->update(['description' => 'New description']);
$webhook->delete();
$webhook->test();
```
### Webhook Events
```php
// Get a webhook event
$webhookEvent = $mollie->webhookEvents->get('whev_abc123');
// Check event status using helper methods
if ($webhookEvent->wasDelivered()) {
echo "Webhook was successfully delivered\n";
} elseif ($webhookEvent->failed()) {
echo "Webhook delivery failed: {$webhookEvent->error}\n";
} elseif ($webhookEvent->hasRetryPending()) {
echo "Retry pending at: {$webhookEvent->nextRetryAt}\n";
}
```
## Common Patterns
### Pagination
Most list methods support pagination:
```php
// Get first page
$payments = $mollie->payments->page();
// Get specific page
$payments = $mollie->payments->page(
from: 'tr_7UhSN1zuXS', // Start from this ID
limit: 50 // Items per page
);
// Get all items using iterator
foreach ($mollie->payments->iterator() as $payment) {
echo $payment->id;
}
```
### Error Handling
Handle errors using `ApiException`:
```php
try {
$payment = $mollie->payments->get('tr_xxx');
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: {$e->getMessage()}";
}
```

View File

@ -0,0 +1,137 @@
# Exceptions
This document describes the exceptions that can be thrown by the Mollie API PHP client.
## Exception Hierarchy
### `MollieException`
The abstract base class for **all** exceptions in the Mollie API PHP client.
### `RequestException`
The `RequestException` serves as the base for all request-related exceptions.
### `ApiException`
Extends `RequestException` and serves as the base for all API-related exceptions. This is the parent class for all specific HTTP status code exceptions. This exception is also thrown when the Mollie API returns an error response that doesn't match any of the more specific exceptions below.
Properties and methods:
- `getDocumentationUrl()`: Returns the URL to the documentation for this error, if available
- `getDashboardUrl()`: Returns the URL to the dashboard for this error, if available
- `getRaisedAt()`: Returns the timestamp when the exception was raised
- `getPlainMessage()`: Returns the plain exception message without timestamp and metadata
## HTTP Status Code Exceptions
The following exceptions all extend `ApiException` and are thrown based on specific HTTP status codes returned by the API:
### `UnauthorizedException` (401)
Thrown when authentication fails. This typically happens when using an invalid API key.
### `ForbiddenException` (403)
Thrown when the request is understood but refused due to permission issues.
### `NotFoundException` (404)
Thrown when the requested resource does not exist.
### `MethodNotAllowedException` (405)
Thrown when the HTTP method used is not allowed for the requested endpoint.
### `RequestTimeoutException` (408)
Thrown when the request times out.
### `ValidationException` (422)
Thrown when the request data fails validation. This typically happens when required fields are missing or have invalid values.
### `TooManyRequestsException` (429)
Thrown when rate limiting is applied because too many requests were made in a short period.
### `ServiceUnavailableException` (503)
Thrown when the Mollie API service is temporarily unavailable.
## Network Exceptions
### `NetworkRequestException`
Thrown when a network error occurs during the request.
### `RetryableNetworkRequestException`
Thrown when a network error occurs that might be resolved by retrying the request.
## Other Exceptions
### `ClientException`
Base exception for client-related errors.
### `EmbeddedResourcesNotParseableException`
Thrown when embedded resources cannot be parsed.
### `IncompatiblePlatformException`
Thrown when the platform is incompatible with the Mollie API client.
### `InvalidAuthenticationException`
Thrown when the authentication method is invalid.
### `JsonParseException`
Thrown when JSON parsing fails.
### `LogicException`
Thrown when there is a logical error in the client code.
### `MissingAuthenticationException`
Thrown when no authentication method is provided.
### `ServerException`
Thrown when a server error occurs.
### `UnrecognizedClientException`
Thrown when the client is not recognized by the Mollie API.
## Handling Exceptions
Here's an example of how to handle exceptions:
```php
try {
$payment = $mollie->payments->get("tr_xxx");
} catch (Mollie\Api\Exceptions\UnauthorizedException $e) {
// Invalid API key
echo "Invalid API key: " . $e->getMessage();
} catch (Mollie\Api\Exceptions\NotFoundException $e) {
// Payment not found
echo "Payment not found: " . $e->getMessage();
} catch (Mollie\Api\Exceptions\MollieException $e) {
// Other Mollie error
echo "Mollie error: " . $e->getMessage();
}
```
You can also catch all Mollie exceptions at once:
```php
try {
$payment = $mollie->payments->get("tr_xxx");
} catch (Mollie\Api\Exceptions\MollieException $e) {
// Any Mollie error
echo "Mollie error: " . $e->getMessage();
}
```

View File

@ -0,0 +1,46 @@
# HTTP Adapters
## Overview
An HTTP adapter is a component that manages the communication between your application and an API. It abstracts the details of making HTTP requests and handling responses, allowing you to use different HTTP clients (like Guzzle, cURL, or custom clients) interchangeably without changing the way you interact with the API.
## MollieHttpAdapterPicker
The `MollieHttpAdapterPicker` is responsible for selecting the appropriate HTTP adapter based on the environment or the provided HTTP client. If no client is specified in the `MollieApiClient` constructor, it picks a default adapter.
### How It Works
1. **No Client Specified**: If no client is provided, it checks if Guzzle is available and picks the appropriate version of the Guzzle adapter.
2. **Custom Client Provided**: If a custom client is provided and it implements the `HttpAdapterContract`, it is used directly. If it's a Guzzle client, it is wrapped in a `GuzzleMollieHttpAdapter`.
3. **Unrecognized Client**: Throws an `UnrecognizedClientException` if the client is not recognized.
## Creating a Custom Adapter
To create a custom HTTP adapter:
1. Implement HttpAdapterContract.
2. Use HasDefaultFactories Trait to simplify adapter implementation.
```php
use Mollie\Api\Contracts\HttpAdapterContract;
use Mollie\Api\Traits\HasDefaultFactories;
class MyCustomHttpAdapter implements HttpAdapterContract {
use HasDefaultFactories;
public function sendRequest(PendingRequest $pendingRequest): Response {
// Implementation for sending HTTP request
}
public function version(): ?string {
return 'my-custom-adapter/1.0';
}
}
```
## Available Adapters
Out of the box, the Mollie API client provides several adapters:
- **GuzzleMollieHttpAdapter**: Wraps a Guzzle HTTP client for sending requests.
- **CurlMollieHttpAdapter**: Uses cURL for sending HTTP requests. This is the default if Guzzle is not available.
- **PSR18MollieHttpAdapter**: [PSR-18](https://www.php-fig.org/psr/psr-18/) compatible HTTP adapter

View File

@ -0,0 +1,60 @@
# Idempotency
## Overview
Idempotency ensures that multiple identical requests to an API result in the same outcome without creating duplicate resources or effects. This is crucial for operations that involve financial transactions to prevent unintended charges or state changes.
Mollie API supports idempotent requests for critical operations such as creating payments or refunds. This is automatically managed by the API client using the `ApplyIdempotencyKey` middleware.
For more detailed information, refer to the [Mollie API Idempotency Documentation](https://docs.mollie.com/reference/api-idempotency).
> [!Note]
> This package automatically handles idempotency for you. The information below allows you to override the default idempotency behavior.
## Automatic Idempotency Key Handling
The Mollie API client automatically handles idempotency for mutating requests (POST, PATCH, DELETE) through the `ApplyIdempotencyKey` middleware. This middleware checks if the request is a mutating type and applies an idempotency key if one is not already provided.
### How It Works
1. **Check Request Type**: The middleware checks if the request method is POST, PATCH, or DELETE.
2. **Apply Idempotency Key**: If the request is mutating, the middleware will:
- Use a custom idempotency key if provided.
- Otherwise, generate a key using the configured `IdempotencyKeyGenerator` if available.
- If no generator is set and no key is provided, no idempotency key is applied.
### Customizing Idempotency Key
You can customize how idempotency keys are generated or applied in several ways:
- **Provide a Custom Key**: Manually set an idempotency key for a specific request.
- **Use a Custom Generator**: Implement the `IdempotencyKeyGeneratorContract` to provide a custom method of generating idempotency keys.
#### Example: Setting a Custom Key
```php
$mollie->setIdempotencyKey('your_custom_key_here');
```
#### Example: Using a Custom Key Generator
Implement your key generator:
```php
class MyCustomKeyGenerator implements IdempotencyKeyGeneratorContract {
public function generate(): string {
return 'custom_' . bin2hex(random_bytes(10));
}
}
$mollie->setIdempotencyKeyGenerator(new MyCustomKeyGenerator());
```
## Best Practices
- **Unique Keys**: Ensure that each idempotency key is unique to each operation. Typically, UUIDs are used for this purpose.
- **Handling Errors**: Handle errors gracefully, especially when an API call with an idempotency key fails. Check the error message to understand whether you should retry the request with the same key or a new key.
## Conclusionz
Using idempotency keys in your API requests to Mollie can significantly enhance the reliability of your payment processing system, ensuring that payments are not unintentionally duplicated and that your application behaves predictably even in the face of network and service interruptions.

View File

@ -0,0 +1,94 @@
##### Multicurrency #####
Since API v2.0 it is now possible to create non-EUR payments for your customers.
A full list of available currencies can be found [in our documentation](https://docs.mollie.com/guides/multicurrency).
```php
$payment = $mollie->payments->create([
"amount" => [
"currency" => "USD",
"value" => "10.00"
],
//...
]);
```
_After creation, the `settlementAmount` will contain the EUR amount that will be settled on your account._
##### Create fully integrated iDEAL payments #####
To fully integrate iDEAL payments on your website, follow these additional steps:
1. Retrieve the list of issuers (banks) that support iDEAL.
```php
$method = $mollie->methods->get(\Mollie\Api\Types\PaymentMethod::IDEAL, ["include" => "issuers"]);
```
Use the `$method->issuers` list to let the customer pick their preferred issuer.
_`$method->issuers` will be a list of objects. Use the property `$id` of this object in the
API call, and the property `$name` for displaying the issuer to your customer._
2. Create a payment with the selected issuer:
```php
$payment = $mollie->payments->create([
"amount" => [
"currency" => "EUR",
"value" => "10.00"
],
"description" => "My first API payment",
"redirectUrl" => "https://webshop.example.org/order/12345/",
"webhookUrl" => "https://webshop.example.org/mollie-webhook/",
"method" => \Mollie\Api\Types\PaymentMethod::IDEAL,
"issuer" => $selectedIssuerId, // e.g. "ideal_INGBNL2A"
]);
```
_The `_links` property of the `$payment` object will contain an object `checkout` with a `href` property, which is a URL that points directly to the online banking environment of the selected issuer.
A short way of retrieving this URL can be achieved by using the `$payment->getCheckoutUrl()`._
For a more in-depth example, see [Example - iDEAL payment](./recipes/payments/create-ideal-payment.md).
#### Retrieving Payments ####
**[Retrieve Payment Documentation](https://docs.mollie.com/reference/v2/payments-api/get-payment)**
We can use the `$payment->id` to retrieve a payment and check if the payment `isPaid`.
```php
$payment = $mollie->payments->get($payment->id);
if ($payment->isPaid())
{
echo "Payment received.";
}
```
Or retrieve a collection of payments.
```php
$payments = $mollie->payments->page();
```
For an extensive example of listing payments with the details and status, see [Example - List Payments](./recipes/payments/list-payments.md).
#### Refunding payments ####
**[Refund Payment Documentation](https://docs.mollie.com/reference/v2/refunds-api/create-payment-refund)**
Our API provides support for refunding payments. It's important to note that there is no confirmation step, and all refunds are immediate and final. Refunds are available for all payment methods except for paysafecard and gift cards.
```php
$payment = $mollie->payments->get($payment->id);
// Refund € 2 of this payment
$refund = $payment->refund([
"amount" => [
"currency" => "EUR",
"value" => "2.00"
]
]);
```
#### Payment webhook ####
When the payment status changes, the `webhookUrl` you specified during payment creation will be called. You can use the `id` from the POST parameters to check the status and take appropriate actions.
For more details, refer to [Example - Webhook](./recipes/payments/handle-webhook.md).
For a working example, see [Example - Refund payment](./recipes/payments/refund-payment.md).

View File

@ -0,0 +1,32 @@
# Mollie API PHP Recipes
This directory contains recipes for common use cases with the Mollie API PHP client. Each recipe provides a practical example of how to use the API client for a specific task.
## Structure
The recipes are organized by resource type:
- `payments/` - Payment-related operations (create, update, refund, etc.)
- `customers/` - Customer management
- `mandates/` - Mandate operations
- `subscriptions/` - Subscription handling
- `captures/` - Payment capture operations
- `chargebacks/` - Chargeback handling
- `refunds/` - Refund operations
- `connect-balance-transfers/` - Connect balance transfer operations
- `webhooks/` - Webhook management and events
Each recipe includes:
- Complete code example
- Example response fields
- Additional notes and considerations
## Getting Started
Before using any recipe, make sure you have properly initialized the API client:
```php
use Mollie\Api\MollieApiClient;
$mollie = new MollieApiClient;
$mollie->setApiKey('test_dHar4XY7LxsDOtmnkVtjNVWXLSlXsM');
```

View File

@ -0,0 +1,67 @@
# List Account Capabilities
How to retrieve the capabilities of a Mollie account using OAuth.
## The Code
```php
use Mollie\Api\Http\Requests\ListCapabilitiesRequest;
use Mollie\Api\Http\Requests\GetCapabilityRequest;
try {
// Initialize the Mollie client with your OAuth access token
$mollie = new \Mollie\Api\MollieApiClient();
$mollie->setAccessToken('access_xxx');
// List all capabilities
$capabilities = $mollie->send(
new ListCapabilitiesRequest()
);
foreach ($capabilities as $capability) {
echo "Capability: {$capability->name}\n";
echo "- Status: {$capability->status}\n";
echo "- Description: {$capability->description}\n\n";
}
// Get a specific capability
$capability = $mollie->send(
new GetCapabilityRequest(
id: 'payments'
)
);
echo "Payment capability status: {$capability->status}\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$capability->id; // "payments"
$capability->name; // "Payments"
$capability->description; // "Accept payments from your customers"
$capability->status; // "active", "pending", "inactive"
```
## Available Capabilities
- `payments` - Accept payments from your customers
- `refunds` - Refund payments to your customers
- `settlements` - Receive settlements in your bank account
- `chargebacks` - Handle chargebacks from your customers
- `onboarding` - Complete onboarding to activate your account
- `organizations` - Create and manage organizations
## Additional Notes
- You need an OAuth access token to access capabilities
- The status indicates whether a capability is available for use:
- `active`: The capability is enabled and ready to use
- `pending`: The capability is being reviewed or requires additional information
- `inactive`: The capability is disabled or not available
- Some capabilities may require additional verification or documentation
- Capabilities vary by country and account type
- Check capabilities before using certain features to ensure they are available

View File

@ -0,0 +1,151 @@
# Create a Connect Balance Transfer
How to create a balance transfer between two connected Mollie balances using the API.
## The Code
```php
use Mollie\Api\Http\Data\Money;
use Mollie\Api\Http\Data\TransferParty;
use Mollie\Api\Http\Requests\CreateConnectBalanceTransferRequest;
use Mollie\Api\Types\ConnectBalanceTransferCategory;
try {
// Initialize the Mollie client with your OAuth access token
$mollie = new \Mollie\Api\MollieApiClient();
$mollie->setAccessToken('access_xxx');
// Create a balance transfer
$balanceTransfer = $mollie->send(
new CreateConnectBalanceTransferRequest(
amount: new Money(
currency: 'EUR',
value: '100.00'
),
description: 'Transfer from balance A to balance B',
source: new TransferParty(
id: 'org_12345678',
description: 'Payment from Organization A'
),
destination: new TransferParty(
id: 'org_87654321',
description: 'Payment to Organization B'
),
category: ConnectBalanceTransferCategory::MANUAL_CORRECTION
)
);
echo "Balance transfer created: {$balanceTransfer->id}\n";
echo "Amount: {$balanceTransfer->amount->currency} {$balanceTransfer->amount->value}\n";
echo "Status: Transfer initiated\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## Using Endpoint Collections (Legacy Style)
```php
try {
// Create a balance transfer using endpoint collections
$balanceTransfer = $mollie->connectBalanceTransfers->create([
'amount' => [
'currency' => 'EUR',
'value' => '100.00'
],
'description' => 'Transfer from balance A to balance B',
'source' => [
'type' => 'organization',
'id' => 'org_12345678',
'description' => 'Payment from Organization A'
],
'destination' => [
'type' => 'organization',
'id' => 'org_87654321',
'description' => 'Payment to Organization B'
],
'category' => 'manual_correction'
]);
echo "Balance transfer created: {$balanceTransfer->id}\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$balanceTransfer->id; // "cbt_4KgGJJSZpH"
$balanceTransfer->resource; // "connect-balance-transfer"
$balanceTransfer->amount->currency; // "EUR"
$balanceTransfer->amount->value; // "100.00"
$balanceTransfer->description; // "Transfer from balance A to balance B"
$balanceTransfer->status; // "created", "failed", "succeeded"
$balanceTransfer->statusReason; // Object with status reason (if applicable)
$balanceTransfer->category; // "manual_correction", "purchase", "refund", etc.
$balanceTransfer->source->type; // "organization"
$balanceTransfer->source->id; // "org_12345678"
$balanceTransfer->source->description; // "Payment from Organization A"
$balanceTransfer->destination->type; // "organization"
$balanceTransfer->destination->id; // "org_87654321"
$balanceTransfer->destination->description; // "Payment to Organization B"
$balanceTransfer->executedAt; // "2023-12-25T10:31:00+00:00" (null if not executed)
$balanceTransfer->mode; // "live" or "test"
$balanceTransfer->createdAt; // "2023-12-25T10:30:54+00:00"
```
## Transfer Status
The transfer status indicates the current state of the transfer:
```php
// Check transfer status
if ($balanceTransfer->status === 'succeeded') {
echo "Transfer completed successfully\n";
echo "Executed at: {$balanceTransfer->executedAt}\n";
} elseif ($balanceTransfer->status === 'failed') {
echo "Transfer failed\n";
if ($balanceTransfer->statusReason) {
echo "Reason: " . json_encode($balanceTransfer->statusReason) . "\n";
}
}
```
## Transfer Categories
Different transfer categories may have different fees:
- `invoice_collection` - Collecting invoice payments
- `purchase` - Purchase-related transfers
- `chargeback` - Chargeback transfers
- `refund` - Refund transfers
- `service_penalty` - Service penalty fees
- `discount_compensation` - Discount compensations
- `manual_correction` - Manual corrections
- `other_fee` - Other fees
## Additional Notes
- **OAuth Required**: You need an OAuth access token to create balance transfers. API keys are not supported for this endpoint.
- **Sub-merchant consent**: You must have a documented consent from your sub-merchants authorizing balance movements.
- **Balance Ownership**: You can only transfer funds between balances that belong to organizations connected through your OAuth app.
- **Transfer Speed**: Balance transfers are processed immediately. The funds are moved instantly between the balances.
- **Status Tracking**: Monitor the `status` field to track transfer completion. Check `executedAt` for the exact completion time.
- **Currency Requirements**:
- Both balances must use the same currency
- The transfer amount must match the balance currency
- **Balance Validation**:
- The source balance must have sufficient available funds
- Both balances must be active and operational
- **Use Cases**:
- Moving funds between merchant balances in a marketplace
- Redistributing funds across different connected accounts
- Managing liquidity between multiple balances
- **Permissions**: Your OAuth app must have the appropriate scopes to access and transfer between the balances
- **Idempotency**: Consider using idempotency keys when creating transfers to prevent duplicate transfers in case of network issues
## Related Resources
- [List Connect Balance Transfers](list-balance-transfers.md)
- [Get Connect Balance Transfer](get-balance-transfer.md)

View File

@ -0,0 +1,105 @@
# Get a Connect Balance Transfer
How to retrieve details of a specific connect balance transfer using the Mollie API.
## The Code
```php
use Mollie\Api\Http\Requests\GetConnectBalanceTransferRequest;
try {
// Initialize the Mollie client with your OAuth access token
$mollie = new \Mollie\Api\MollieApiClient();
$mollie->setAccessToken('access_xxx');
// Get a specific balance transfer
$balanceTransfer = $mollie->send(
new GetConnectBalanceTransferRequest(
id: 'cbt_4KgGJJSZpH'
)
);
echo "Balance Transfer {$balanceTransfer->id}:\n";
echo "- Amount: {$balanceTransfer->amount->currency} {$balanceTransfer->amount->value}\n";
echo "- Description: {$balanceTransfer->description}\n";
echo "- From: {$balanceTransfer->source->balanceId}\n";
echo "- To: {$balanceTransfer->destination->balanceId}\n";
echo "- Created: {$balanceTransfer->createdAt}\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## Using Endpoint Collections (Legacy Style)
```php
try {
// Get a balance transfer using endpoint collections
$balanceTransfer = $mollie->connectBalanceTransfers->get('cbt_4KgGJJSZpH');
echo "Transfer amount: {$balanceTransfer->amount->value}\n";
echo "Transfer description: {$balanceTransfer->description}\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$balanceTransfer->id; // "cbt_4KgGJJSZpH"
$balanceTransfer->resource; // "connect-balance-transfer"
$balanceTransfer->amount->currency; // "EUR"
$balanceTransfer->amount->value; // "100.00"
$balanceTransfer->description; // "Transfer from balance A to balance B"
$balanceTransfer->status; // "created", "failed", "succeeded"
$balanceTransfer->statusReason; // Object with status reason (if applicable)
$balanceTransfer->category; // "manual_correction", "purchase", "refund", etc.
$balanceTransfer->source->type; // "organization"
$balanceTransfer->source->id; // "org_12345678"
$balanceTransfer->source->description; // "Payment from Organization A"
$balanceTransfer->destination->type; // "organization"
$balanceTransfer->destination->id; // "org_87654321"
$balanceTransfer->destination->description; // "Payment to Organization B"
$balanceTransfer->executedAt; // "2023-12-25T10:31:00+00:00" (null if not executed)
$balanceTransfer->mode; // "live" or "test"
$balanceTransfer->createdAt; // "2023-12-25T10:30:54+00:00"
$balanceTransfer->_links; // Object containing relevant URLs
```
## Transfer Party Details
Access the transfer party information:
```php
// Source party details
echo "Source Organization:\n";
echo "- Type: {$balanceTransfer->source->type}\n"; // "organization"
echo "- ID: {$balanceTransfer->source->id}\n"; // Organization token
echo "- Description: {$balanceTransfer->source->description}\n";
// Destination party details
echo "Destination Organization:\n";
echo "- Type: {$balanceTransfer->destination->type}\n";
echo "- ID: {$balanceTransfer->destination->id}\n";
echo "- Description: {$balanceTransfer->destination->description}\n";
```
## Additional Notes
- **OAuth Required**: You need an OAuth access token to retrieve balance transfer details
- **Access Control**: You can only retrieve transfers involving balances accessible through your OAuth app
- **Transfer Tracking**: Use the transfer ID to track and audit balance movements
- **Embedded Data**: The response may include embedded balance details for both source and destination balances
- **Links**: The `_links` object contains URLs to related resources:
- `self`: Link to this balance transfer
- `documentation`: Link to API documentation
- **Audit Trail**: Store transfer IDs in your system for reconciliation and reporting purposes
- **Error Handling**:
- Returns 404 if the transfer ID doesn't exist
- Returns 403 if you don't have permission to access this transfer
## Related Resources
- [Create Connect Balance Transfer](create-balance-transfer.md)
- [List Connect Balance Transfers](list-balance-transfers.md)

View File

@ -0,0 +1,177 @@
# List Connect Balance Transfers
How to retrieve a list of all connect balance transfers using the Mollie API.
## The Code
```php
use Mollie\Api\Http\Requests\ListConnectBalanceTransfersRequest;
try {
// Initialize the Mollie client with your OAuth access token
$mollie = new \Mollie\Api\MollieApiClient();
$mollie->setAccessToken('access_xxx');
// List all balance transfers
$balanceTransfers = $mollie->send(
new ListConnectBalanceTransfersRequest()
);
foreach ($balanceTransfers as $transfer) {
echo "Transfer {$transfer->id}:\n";
echo "- Amount: {$transfer->amount->currency} {$transfer->amount->value}\n";
echo "- Description: {$transfer->description}\n";
echo "- From: {$transfer->source->balanceId}\n";
echo "- To: {$transfer->destination->balanceId}\n";
echo "- Created: {$transfer->createdAt}\n\n";
}
// Get the next page if available
if ($balanceTransfers->hasNext()) {
$nextPage = $balanceTransfers->next();
// Process next page...
}
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## With Pagination Parameters
```php
use Mollie\Api\Http\Requests\ListConnectBalanceTransfersRequest;
try {
// List balance transfers with pagination
$balanceTransfers = $mollie->send(
new ListConnectBalanceTransfersRequest(
from: 'cbt_8KhHNOSdpL', // Start from this ID
limit: 50, // Limit to 50 results
sort: 'createdAt' // Sort by creation date
)
);
foreach ($balanceTransfers as $transfer) {
// Process each transfer...
}
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## Using Iterator for All Transfers
```php
use Mollie\Api\Http\Requests\ListConnectBalanceTransfersRequest;
try {
// Use iterator to automatically handle pagination
$request = new ListConnectBalanceTransfersRequest();
$allTransfers = $mollie->send($request->useIterator());
foreach ($allTransfers as $transfer) {
echo "Transfer {$transfer->id}: ";
echo "{$transfer->amount->currency} {$transfer->amount->value}\n";
}
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## Using Endpoint Collections (Legacy Style)
```php
try {
// List balance transfers using endpoint collections
$balanceTransfers = $mollie->connectBalanceTransfers->page();
foreach ($balanceTransfers as $transfer) {
echo "Transfer: {$transfer->description}\n";
echo "Amount: {$transfer->amount->value}\n\n";
}
// Use iterator for automatic pagination
foreach ($mollie->connectBalanceTransfers->iterator() as $transfer) {
// Process each transfer across all pages...
}
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
// Collection properties
$balanceTransfers->count; // Number of transfers in current page
$balanceTransfers->_links; // Pagination links
// Individual transfer properties
$transfer->id; // "cbt_4KgGJJSZpH"
$transfer->resource; // "connect-balance-transfer"
$transfer->amount->currency; // "EUR"
$transfer->amount->value; // "100.00"
$transfer->description; // "Transfer from balance A to balance B"
$transfer->status; // "created", "failed", "succeeded"
$transfer->category; // "manual_correction", "purchase", "refund", etc.
$transfer->source->type; // "organization"
$transfer->source->id; // "org_12345678"
$transfer->source->description; // "Payment from Organization A"
$transfer->destination->type; // "organization"
$transfer->destination->id; // "org_87654321"
$transfer->destination->description; // "Payment to Organization B"
$transfer->executedAt; // "2023-12-25T10:31:00+00:00" (null if not executed)
$transfer->mode; // "live" or "test"
$transfer->createdAt; // "2023-12-25T10:30:54+00:00"
```
## Pagination Methods
```php
// Check if there's a next page
if ($balanceTransfers->hasNext()) {
$nextPage = $balanceTransfers->next();
}
// Check if there's a previous page
if ($balanceTransfers->hasPrevious()) {
$previousPage = $balanceTransfers->previous();
}
// Get auto-iterator for all transfers
$iterator = $balanceTransfers->getAutoIterator();
foreach ($iterator as $transfer) {
// Processes all transfers across all pages automatically
}
```
## Additional Notes
- **OAuth Required**: You need an OAuth access token to list balance transfers
- **Access Scope**: The list includes only transfers involving balances accessible through your OAuth app
- **Pagination**:
- Results are paginated with a default limit
- Use `from` parameter to start from a specific transfer ID
- Use `limit` to control the number of results per page (max 250)
- **Sorting**:
- Transfers are returned in descending order by creation date (newest first)
- Use the `sort` parameter to customize ordering
- **Filtering**: Currently, there are no filter parameters available. All accessible transfers are returned.
- **Performance**:
- Use the iterator for processing large numbers of transfers
- The iterator automatically handles pagination
- Set appropriate limits to balance between API calls and memory usage
- **Iteration Direction**:
- By default, iteration goes forward (newest to oldest)
- Set `iterateBackwards: true` to reverse the direction
- **Use Cases**:
- Financial reporting and reconciliation
- Audit trails for balance movements
- Analyzing transfer patterns
- Monitoring platform activity
## Related Resources
- [Create Connect Balance Transfer](create-balance-transfer.md)
- [Get Connect Balance Transfer](get-balance-transfer.md)

View File

@ -0,0 +1,138 @@
# Customer Payments
How to create and manage payments for customers using the Mollie API.
## First Payment (For Recurring)
```php
use Mollie\Api\Http\Data\Money;
use Mollie\Api\Http\Requests\CreateCustomerPaymentRequest;
use Mollie\Api\Types\SequenceType;
try {
// Create the first payment for a customer
$payment = $mollie->send(
new CreateCustomerPaymentRequest(
customerId: 'cst_8wmqcHMN4U',
description: 'First payment - Order #12345',
amount: new Money(
currency: 'EUR',
value: '29.95'
),
redirectUrl: 'https://example.com/payments/return?order_id=12345',
webhookUrl: 'https://example.com/payments/webhook',
metadata: [
'order_id' => '12345'
],
sequenceType: SequenceType::FIRST // This creates a mandate for future payments
)
);
// Redirect the customer to complete the payment
header('Location: ' . $payment->getCheckoutUrl(), true, 303);
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## Recurring Payment
```php
use Mollie\Api\Http\Data\Money;
use Mollie\Api\Http\Requests\CreateCustomerPaymentRequest;
use Mollie\Api\Types\SequenceType;
try {
// Create a recurring payment using the mandate
$payment = $mollie->send(
new CreateCustomerPaymentRequest(
customerId: 'cst_8wmqcHMN4U',
description: 'Recurring payment - Order #12346',
amount: new Money(
currency: 'EUR',
value: '29.95'
),
webhookUrl: 'https://example.com/payments/webhook',
metadata: [
'order_id' => '12346'
],
sequenceType: SequenceType::RECURRING // This uses the mandate created by the first payment
)
);
// The payment will be either pending or paid immediately
echo "Payment status: {$payment->status}\n";
echo "Used mandate: {$payment->mandateId}\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## List Customer Payments
```php
use Mollie\Api\Http\Requests\GetPaginatedCustomerPaymentsRequest;
use Mollie\Api\Resources\PaymentCollection;
try {
// Get all payments for a customer
/** @var PaymentCollection */
$payments = $mollie->send(
new GetPaginatedCustomerPaymentsRequest(
customerId: 'cst_8wmqcHMN4U'
)
);
foreach ($payments as $payment) {
echo "Payment {$payment->id}:\n";
echo "- Description: {$payment->description}\n";
echo "- Amount: {$payment->amount->currency} {$payment->amount->value}\n";
echo "- Status: {$payment->status}\n";
if ($payment->hasRefunds()) {
echo "- Has been (partially) refunded\n";
}
if ($payment->hasChargebacks()) {
echo "- Has been charged back\n";
}
echo "\n";
}
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Payment Response
```php
$payment->id; // "tr_7UhSN1zuXS"
$payment->customerId; // "cst_8wmqcHMN4U"
$payment->mode; // "live" or "test"
$payment->description; // "Order #12345"
$payment->metadata; // Object containing custom metadata
$payment->status; // "open", "pending", "paid", "failed", "expired", "canceled"
$payment->isCancelable; // Whether the payment can be canceled
$payment->sequenceType; // "first" or "recurring"
$payment->redirectUrl; // URL to redirect the customer to
$payment->webhookUrl; // URL for webhook notifications
$payment->createdAt; // "2024-02-24T12:13:14+00:00"
```
## Additional Notes
- First create a customer before creating customer payments
- The first payment creates a mandate for future recurring payments
- Sequence types:
- `first`: Creates a mandate for future payments
- `recurring`: Uses an existing mandate
- Recurring payments:
- Don't need a `redirectUrl` as they use the stored mandate
- Will be executed immediately without customer interaction
- May still be pending depending on the payment method
- The mandate status determines if recurring payments can be created
- Store the payment ID and status in your database
- Always implement proper webhook handling
- Available payment methods may differ for first and recurring payments
- Some payment methods don't support recurring payments

View File

@ -0,0 +1,125 @@
# Manage Customers
How to create, update, and delete customers using the Mollie API.
## Create a Customer
```php
use Mollie\Api\Http\Requests\CreateCustomerRequest;
try {
// Create a new customer
$customer = $mollie->send(
new CreateCustomerRequest(
name: 'Luke Skywalker',
email: 'luke@example.com',
locale: 'en_US',
metadata: [
'isJedi' => true
]
)
);
echo "New customer created: {$customer->id}\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## Update a Customer
```php
use Mollie\Api\Http\Requests\GetCustomerRequest;
use Mollie\Api\Http\Requests\UpdateCustomerRequest;
try {
// First retrieve the customer you want to update
$customer = $mollie->send(
new GetCustomerRequest(
id: 'cst_8wmqcHMN4U'
)
);
// Update specific customer fields
$customer = $mollie->send(
new UpdateCustomerRequest(
id: $customer->id,
name: 'Luke Sky',
email: 'luke@example.com',
locale: 'en_US',
metadata: [
'isJedi' => true
]
// Fields we don't specify will keep their current values
)
);
echo "Customer updated: {$customer->name}\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## Delete a Customer
```php
use Mollie\Api\Http\Requests\DeleteCustomerRequest;
try {
// Delete a customer
$mollie->send(
new DeleteCustomerRequest(
id: 'cst_8wmqcHMN4U'
)
);
echo "Customer deleted\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$customer->id; // "cst_8wmqcHMN4U"
$customer->name; // "Luke Skywalker"
$customer->email; // "luke@example.com"
$customer->locale; // "en_US"
$customer->metadata; // Object containing custom metadata
$customer->mode; // "live" or "test"
$customer->createdAt; // "2024-02-24T12:13:14+00:00"
```
## Additional Notes
- Customers are used to store recurring payment details
- Each customer can have multiple mandates for different payment methods
- The customer ID is required for:
- Creating recurring payments
- Creating subscriptions
- Retrieving payment history
- Customer data should be kept in sync with your own database
- Available locales:
- `en_US` English (US)
- `en_GB` English (UK)
- `nl_NL` Dutch (Netherlands)
- `nl_BE` Dutch (Belgium)
- `fr_FR` French (France)
- `fr_BE` French (Belgium)
- `de_DE` German (Germany)
- `de_AT` German (Austria)
- `de_CH` German (Switzerland)
- `es_ES` Spanish (Spain)
- `ca_ES` Catalan (Spain)
- `pt_PT` Portuguese (Portugal)
- `it_IT` Italian (Italy)
- `nb_NO` Norwegian (Norway)
- `sv_SE` Swedish (Sweden)
- `fi_FI` Finnish (Finland)
- `da_DK` Danish (Denmark)
- `is_IS` Icelandic (Iceland)
- `hu_HU` Hungarian (Hungary)
- `pl_PL` Polish (Poland)
- `lv_LV` Latvian (Latvia)
- `lt_LT` Lithuanian (Lithuania)

View File

@ -0,0 +1,84 @@
# List Mollie Invoices
How to retrieve your Mollie invoices using the API.
## The Code
```php
use Mollie\Api\Http\Requests\GetPaginatedInvoiceRequest;
try {
// Initialize with OAuth (required for invoices)
$mollie = new \Mollie\Api\MollieApiClient();
$mollie->setAccessToken('access_xxx');
// Get all invoices
$response = $mollie->send(
new GetPaginatedInvoiceRequest()
);
foreach ($response as $invoice) {
echo "Invoice {$invoice->reference}:\n";
echo "- Status: {$invoice->status}\n";
echo "- Issued: {$invoice->issuedAt}\n";
echo "- Paid: {$invoice->paidAt}\n";
echo "- Due: {$invoice->dueAt}\n\n";
echo "Lines:\n";
foreach ($invoice->lines as $line) {
echo "- {$line->description}\n";
echo " Period: {$line->period}\n";
echo " Count: {$line->count}\n";
echo " VAT: {$line->vatPercentage}%\n";
echo " Amount: {$line->amount->currency} {$line->amount->value}\n\n";
}
echo "Totals:\n";
echo "- Net: {$invoice->netAmount->currency} {$invoice->netAmount->value}\n";
echo "- VAT: {$invoice->vatAmount->currency} {$invoice->vatAmount->value}\n";
echo "- Gross: {$invoice->grossAmount->currency} {$invoice->grossAmount->value}\n\n";
echo "PDF: {$invoice->_links->pdf->href}\n\n";
}
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$invoice->id; // "inv_xBEbP9rvAq"
$invoice->reference; // "2024.10000"
$invoice->vatNumber; // "NL123456789B01"
$invoice->status; // "paid", "open"
$invoice->issuedAt; // "2024-02-24"
$invoice->paidAt; // "2024-02-24"
$invoice->dueAt; // "2024-03-24"
$invoice->netAmount; // Object containing amount excluding VAT
$invoice->vatAmount; // Object containing VAT amount
$invoice->grossAmount; // Object containing amount including VAT
$invoice->lines; // Array of invoice lines
$invoice->_links->pdf->href; // URL to download PDF invoice
```
## Invoice Line Details
```php
$line->period; // "2024-01"
$line->description; // "iDEAL transaction fees"
$line->count; // 1337
$line->vatPercentage; // "21.00"
$line->amount; // Object containing line amount
```
## Additional Notes
- OAuth access token is required to access invoices
- Invoices are generated monthly for your Mollie account
- Each invoice line represents a different type of fee:
- Transaction fees per payment method
- Refund fees
- Chargeback fees
- Other service fees
- The PDF invoice is available through the `_links.pdf.href` URL

View File

@ -0,0 +1,97 @@
# Manage Customer Mandates
How to create, list, and revoke mandates for recurring payments using the Mollie API.
## Create a Mandate
```php
use Mollie\Api\Http\Requests\CreateMandateRequest;
use Mollie\Api\Types\MandateMethod;
try {
// Create a SEPA Direct Debit mandate
$mandate = $mollie->send(
new CreateMandateRequest(
customerId: 'cst_8wmqcHMN4U',
method: MandateMethod::DIRECTDEBIT,
consumerName: 'B. A. Example',
consumerAccount: 'NL34ABNA0243341423'
)
);
echo "New mandate created: {$mandate->id}\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## List Mandates
```php
use Mollie\Api\Http\Requests\GetPaginatedMandateRequest;
try {
// List all mandates for a customer
$response = $mollie->send(
new GetPaginatedMandateRequest(
customerId: 'cst_8wmqcHMN4U'
)
);
foreach ($response as $mandate) {
echo "Mandate {$mandate->id}:\n";
echo "- Method: {$mandate->method}\n";
echo "- Status: {$mandate->status}\n";
echo "- Details: {$mandate->details->consumerName}\n";
echo " {$mandate->details->consumerAccount}\n\n";
}
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## Revoke a Mandate
```php
use Mollie\Api\Http\Requests\RevokeMandateRequest;
try {
// Revoke a specific mandate
$mollie->send(
new RevokeMandateRequest(
customerId: 'cst_8wmqcHMN4U',
mandateId: 'mdt_h3gAaD5zP'
)
);
echo "Mandate revoked\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$mandate->id; // "mdt_h3gAaD5zP"
$mandate->status; // "valid", "pending", "invalid"
$mandate->method; // "directdebit"
$mandate->details; // Object containing mandate details
$mandate->customerId; // "cst_8wmqcHMN4U"
$mandate->createdAt; // "2024-02-24T12:13:14+00:00"
$mandate->signatureDate; // "2024-02-24" (optional)
$mandate->mandateReference; // "YOUR-COMPANY-MD13804" (optional)
```
## Additional Notes
- Mandates are used for recurring payments
- A customer can have multiple mandates
- Only valid mandates can be used for payments
- Available mandate methods:
- `directdebit`: SEPA Direct Debit
- `creditcard`: Credit card
- Mandate status:
- `valid`: The mandate is valid and can be used for payments
- `pending`: The mandate is pending and cannot be used yet
- `invalid`: The mandate is invalid and cannot be used

View File

@ -0,0 +1,84 @@
# Create a Client Link
How to create a client link to onboard new merchants to Mollie through your app.
## The Code
```php
use Mollie\Api\Http\Data\Owner;
use Mollie\Api\Resources\ClientLink;
use Mollie\Api\Http\Data\OwnerAddress;
use Mollie\Api\Http\Requests\CreateClientLinkRequest;
try {
// Create a client link for a new merchant
/** @var ClientLink $clientLink */
$clientLink = $mollie->send(
new CreateClientLinkRequest(
owner: new Owner(
email: 'merchant@example.com',
givenName: 'John',
familyName: 'Doe',
locale: 'en_US'
),
organizationName: 'Example Store',
address: new OwnerAddress(
countryCode: 'NL',
streetAndNumber: 'Keizersgracht 313',
postalCode: '1016 EE',
city: 'Amsterdam'
),
registrationNumber: '30204462',
vatNumber: 'NL123456789B01'
)
);
// Generate the redirect URL for the merchant
$redirectUrl = $clientLink->getRedirectUrl(
clientId: 'app_j9Pakf56Ajta6Y65AkdTtAv',
state: bin2hex(random_bytes(8)), // Random state to prevent CSRF
prompt: 'force', // Always show login screen
scopes: [
'onboarding.read',
'onboarding.write'
]
);
// Redirect the merchant to complete their onboarding
header('Location: ' . $redirectUrl, true, 303);
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$clientLink->id; // "csr_wJPGBj7sFr"
$clientLink->resource; // "client-link"
$clientLink->status; // "pending"
$clientLink->createdAt; // "2024-02-24T12:13:14+00:00"
$clientLink->expiresAt; // "2024-02-25T12:13:14+00:00"
```
## Additional Notes
- Client links are used to onboard new merchants to Mollie through your app
- The link expires after 24 hours
- Required merchant information:
- Owner details (name, email, locale)
- Organization details (name, address)
- Registration number (Chamber of Commerce number)
- VAT number (if applicable)
- The `state` parameter should be:
- Random and unique
- Stored in your session
- Verified when the merchant returns to prevent CSRF attacks
- Available scopes:
- `onboarding.read`: View onboarding status
- `onboarding.write`: Update onboarding information
- The merchant will need to:
1. Create a Mollie account or log in
2. Connect their account to your app
3. Complete the onboarding process
- You can track the onboarding status through the OAuth APIs

View File

@ -0,0 +1,71 @@
# Manage Payment Links
How to create and list payment links using the Mollie API.
## Create a Payment Link
```php
use Mollie\Api\Http\Requests\CreatePaymentLinkRequest;
try {
// Create a payment link
$paymentLink = $mollie->send(
new CreatePaymentLinkRequest([
'amount' => [
'currency' => 'EUR',
'value' => '10.00'
],
'description' => 'Bicycle tires',
'expiresAt' => '2026-01-01T12:00:00', // optional
'webhookUrl' => 'https://example.com/webhook' // optional
])
);
// Redirect the customer to the payment link
header('Location: ' . $paymentLink->getCheckoutUrl(), true, 303);
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## List Payment Links
```php
use Mollie\Api\Http\Requests\GetPaginatedPaymentLinksRequest;
try {
// List all payment links
$response = $mollie->send(new GetPaginatedPaymentLinksRequest);
foreach ($response as $paymentLink) {
echo "Payment Link {$paymentLink->id}:\n";
echo "- Description: {$paymentLink->description}\n";
echo "- Amount: {$paymentLink->amount->currency} {$paymentLink->amount->value}\n";
echo "- Status: {$paymentLink->status}\n";
echo "- Created: {$paymentLink->createdAt}\n";
echo "- URL: {$paymentLink->getCheckoutUrl()}\n\n";
}
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$paymentLink->id; // "pl_4Y0eZitmBnQ6IDoMqZQKh"
$paymentLink->description; // "Bicycle tires"
$paymentLink->amount; // Object containing amount and currency
$paymentLink->status; // "paid", "open", "expired"
$paymentLink->createdAt; // "2024-02-24T12:13:14.)Z"
$paymentLink->paidAt; // "2024-02-24T12:15:16.0Z" (optional)
$paymentLink->expiresAt; // "2026-01-01T00:00:00.0Z" (optional)
$paymentLink->webhookUrl; // "https://example.com/webhook" (optional)
```
## Additional Notes
- Payment links are shareable URLs to accept payments
- They never expire unless you specify an `expiresAt` date
- The webhook will be called when the payment status changes
- Payment links can be shared via email, chat, or QR code

View File

@ -0,0 +1,60 @@
# Create a Capturable Payment
How to create a payment that can be captured manually with the Mollie API.
## The Code
```php
use Mollie\Api\Http\Data\Money;
use Mollie\Api\Http\Requests\CreatePaymentRequest;
use Mollie\Api\Types\PaymentMethod;
try {
// Generate a unique order ID
$orderId = time();
// Create the payment
$payment = $mollie->send(
new CreatePaymentRequest(
description: "Order #{$orderId}",
amount: new Money(currency: 'EUR', value: '10.00'),
redirectUrl: "https://example.com/return.php?order_id={$orderId}",
cancelUrl: "https://example.com/cancel.php",
webhookUrl: "https://example.com/webhook.php",
metadata: ['order_id' => $orderId],
method: PaymentMethod::CREDITCARD,
captureMode: 'manual'
)
);
// Store the order in the database
database_write($orderId, $payment->status);
// Redirect to checkout
header('Location: ' . $payment->getCheckoutUrl(), true, 303);
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo 'API call failed: ' . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$payment->id; // "tr_7UhSN1zuXS"
$payment->status; // "open"
$payment->amount->currency; // "EUR"
$payment->amount->value; // "10.00"
$payment->description; // "Order #1234"
$payment->metadata->order_id; // "1234"
$payment->method; // "creditcard"
$payment->captureMode; // "manual"
$payment->getCheckoutUrl(); // "https://www.mollie.com/checkout/select-method/7UhSN1zuXS"
```
## Additional Notes
- Manual capturing is currently only supported for credit card payments, Billie, Riverty, and Klarna
- For Klarna payments, submitting more data like order lines and addresses is required
- After the payment is authorized, you'll need to capture it manually to actually charge the customer
- The payment will remain in the 'authorized' status until you either capture or cancel it
- Make sure to implement the webhook handler to process payment status updates

View File

@ -0,0 +1,49 @@
# Create a Payment Capture
How to capture a payment that was created with manual capture using the Mollie API.
## The Code
```php
use Mollie\Api\Http\Data\Money;
use Mollie\Api\Resources\Capture;
use Mollie\Api\Http\Requests\CreatePaymentCaptureRequest;
try {
// Create a capture for the payment
/** @var Capture */
$capture = $mollie->send(
new CreatePaymentCaptureRequest(
paymentId: 'tr_WDqYK6vllg',
description: 'Order #12345',
amount: new Money(currency: 'EUR', value: '5.00')
)
);
echo "New capture created: {$capture->id}\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$capture->id; // "cpt_4qqhO89gsT"
$capture->paymentId; // "tr_WDqYK6vllg"
$capture->amount->currency; // "EUR"
$capture->amount->value; // "5.00"
$capture->description; // "Order #12345"
$capture->status; // "pending", "succeeded", "failed"
$capture->createdAt; // "2024-02-24T12:13:14+00:00"
```
## Additional Notes
- The payment must have been created with `captureMode: 'manual'`
- The capture amount can be equal to or lower than the payment amount
- You can create multiple partial captures for a single payment
- The capture will be processed asynchronously
- The payment status will change to `paid` once the capture is successful
- Make sure to handle the webhook to process capture status updates
- Captures are only available for certain payment methods (e.g., credit cards)

View File

@ -0,0 +1,56 @@
# Create an iDEAL Payment
How to prepare a new iDEAL payment with the Mollie API.
## The Code
```php
use Mollie\Api\Http\Data\Money;
use Mollie\Api\Http\Requests\CreatePaymentRequest;
use Mollie\Api\Types\PaymentMethod;
try {
// Generate a unique order ID
$orderId = time();
// Create the payment
$payment = $mollie->send(
new CreatePaymentRequest(
description: "Order #{$orderId}",
amount: new Money(currency: 'EUR', value: '27.50'),
redirectUrl: "https://example.com/return.php?order_id={$orderId}",
cancelUrl: "https://example.com/cancel.php",
webhookUrl: "https://example.com/webhook.php",
metadata: ['order_id' => $orderId],
method: PaymentMethod::IDEAL
)
);
// Store the order in the database
database_write($orderId, $payment->status);
// Redirect to checkout
header('Location: ' . $payment->getCheckoutUrl(), true, 303);
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo 'API call failed: ' . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$payment->id; // "tr_7UhSN1zuXS"
$payment->status; // "open"
$payment->amount->currency; // "EUR"
$payment->amount->value; // "27.50"
$payment->description; // "Order #1234"
$payment->metadata->order_id; // "1234"
$payment->method; // "ideal"
$payment->getCheckoutUrl(); // "https://www.mollie.com/checkout/select-method/7UhSN1zuXS"
```
## Additional Notes
- The iDEAL payment method is only available for payments in EUR
- The bank selection is now handled by iDEAL
- The webhook will be called when the payment status changes, so make sure to implement the webhook handler to process status updates

View File

@ -0,0 +1,64 @@
# Create a Payment with OAuth
How to create a payment using OAuth authentication with the Mollie API.
## The Code
```php
use Mollie\Api\Http\Data\Money;
use Mollie\Api\Http\Requests\CreatePaymentRequest;
use Mollie\Api\Http\Requests\GetPaginatedProfilesRequest;
try {
// Initialize the Mollie client with your OAuth access token
$mollie = new \Mollie\Api\MollieApiClient();
$mollie->setAccessToken('access_xxx');
// Get the first available profile since OAuth tokens don't belong to a specific profile
$profiles = $mollie->send(new GetPaginatedProfilesRequest);
$profile = $profiles[0]; // Select the correct profile for this merchant
// Generate a unique order ID
$orderId = time();
// Create the payment
$payment = $mollie->send(
new CreatePaymentRequest(
profileId: $profile->id, // Required when using OAuth
description: "Order #{$orderId}",
amount: new Money(currency: 'EUR', value: '10.00'),
redirectUrl: 'https://example.com/return.php?order_id=' . $orderId,
cancelUrl: 'https://example.com/cancel.php',
webhookUrl: 'https://example.com/webhook.php',
metadata: ['order_id' => $orderId]
)
);
// Redirect the customer to complete the payment
header('Location: ' . $payment->getCheckoutUrl(), true, 303);
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$payment->id; // "tr_7UhSN1zuXS"
$payment->status; // "open"
$payment->amount->currency; // "EUR"
$payment->amount->value; // "10.00"
$payment->description; // "Order #1234"
$payment->metadata; // Object containing order_id
$payment->profileId; // "pfl_v9hTwCvYqw"
$payment->createdAt; // "2024-02-24T12:13:14+00:00"
```
## Additional Notes
- OAuth access tokens don't belong to a specific profile, so you need to specify the `profileId` parameter
- Get the profile ID by listing the available profiles with `GetPaginatedProfilesRequest`
- OAuth tokens are required for certain features like routing payments
- Make sure to handle the webhook to process payment status updates
- Store your OAuth tokens securely and refresh them when needed

View File

@ -0,0 +1,50 @@
# Create a Payment
How to prepare a new payment with the Mollie API.
## The Code
```php
use Mollie\Api\Http\Data\Money;
use Mollie\Api\Http\Requests\CreatePaymentRequest;
try {
$payment = $mollie->send(
new CreatePaymentRequest(
description: "Order #{$orderId}",
amount: new Money(currency: 'EUR', value: '10.00'),
redirectUrl: 'https://example.org/return',
cancelUrl: 'https://example.org/cancel',
webhookUrl: 'https://example.org/webhook',
metadata: ['order_id' => $orderId]
)
);
// Get the checkout URL to redirect the customer to
$checkoutUrl = $payment->getCheckoutUrl();
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$payment->id; // "tr_7UhSN1zuXS"
$payment->status; // "open"
$payment->amount->currency; // "EUR"
$payment->amount->value; // "10.00"
$payment->description; // "Order #1234"
$payment->metadata->order_id; // "1234"
$payment->getCheckoutUrl(); // "https://www.mollie.com/checkout/select-method/7UhSN1zuXS"
```
## Additional Notes
- Always use strings for the amount value to ensure correct decimal precision
- The payment `status` will be `open` when initially created
- Store the payment `id` in your database for reference
- Use `webhookUrl` to get notified of payment status changes
- The `redirectUrl` is where your customer will be redirected after the payment
- You can store any custom data in the `metadata` object
- The checkout URL from `getCheckoutUrl()` is where you should redirect your customer to complete the payment

View File

@ -0,0 +1,94 @@
# Create a Routed Payment
How to create a payment with routing rules using the Mollie API. Routed payments allow you to split payments between connected accounts.
## The Code
```php
use Mollie\Api\Http\Data\Money;
use Mollie\Api\Http\Data\Route;
use Mollie\Api\Http\Requests\CreatePaymentRequest;
try {
// Initialize the Mollie client with your OAuth access token
$mollie = new \Mollie\Api\MollieApiClient();
$mollie->setAccessToken('access_xxx');
// Generate a unique order ID
$orderId = time();
// Create the payment with routing rules
$payment = $mollie->send(
new CreatePaymentRequest(
profileId: 'pfl_v9hTwCvYqw',
description: "Order #{$orderId}",
amount: new Money(currency: 'EUR', value: '10.00'),
redirectUrl: 'https://example.com/return.php?order_id=' . $orderId,
cancelUrl: 'https://example.com/cancel.php',
webhookUrl: 'https://example.com/webhook.php',
routing: [
new Route(
amount: new Money(currency: 'EUR', value: '7.50'),
destination: [
'type' => 'organization',
'organizationId' => 'org_23456'
]
)
]
)
);
// Redirect the customer to complete the payment
header('Location: ' . $payment->getCheckoutUrl(), true, 303);
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
### With a Future Release Date
You can also specify when the routed funds should become available on the connected account's balance:
```php
$payment = $mollie->send(
new CreatePaymentRequest(
profileId: 'pfl_v9hTwCvYqw',
description: "Order #{$orderId}",
amount: new Money(currency: 'EUR', value: '10.00'),
redirectUrl: 'https://example.com/return.php?order_id=' . $orderId,
cancelUrl: 'https://example.com/cancel.php',
webhookUrl: 'https://example.com/webhook.php',
routing: [
new Route(
amount: new Money(currency: 'EUR', value: '7.50'),
destination: [
'type' => 'organization',
'organizationId' => 'org_23456'
],
releaseDate: '2025-01-01'
)
]
)
);
```
## The Response
```php
$payment->id; // "tr_7UhSN1zuXS"
$payment->status; // "open"
$payment->amount->currency; // "EUR"
$payment->amount->value; // "10.00"
$payment->description; // "Order #1234"
$payment->routing; // Array containing routing rules
$payment->createdAt; // "2024-02-24T12:13:14+00:00"
```
## Additional Notes
- Split payments (routing) must be enabled on your account first. Contact Mollie support to enable this feature
- You need an OAuth access token to create routed payments
- The sum of routed amounts cannot exceed the payment amount
- The release date must be in the future and in the format 'YYYY-MM-DD'
- Routing rules are only available for certain payment methods
- Make sure to handle the webhook to process payment status updates

View File

@ -0,0 +1,98 @@
# Handle Payment Webhooks
How to handle payment status updates via webhooks from the Mollie API.
## The Code
```php
use Mollie\Api\Http\Requests\GetPaymentRequest;
try {
// Retrieve the payment's current state
$payment = $mollie->send(
new GetPaymentRequest(
id: $_POST['id']
)
);
$orderId = $payment->metadata->order_id;
// First handle status changes
if ($payment->status !== $previousPaymentStatus) {
// Update your order administration with the new status
updateOrder($orderId, $payment->status);
// Handle the status change
if ($payment->isPaid()) {
// The payment is paid
// Start the process of delivering the product to the customer
startDeliveryProcess($orderId);
} elseif ($payment->isFailed()) {
// The payment has failed
// Notify the customer
notifyCustomerOfFailure($orderId);
} elseif ($payment->isExpired()) {
// The payment is expired
// Notify the customer
notifyCustomerOfExpiration($orderId);
} elseif ($payment->isCanceled()) {
// The payment is canceled
// Notify the customer
notifyCustomerOfCancellation($orderId);
}
}
// Then handle refunds and chargebacks (these don't change the payment status)
if ($payment->hasRefunds()) {
// The payment has possibly been (partially) refunded
// Note: the payment status remains "paid"
processPotentialRefund($orderId);
}
if ($payment->hasChargebacks()) {
// The payment has possibly been (partially) charged back
// Note: the payment status remains "paid"
processPotentialChargeback($orderId);
}
// Always return 200 OK to Mollie
http_response_code(200);
} catch (\Mollie\Api\Exceptions\ApiException $e) {
// Handle the error
logError($e->getMessage());
http_response_code(500);
}
```
## The Response
```php
$payment->id; // "tr_7UhSN1zuXS"
$payment->status; // "paid"
$payment->amount->currency; // "EUR"
$payment->amount->value; // "10.00"
$payment->description; // "Order #1234"
$payment->metadata; // Object containing order_id
$payment->createdAt; // "2024-02-24T12:13:14+00:00"
$payment->paidAt; // "2024-02-24T12:15:00+00:00"
```
## Additional Notes
- Handle webhooks in the correct order:
1. First check for payment status changes
2. Then handle refunds and chargebacks (these don't change the payment status).
- When completing a payment, the webhook may be called _after_ the customer has already been redirected to the payment's `redirectUrl`.
- A payment can have multiple partial refunds and multiple partial chargebacks.
- Always respond with a 200 OK status code, even when the payment status is not successful
- Process the webhook quickly (within 2 seconds) to avoid timeouts
- Use the payment status helper methods (`$payment->isPaid()`, `$payment->isFailed()`, etc.) for reliable status checks
- Store the payment status in your database to track the order status
- The webhook may be called multiple times for the same payment as Mollie will retry the webhook call.
- Webhooks are sent asynchronously, so there might be a delay
- Test your webhook implementation thoroughly with different payment scenarios, including:
- Status changes (paid, failed, expired, etc.)
- Refunds (full and partial)
- Chargebacks

View File

@ -0,0 +1,49 @@
# List Payment Methods
How to retrieve all available payment methods with the Mollie API.
## The Code
```php
try {
$methods = $mollie->send(
new GetAllMethodsRequest(
includeIssuers: false,
includePricing: false,
locale: 'nl_NL',
amount: new Money(currency: 'EUR', value: '100.00')
)
);
foreach ($methods as $method) {
echo '<div style="line-height:40px; vertical-align:top">';
echo '<img src="' . htmlspecialchars($method->image->size1x) . '" srcset="' . htmlspecialchars($method->image->size2x) . ' 2x"> ';
echo htmlspecialchars($method->description) . ' (' . htmlspecialchars($method->id) . ')';
echo '</div>';
}
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$method->id; // "ideal"
$method->description; // "iDEAL"
$method->image->size1x; // "https://www.mollie.com/external/icons/payment-methods/ideal.png"
$method->image->size2x; // "https://www.mollie.com/external/icons/payment-methods/ideal%402x.png"
$method->minimumAmount->value; // "0.01"
$method->minimumAmount->currency; // "EUR"
$method->maximumAmount->value; // "50000.00"
$method->maximumAmount->currency; // "EUR"
```
## Additional Notes
- Use `sequenceType` to filter methods available for recurring payments
- The `locale` parameter affects translations of method names and descriptions
- The `amount` parameter filters methods available for that specific amount
- The `billingCountry` parameter filters methods available in that country
- Each method includes image URLs for regular (1x) and retina (2x) displays
- Methods may have minimum and maximum amount constraints

View File

@ -0,0 +1,69 @@
# List Payments
How to retrieve a list of payments from the Mollie API.
## The Code
```php
use Mollie\Api\Http\Requests\GetPaginatedPaymentsRequest;
try {
// Get all payments for this API key ordered by newest
$payments = $mollie->send(new GetPaginatedPaymentsRequest);
// Display the payments
foreach ($payments as $payment) {
echo "Payment {$payment->id}:\n";
echo "- Description: {$payment->description}\n";
echo "- Amount: {$payment->amount->currency} {$payment->amount->value}\n";
echo "- Status: {$payment->status}\n";
if ($payment->hasRefunds()) {
echo "- Has been (partially) refunded\n";
}
if ($payment->hasChargebacks()) {
echo "- Has been charged back\n";
}
if ($payment->canBeRefunded() && $payment->amountRemaining->currency === 'EUR' && floatval($payment->amountRemaining->value) >= 2.00) {
echo "- Can be refunded\n";
}
echo "\n";
}
// Get the next page of payments if available
$nextPayments = $payments->next();
if (!empty($nextPayments)) {
foreach ($nextPayments as $payment) {
// Process next page of payments...
}
}
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$payment->id; // "tr_7UhSN1zuXS"
$payment->status; // "paid"
$payment->amount->currency; // "EUR"
$payment->amount->value; // "10.00"
$payment->description; // "Order #1234"
$payment->metadata; // Object containing any custom metadata
$payment->createdAt; // "2024-02-24T12:13:14+00:00"
$payment->paidAt; // "2024-02-24T12:15:00+00:00" (or null)
$payment->method; // "ideal" (or null)
```
## Additional Notes
- Payments are returned in descending order by creation date (newest first)
- The list is paginated, use the `next()` method to retrieve the next page
- You can check payment status using helper methods like `isPaid()`, `isFailed()`, etc.
- Use `hasRefunds()` and `hasChargebacks()` to check if a payment has been refunded or charged back
- Use `canBeRefunded()` to check if a payment can still be refunded

View File

@ -0,0 +1,66 @@
# Refund a Payment
How to refund a payment using the Mollie API.
## The Code
```php
use Mollie\Api\Http\Data\Money;
use Mollie\Api\Http\Requests\GetPaymentRequest;
use Mollie\Api\Http\Requests\CreatePaymentRefundRequest;
try {
// Retrieve the payment you want to refund
$payment = $mollie->send(
new GetPaymentRequest(
id: 'tr_WDqYK6vllg'
)
);
// Check if the payment can be refunded
if ($payment->canBeRefunded() && $payment->amountRemaining->currency === 'EUR' && floatval($payment->amountRemaining->value) >= 2.00) {
// Refund € 2,00 of the payment
$refund = $mollie->send(
new CreatePaymentRefundRequest(
paymentId: $payment->id,
description: 'Order cancelled by customer',
amount: new Money(currency: 'EUR', value: '2.00')
)
);
echo "{$refund->amount->currency} {$refund->amount->value} of payment {$payment->id} refunded.\n";
} else {
echo "Payment {$payment->id} cannot be refunded.\n";
}
// List all refunds for this payment
foreach ($payment->refunds() as $refund) {
echo "Refund {$refund->id}:\n";
echo "- Description: {$refund->description}\n";
echo "- Amount: {$refund->amount->currency} {$refund->amount->value}\n";
echo "- Status: {$refund->status}\n\n";
}
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$refund->id; // "re_4qqhO89gsT"
$refund->amount->currency; // "EUR"
$refund->amount->value; // "2.00"
$refund->status; // "pending", "processing", "refunded", "failed"
$refund->description; // "Order cancelled by customer"
$refund->createdAt; // "2024-02-24T12:13:14+00:00"
$refund->paymentId; // "tr_WDqYK6vllg"
```
## Additional Notes
- Not all payments can be refunded. Use `canBeRefunded()` to check if a payment can be refunded
- You can do partial refunds by specifying a lower amount than the payment amount
- Refunds are not instant. Check the refund status to see if it was successful
- The payment must be in the `paid` status to be refundable
- Some payment methods may have additional requirements or limitations for refunds

View File

@ -0,0 +1,74 @@
# Retrieve Payment Captures
How to retrieve and list captures for a payment using the Mollie API.
## Get a Single Capture
```php
use Mollie\Api\Resources\Capture;
use Mollie\Api\Http\Requests\GetPaymentCaptureRequest;
try {
// Retrieve a specific capture
/** @var Capture $capture */
$capture = $mollie->send(
new GetPaymentCaptureRequest(
paymentId: 'tr_WDqYK6vllg',
captureId: 'cpt_4qqhO89gsT'
)
);
echo "Captured {$capture->amount->currency} {$capture->amount->value}\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## List All Captures
```php
use Mollie\Api\Resources\Capture;
use Mollie\Api\Resources\LazyCollection;
use Mollie\Api\Http\Requests\GetPaginatedPaymentCapturesRequest;
try {
// List all captures for a payment
/** @var LazyCollection $captures */
$captures = $mollie->send(
(new GetPaginatedPaymentCapturesRequest(
paymentId: 'tr_WDqYK6vllg'
))->useIterator()
);
/** @var Capture $capture */
foreach ($captures as $capture) {
echo "Capture {$capture->id}:\n";
echo "- Amount: {$capture->amount->currency} {$capture->amount->value}\n";
echo "- Status: {$capture->status}\n";
echo "- Created: {$capture->createdAt}\n\n";
}
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$capture->id; // "cpt_4qqhO89gsT"
$capture->paymentId; // "tr_WDqYK6vllg"
$capture->amount->currency; // "EUR"
$capture->amount->value; // "5.00"
$capture->description; // "Order #12345"
$capture->status; // "pending", "succeeded", "failed"
$capture->createdAt; // "2024-02-24T12:13:14+00:00"
```
## Additional Notes
- You can only retrieve captures for payments created with `captureMode: 'manual'`
- A payment can have multiple captures if partial captures were used
- The captures list is paginated, use `next()` to get the next page
- The capture status indicates whether the capture was successful
- The payment status will change to `paid` once all captures are successful
- Make sure to handle the webhook to process capture status updates

View File

@ -0,0 +1,65 @@
# Update a Payment
How to update an existing payment using the Mollie API.
## The Code
```php
use Mollie\Api\Http\Requests\GetPaymentRequest;
use Mollie\Api\Http\Requests\UpdatePaymentRequest;
try {
// First retrieve the payment you want to update
$payment = $mollie->send(
new GetPaymentRequest(
id: 'tr_7UhSN1zuXS'
)
);
// Update specific payment fields
$newOrderId = 98765;
$payment = $mollie->send(
new UpdatePaymentRequest(
id: $payment->id,
description: "Order #{$newOrderId}",
redirectUrl: 'https://example.com/return.php?order_id=' . $newOrderId,
metadata: ['order_id' => $newOrderId]
// Fields we don't specify will keep their current values:
// - webhookUrl
// - cancelUrl
// - etc.
)
);
// Redirect the customer to complete the payment
header('Location: ' . $payment->getCheckoutUrl(), true, 303);
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$payment->id; // "tr_7UhSN1zuXS"
$payment->status; // "open"
$payment->description; // "Order #98765"
$payment->redirectUrl; // "https://example.com/return.php?order_id=98765"
$payment->webhookUrl; // "https://example.com/webhook.php"
$payment->metadata; // Object containing order_id
$payment->createdAt; // "2024-02-24T12:13:14+00:00"
$payment->updatedAt; // "2024-02-24T12:15:00+00:00"
```
## Additional Notes
- Only certain fields can be updated, including:
- description
- redirectUrl
- cancelUrl
- webhookUrl
- metadata
- You cannot update the amount or currency of a payment
- The payment must be in a state that allows updates (e.g., you cannot update a completed payment)
- Make sure to handle the webhook to process payment status updates
- The updatedAt field will be set to the time of the last update

View File

@ -0,0 +1,112 @@
# Manage Profiles
How to create, update, list, and delete profiles using the Mollie API.
## Create a Profile
```php
use Mollie\Api\Http\Requests\CreateProfileRequest;
try {
// Create a new profile
$profile = $mollie->send(
new CreateProfileRequest([
'name' => 'My Website Name',
'website' => 'https://www.mywebsite.com',
'email' => 'info@mywebsite.com',
'phone' => '+31208202070',
'businessCategory' => 'MARKETPLACES',
'mode' => 'live'
])
);
echo "Profile created: {$profile->name}\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## List Profiles
```php
use Mollie\Api\Http\Requests\GetPaginatedProfilesRequest;
try {
// List all profiles
$response = $mollie->send(new GetPaginatedProfilesRequest);
foreach ($response as $profile) {
echo "Profile {$profile->id}:\n";
echo "- Name: {$profile->name}\n";
echo "- Website: {$profile->website}\n";
echo "- Mode: {$profile->mode}\n";
echo "- Status: {$profile->status}\n\n";
}
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## Update a Profile
```php
use Mollie\Api\Http\Requests\UpdateProfileRequest;
try {
// Update an existing profile
$profile = $mollie->send(
new UpdateProfileRequest(
id: 'pfl_v9hTwCvYqw',
name: 'Updated Website Name',
website: 'https://www.updated-website.com',
email: 'info@updated-website.com',
phone: '+31208202071',
businessCategory: 'MARKETPLACES'
)
);
echo "Profile updated: {$profile->name}\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## Delete a Profile
```php
use Mollie\Api\Http\Requests\DeleteProfileRequest;
try {
// Delete a profile
$mollie->send(
new DeleteProfileRequest(
profileId: 'pfl_v9hTwCvYqw'
)
);
echo "Profile deleted\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$profile->id; // "pfl_v9hTwCvYqw"
$profile->mode; // "live" or "test"
$profile->name; // "My Website Name"
$profile->website; // "https://www.mywebsite.com"
$profile->email; // "info@mywebsite.com"
$profile->phone; // "+31208202070"
$profile->businessCategory; // "MARKETPLACES"
$profile->status; // "verified", "unverified"
$profile->review; // Object containing review status (optional)
$profile->createdAt; // "2024-02-24T12:13:14+00:00"
```
## Additional Notes
- OAuth access token is required to manage profiles
- A profile represents your business or website
- Business categories define the type of products/services you offer

View File

@ -0,0 +1,47 @@
# Manage Sales Invoices
How to list and manage sales invoices using the Mollie API.
## List Sales Invoices
```php
use Mollie\Api\Http\Requests\GetPaginatedSalesInvoicesRequest;
try {
// List all sales invoices
$response = $mollie->send(new GetPaginatedSalesInvoicesRequest);
foreach ($response as $invoice) {
echo "Invoice {$invoice->reference}:\n";
echo "- Status: {$invoice->status}\n";
echo "- Issued: {$invoice->issuedAt}\n";
echo "- Amount: {$invoice->amount->currency} {$invoice->amount->value}\n";
echo "- PDF: {$invoice->_links->pdf->href}\n\n";
}
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$invoice->id; // "si_abc123"
$invoice->reference; // "2024.0001"
$invoice->status; // "paid", "open"
$invoice->issuedAt; // "2024-02-24"
$invoice->paidAt; // "2024-02-24" (optional)
$invoice->dueAt; // "2024-03-24"
$invoice->amount; // Object containing amount and currency
$invoice->netAmount; // Object containing amount and currency (excluding VAT)
$invoice->vatAmount; // Object containing amount and currency
$invoice->lines; // Array of invoice lines
$invoice->_links; // Object containing links (e.g., PDF download)
```
## Additional Notes
- OAuth access token is required to access sales invoices
- Sales invoices are generated for your Mollie account
- Each invoice line represents a different type of fee
- The PDF invoice is available through the `_links.pdf.href` URL

View File

@ -0,0 +1,55 @@
# Manage Sessions
How to create and manage sessions using the Mollie API.
## Create a Session
```php
use Mollie\Api\Http\Requests\CreateSessionRequest;
try {
// Create a new session
$session = $mollie->send(
new CreateSessionRequest([
'paymentData' => [
'amount' => [
'value' => '10.00',
'currency' => 'EUR'
],
'description' => 'Order #12345'
],
'method' => 'paypal',
'methodDetails' => [
'checkoutFlow' => 'express'
],
'returnUrl' => 'https://example.com/shipping',
'cancelUrl' => 'https://example.com/cancel'
])
);
// Redirect to the session URL
header('Location: ' . $session->getRedirectUrl(), true, 303);
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$session->id; // "ses_abc123"
$session->status; // "created", "completed", "canceled"
$session->method; // "paypal"
$session->methodDetails; // Object containing method-specific details
$session->paymentData; // Object containing payment details
$session->createdAt; // "2024-02-24T12:13:14+00:00"
$session->expiresAt; // "2024-02-24T13:13:14+00:00"
$session->_links; // Object containing links (e.g., redirect URL)
```
## Additional Notes
- Sessions are used for payment methods that require additional steps
- Currently supports PayPal Express Checkout
- The session expires after 1 hour
- Use the redirect URL to send customers to complete their payment

View File

@ -0,0 +1,66 @@
# List Settlements
How to list settlements using the Mollie API.
## List Settlements
```php
use Mollie\Api\Http\Requests\GetPaginatedSettlementsRequest;
try {
// List all settlements
$response = $mollie->send(new GetPaginatedSettlementsRequest);
foreach ($response as $settlement) {
echo "Settlement {$settlement->reference}:\n";
echo "- Created: {$settlement->createdAt}\n";
echo "- Status: {$settlement->status}\n";
echo "- Amount: {$settlement->amount->currency} {$settlement->amount->value}\n\n";
// Show settlement periods
foreach ($settlement->periods as $year => $months) {
foreach ($months as $month => $data) {
echo "Period {$year}-{$month}:\n";
// Show revenue
foreach ($data->revenue as $revenue) {
echo "Revenue: {$revenue->description}\n";
echo "- Count: {$revenue->count}\n";
echo "- Net: {$revenue->amountNet->currency} {$revenue->amountNet->value}\n";
echo "- VAT: {$revenue->amountVat->currency} {$revenue->amountVat->value}\n";
echo "- Gross: {$revenue->amountGross->currency} {$revenue->amountGross->value}\n\n";
}
// Show costs
foreach ($data->costs as $cost) {
echo "Cost: {$cost->description}\n";
echo "- Count: {$cost->count}\n";
echo "- Net: {$cost->amountNet->currency} {$cost->amountNet->value}\n";
echo "- VAT: {$cost->amountVat->currency} {$cost->amountVat->value}\n";
echo "- Gross: {$cost->amountGross->currency} {$cost->amountGross->value}\n\n";
}
}
}
}
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$settlement->id; // "stl_abc123"
$settlement->reference; // "1234567.1804.03"
$settlement->createdAt; // "2024-02-24T12:13:14+00:00"
$settlement->settledAt; // "2024-02-24T12:13:14+00:00"
$settlement->status; // "open", "pending", "paidout", "failed"
$settlement->amount; // Object containing amount and currency
$settlement->periods; // Object containing settlement periods
```
## Additional Notes
- OAuth access token is required to access settlements
- Settlements contain revenue and costs grouped by period
- Each period shows transaction fees, refunds, and chargebacks

View File

@ -0,0 +1,112 @@
# Manage Subscriptions
How to create and manage subscriptions using the Mollie API.
## Create a Subscription
```php
use Mollie\Api\Http\Requests\CreateSubscriptionRequest;
try {
// Create a subscription for a customer
$subscription = $mollie->send(
new CreateSubscriptionRequest(
customerId: 'cst_8wmqcHMN4U',
parameters: [
'amount' => [
'value' => '10.00',
'currency' => 'EUR'
],
'interval' => '1 month',
'description' => 'Monthly subscription',
'webhookUrl' => 'https://example.com/webhook',
'metadata' => [
'subscription_id' => time()
]
]
)
);
echo "Subscription status: {$subscription->status}\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## List Subscriptions
```php
use Mollie\Api\Http\Requests\GetPaginatedSubscriptionsRequest;
try {
// List all subscriptions for a customer
$response = $mollie->send(
new GetPaginatedSubscriptionsRequest(
customerId: 'cst_8wmqcHMN4U'
)
);
foreach ($response as $subscription) {
echo "Subscription {$subscription->id}:\n";
echo "- Status: {$subscription->status}\n";
echo "- Amount: {$subscription->amount->currency} {$subscription->amount->value}\n";
echo "- Times: {$subscription->times}\n";
echo "- Interval: {$subscription->interval}\n";
echo "- Next payment: {$subscription->nextPaymentDate}\n\n";
}
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## Cancel a Subscription
```php
use Mollie\Api\Http\Requests\CancelSubscriptionRequest;
try {
// Cancel a subscription
$mollie->send(
new CancelSubscriptionRequest(
customerId: 'cst_8wmqcHMN4U',
subscriptionId: 'sub_rVKGtNd6s3'
)
);
echo "Subscription canceled\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$subscription->id; // "sub_rVKGtNd6s3"
$subscription->customerId; // "cst_8wmqcHMN4U"
$subscription->mode; // "live" or "test"
$subscription->createdAt; // "2024-02-24T12:13:14+00:00"
$subscription->status; // "active", "pending", "canceled", "suspended", "completed"
$subscription->amount; // Object containing amount and currency
$subscription->times; // 12 (optional)
$subscription->timesRemaining; // 4 (optional)
$subscription->interval; // "1 month"
$subscription->startDate; // "2024-02-24"
$subscription->nextPaymentDate; // "2024-03-24"
$subscription->description; // "Monthly subscription"
$subscription->method; // null or payment method
$subscription->webhookUrl; // "https://example.com/webhook"
$subscription->metadata; // Object containing custom metadata
```
## Additional Notes
- The customer must have a valid mandate for recurring payments
- Subscriptions can be created with various intervals (e.g., "1 month", "3 months")
- The webhook is called for every subscription payment
- Subscription status can be:
- `pending`: Waiting for a valid mandate
- `active`: The subscription is active
- `canceled`: The subscription is canceled
- `suspended`: The subscription is suspended
- `completed`: The subscription has ended

View File

@ -0,0 +1,102 @@
# Create a Webhook
How to create webhooks to receive notifications about events using the Mollie API.
## The Code
```php
use Mollie\Api\Http\Requests\CreateWebhookRequest;
use Mollie\Api\Types\WebhookEventType;
try {
// Create a webhook using the direct request (new style)
$webhook = $mollie->send(
new CreateWebhookRequest(
url: 'https://example.com/webhook',
name: 'Payment notifications',
eventTypes: WebhookEventType::PAYMENT_LINK_PAID
)
);
echo "Webhook created: {$webhook->id}\n";
echo "URL: {$webhook->url}\n";
echo "Name: {$webhook->name}\n";
echo "Event Types: {$webhook->eventTypes}\n";
echo "Status: {$webhook->status}\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## Using Endpoint Collections (Legacy Style)
```php
use Mollie\Api\Types\WebhookEventType;
try {
// Create a webhook using endpoint collections
$webhook = $mollie->webhooks->create([
'url' => 'https://example.com/webhook',
'name' => 'Payment notifications',
'eventTypes' => WebhookEventType::PAYMENT_LINK_PAID
]);
echo "Webhook created: {$webhook->id}\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$webhook->resource; // "webhook"
$webhook->id; // "wh_4KgGJJSZpH"
$webhook->url; // "https://example.com/webhook"
$webhook->profileId; // "pfl_v9hTwCvYqw"
$webhook->createdAt; // "2023-12-25T10:30:54+00:00"
$webhook->name; // "Payment notifications"
$webhook->eventTypes; // "payment-link.paid"
$webhook->status; // "enabled"
$webhook->_links; // Object containing webhook links
```
## Available Event Types
This endpoint is under active development. Keep an eye on the Mollie documentation to see what events are being supported.
Currently, only one event type is supported:
```php
use Mollie\Api\Types\WebhookEventType;
// Currently supported event type
WebhookEventType::PAYMENT_LINK_PAID; // "payment-link.paid"
// Get all available event types
$allEventTypes = WebhookEventType::getAll();
// Returns: ["payment-link.paid"]
```
## Additional Notes
- **Webhook URL Requirements**:
- There are no security requirements for test mode. However, in order to receive live mode events, the URL needs to be secured with an up-to-date HTTPS connection.
- Must respond with HTTP 200 status code
- Cannot be `localhost` URLs (use ngrok for local testing)
- **Event Types**:
- Check out [the Mollie documentation](https://docs.mollie.com/reference/webhooks-new#event-types) to see what events are supported.
- Additional event types will be added in future updates
- Subscribe only to events you need to reduce noise
- **Webhook URL Best Practices**:
- Use HTTPS for security
- Implement proper authentication/authorization
- Handle webhooks idempotently (same webhook may be sent multiple times)
- Log webhook payloads for debugging
- Return 200 OK as quickly as possible
- **Testing**:
- Use ngrok or similar tools for local development
- Test webhook handling with the webhook test endpoint
- Verify signature validation works correctly

View File

@ -0,0 +1,205 @@
# Manage Webhooks
How to retrieve, update, delete, test, and list webhooks using the Mollie API.
## Get a Webhook
```php
use Mollie\Api\Http\Requests\GetWebhookRequest;
try {
// Get a specific webhook using direct request
$webhook = $mollie->send(
new GetWebhookRequest(
id: 'wh_4KgGJJSZpH'
)
);
echo "Webhook {$webhook->id}:\n";
echo "- URL: {$webhook->url}\n";
echo "- Name: {$webhook->name}\n";
echo "- Event Types: {$webhook->eventTypes}\n";
echo "- Status: {$webhook->status}\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## Update a Webhook
```php
use Mollie\Api\Http\Requests\UpdateWebhookRequest;
use Mollie\Api\Webhooks\WebhookEventType;
try {
// Update webhook properties
$webhook = $mollie->send(
new UpdateWebhookRequest(
id: 'wh_4KgGJJSZpH',
url: 'https://updated-example.com/webhook',
name: 'Updated webhook name',
eventTypes: WebhookEventType::PAYMENT_LINK_PAID
)
);
echo "Webhook updated successfully\n";
echo "New URL: {$webhook->url}\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## Delete a Webhook
```php
use Mollie\Api\Http\Requests\DeleteWebhookRequest;
try {
// Delete a webhook
$mollie->send(
new DeleteWebhookRequest(
id: 'wh_4KgGJJSZpH'
)
);
echo "Webhook deleted successfully\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## Test a Webhook
```php
use Mollie\Api\Http\Requests\TestWebhookRequest;
try {
// Test webhook delivery
$result = $mollie->send(
new TestWebhookRequest(
id: 'wh_4KgGJJSZpH'
)
);
echo "Webhook test initiated\n";
echo "Test status: Success\n";
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "Webhook test failed: " . htmlspecialchars($e->getMessage());
}
```
## List All Webhooks
```php
use Mollie\Api\Http\Requests\GetPaginatedWebhooksRequest;
try {
// List all webhooks with pagination
$webhooks = $mollie->send(
new GetPaginatedWebhooksRequest()
);
foreach ($webhooks as $webhook) {
echo "Webhook {$webhook->id}:\n";
echo "- URL: {$webhook->url}\n";
echo "- Name: {$webhook->name}\n";
echo "- Event Types: {$webhook->eventTypes}\n";
echo "- Status: {$webhook->status}\n";
echo "- Created: {$webhook->createdAt}\n\n";
}
// Handle pagination if needed
if ($webhooks->hasNext()) {
$nextPage = $webhooks->next();
// Process next page...
}
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## Using Endpoint Collections (Legacy Style)
```php
try {
// Get a webhook
$webhook = $mollie->webhooks->get('wh_4KgGJJSZpH');
// Update a webhook
$webhook = $mollie->webhooks->update('wh_4KgGJJSZpH', [
'url' => 'https://updated-example.com/webhook',
'name' => 'Updated name'
]);
// Delete a webhook
$mollie->webhooks->delete('wh_4KgGJJSZpH');
// Test a webhook
$mollie->webhooks->test('wh_4KgGJJSZpH');
// List webhooks
$webhooks = $mollie->webhooks->page();
// Use convenience methods on webhook resource
$webhook = $mollie->webhooks->get('wh_4KgGJJSZpH');
// Update using convenience method
$webhook->update([
'name' => 'New name'
]);
// Delete using convenience method
$webhook->delete();
// Test using convenience method
$webhook->test();
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$webhook->resource; // "webhook"
$webhook->id; // "wh_4KgGJJSZpH"
$webhook->url; // "https://example.com/webhook"
$webhook->profileId; // "pfl_v9hTwCvYqw"
$webhook->createdAt; // "2023-12-25T10:30:54+00:00"
$webhook->name; // "Payment notifications"
$webhook->eventTypes; // "payment-link.paid"
$webhook->status; // "enabled"
$webhook->_links; // Object containing webhook links
```
## Additional Notes
- **Webhook Updates**:
- All parameters are optional when updating
- Only specify fields you want to change
- **Webhook Testing**:
- Tests webhook delivery by sending a test payload
- Useful for verifying webhook URL accessibility
- Check your webhook endpoint logs to confirm receipt
- Test payloads may differ from actual event payloads
- **Webhook Deletion**:
- Permanently removes the webhook
- Cannot be undone
- Stop receiving notifications for deleted webhooks immediately
- **Listing Webhooks**:
- Returns all webhooks in your account
- Results are paginated
- Use `hasNext()` and `next()` for pagination
- **Convenience Methods**:
- Webhook resources include convenience methods (`update()`, `delete()`, `test()`)
- These methods operate on the current webhook instance
- Simplify common operations without needing to specify the webhook ID again
- **Error Handling**:
- Webhook operations may fail due to network issues
- Handle `ApiException` to catch and respond to errors appropriately
- Common errors include webhook not found (404) or validation errors (422)

View File

@ -0,0 +1,91 @@
# Webhook Signature Verification
This recipe shows you how to verify Mollie webhook signatures in your application using the `SignatureValidator` class.
## Basic Example
```php
use Mollie\Api\Webhooks\SignatureValidator;
use Mollie\Api\Exceptions\InvalidSignatureException;
$signingSecret = "foobar";
$request = \GuzzleHttp\Psr7\ServerRequest::fromGlobals();
$requestBody = (string)$request->getBody();
$signature = $request->getHeader("X-Mollie-Signature");
try {
$validator = new SignatureValidator($signingSecret);
$isValid = $validator->validatePayload($requestBody, $signature);
if ($isValid) {
// Process the verified webhook event
} else {
// Legacy webhook without signature
}
return new \GuzzleHttp\Psr7\Response(200);
} catch (InvalidSignatureException $e) {
return new \GuzzleHttp\Psr7\Response(400);
}
```
For more information about webhooks and signature verification, see the [Webhooks Guide](../webhooks.md).
## Advanced Usage
### Multiple Signing Secrets
During key rotation or migration periods, you can verify signatures against multiple secrets:
```php
use Mollie\Api\Webhooks\SignatureValidator;
use Mollie\Api\Exceptions\InvalidSignatureException;
$signingSecrets = [
"current_secret",
"previous_secret"
];
try {
$validator = new SignatureValidator($signingSecrets);
$isValid = $validator->validatePayload($requestBody, $signature);
if ($isValid) {
processWebhook($webhookData);
} else {
// Handle legacy webhook
processLegacyWebhook($webhookData);
}
} catch (InvalidSignatureException $e) {
http_response_code(400);
echo 'Invalid signature';
}
```
### PSR-7 Request Validation
If you're using PSR-7 compatible requests:
```php
use Mollie\Api\Webhooks\SignatureValidator;
use Psr\Http\Message\ServerRequestInterface;
$signingSecret = "your_webhook_signing_secret";
$validator = new SignatureValidator($signingSecret);
try {
$isValid = $validator->validateRequest($request);
if ($isValid) {
// Signature is valid
} else {
// Legacy webhook (no signature)
}
return new Response(200, [], 'OK');
} catch (InvalidSignatureException $e) {
return new Response(400, [], 'Invalid signature');
}
```

View File

@ -0,0 +1,162 @@
# Webhook Events
How to retrieve and manage webhook events using the Mollie API.
## Get a Webhook Event
```php
use Mollie\Api\Http\Requests\GetWebhookEventRequest;
try {
// Get a specific webhook event
$webhookEvent = $mollie->send(
new GetWebhookEventRequest(
id: 'whev_abc123'
)
);
echo "Webhook Event {$webhookEvent->id}:\n";
echo "- Resource: {$webhookEvent->resource}\n";
echo "- Event Type: {$webhookEvent->type}\n";
echo "- Entity ID: {$webhookEvent->entityId}\n";
echo "- Created: {$webhookEvent->createdAt}\n";
// Check if event has embedded entity data
if ($webhookEvent->hasEntity()) {
$entity = $webhookEvent->getEntity();
echo "- Entity Data Available: Yes\n";
echo "- Entity Type: " . get_class($entity) . "\n";
} else {
echo "- Entity Data Available: No\n";
}
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## Using Endpoint Collections (Legacy Style)
```php
try {
// Get a webhook event using endpoint collections
$webhookEvent = $mollie->webhookEvents->get('whev_abc123');
echo "Event Type: {$webhookEvent->type}\n";
echo "Entity ID: {$webhookEvent->entityId}\n";
// Access the embedded entity data
if ($webhookEvent->hasEntity()) {
$entity = $webhookEvent->getEntity();
echo "Entity data is available\n";
}
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "API call failed: " . htmlspecialchars($e->getMessage());
}
```
## The Response
```php
$webhookEvent->resource; // "event"
$webhookEvent->id; // "whev_abc123"
$webhookEvent->type; // "payment-link.paid"
$webhookEvent->entityId; // "tr_abc123" (the payment link ID)
$webhookEvent->createdAt; // "2023-12-25T10:30:54+00:00"
$webhookEvent->_embedded; // Object containing embedded entity data
$webhookEvent->_links; // Object containing relevant URLs
```
## Working with Embedded Entity Data
The webhook event contains the full payload of the triggered event in the `_embedded` property:
```php
// Check if entity data is available
if ($webhookEvent->hasEntity()) {
// Get the embedded entity
$entity = $webhookEvent->getEntity();
// For payment-link.paid events, this would be the payment link data
echo "Payment Link ID: {$entity->id}\n";
echo "Payment Link Status: {$entity->status}\n";
echo "Amount: {$entity->amount->value} {$entity->amount->currency}\n";
}
// Direct access to _embedded structure
$embedded = $webhookEvent->_embedded;
if (!empty($embedded->entity)) {
$entity = $embedded->entity;
echo "Entity ID: {$entity->id}\n";
}
```
## Helper Methods
The `WebhookEvent` resource includes convenient methods for working with entity data:
```php
// Check if entity data is available
if ($webhookEvent->hasEntity()) {
echo "Entity data is embedded in this event\n";
}
// Get the entity data
$entity = $webhookEvent->getEntity();
if ($entity) {
echo "Entity ID: {$entity->id}\n";
echo "Entity Status: {$entity->status}\n";
}
```
## Event Types and Their Payloads
Currently, only payment link events are supported:
### Payment Link Events
```php
// payment-link.paid
if ($webhookEvent->type === 'payment-link.paid') {
$paymentLink = $webhookEvent->getEntity();
echo "Payment Link: {$paymentLink->id}\n";
echo "Amount: {$paymentLink->amount->value} {$paymentLink->amount->currency}\n";
echo "Status: {$paymentLink->status}\n";
}
```
## Additional Notes
- **Event Structure**:
- All webhook events have the `resource` property set to "event"
- The `type` property indicates what kind of event occurred
- The `entityId` references the ID of the object that triggered the event
- The `_embedded.entity` contains the full object data at the time of the event
- **Entity Data**:
- The embedded entity contains the complete state of the object when the event occurred
- This is useful for getting the full context without making additional API calls
- Entity data may be null for some event types
- **Event Identification**:
- Use the `id` property to uniquely identify webhook events
- The `entityId` property tells you which object triggered the event
- The `type` property tells you what kind of event occurred
- **Event Ordering**:
- Webhook events are not guaranteed to be delivered in order
- Use the `createdAt` timestamp to determine the actual order of events
- Handle events idempotently in case duplicates are received
- **Processing Events**:
- Always check if entity data is available using `hasEntity()`
- Use `getEntity()` to safely access embedded entity data
- The entity data reflects the state at the time the event was created
- **Security**:
- Always verify webhook signatures when processing events
- The webhook event data shows what was sent, but verify authenticity
- Consider webhook events as notifications, not the primary source of truth
- **Debugging**:
- Use the webhook event ID to track specific events
- The embedded entity data helps understand what triggered the event
- Check the `_links` property for related URLs and actions

View File

@ -0,0 +1,53 @@
# Requests
## Overview
The Mollie API client uses request classes to communicate with the Mollie API. Each request class handles specific API endpoints and operations. The response is casted into a dedicated `Mollie\Api\Resources\*` class.
## Sending a Request
To send a request using the Mollie API client, you typically need to:
1. **Create an instance of the client**:
```php
use Mollie\Api\MollieApiClient;
$mollie = new MollieApiClient();
$mollie->setApiKey('test_dHar4XY7LxsDOtmnkVtjNVWXLSlXsM');
```
2. **Create and configure the request**:
Depending on the operation, you might need to create an instance of a specific request class and configure it with necessary parameters.
3. **Send the request**:
Use the client to send the request and handle the response.
```php
use Mollie\Api\MollieApiClient;
use Mollie\Api\Http\Data\Money;
use Mollie\Api\Http\Requests\CreatePaymentRequest;
$mollie = new MollieApiClient();
$createPaymentRequest = new CreatePaymentRequest(
'Test payment',
new Money('EUR', '10.00'),
'https://example.org/redirect',
'https://example.org/webhook'
);
/** @var \Mollie\Api\Resources\Payment $payment */
$payment = $mollie->send($createPaymentRequest);
$this->assertEquals(200, $payment->getResponse()->status());
```
## Adding unsupported properties
If the SDK is not up to date with the API, you can manually add a property to a request via the `query()` or `payload()` methods.
```php
$someRequestUsingPayload = new SomePayloadRequest(...);
$someRequestUsingPayload->payload()->add('foo', 'bar');
$someRequestUsingQueryParams = new SomeQueryRequest(...);
$someRequestUsingQueryParams->query()->add('foo', 'bar');
```

View File

@ -0,0 +1,84 @@
# Responses
Whether you interact with the endpoints using the traditional method (`$mollie->payments->...`) or the new `Request` classes, you can always inspect the raw `Response`.
## Resource Hydration
By default, all responses from are automatically hydrated into the corresponding `Resource` or `ResourceCollection` objects. You can still access the raw response using the `->getResponse()` method.
For example, when retrieving a payment you'll receive a Payment resource object, on which you can still access the raw Response class.
```php
/**
* Legacy approach
*
* @var Mollie\Api\Resources\Payment $payment
*/
$payment = $mollie->payments->get('tr_*********');
/**
* New approach
*
* @var Mollie\Api\Resources\Payment $payment
*/
$payment = $mollie->send(new GetPaymentRequest('tr_*********'));
$response = $payment->getResponse();
```
## Resource Wrappers
Sometimes it's benefitial to directly hydrate a custom class with the information returned by the API. The wrapper resource can be used to define a subset of resource properties used by your app or cast them into your own dedicated Objects.
The resource wrapper class still has access to the underlying `Resource` it wrapps around.
### Define a Wrapper
```php
use Mollie\Api\Utils\Utility;
use Mollie\Api\Resources\Payment;
use Mollie\Api\Resources\ResourceWrapper;
class PaymentWrapper extends ResourceWrapper
{
public function __construct(
public Money $amount,
public Timestamp $createdAt,
)
public static function fromResource($resource): self
{
/** @var Payment $resource */
return (new self(
amount: Utility::transform($resource->amount, fn (stdClass $amount) => Money::fromMollieObject($amount))
createdAt: Utility::transform($resource->createdAt, fn (string $timestamp) => Timestamp::fromIsoString($timestamp))
))->setWrapped($resource);
}
}
```
The `Utility::transform()` method can be used to transform values into your own objects.
### Usage
A resource wrapper can be used by setting the `setHydratableResource()` to the new `WrapperResource`.
```php
use Mollie\Api\Resources\WrapperResource;
$request = new GetPaymentRequest('tr_*********');
$request->setHydratableResource(new WrapperResource(PaymentWrapper::class));
/** @var PaymentWrapper $paymentWrapper */
$paymentWrapper = $mollie->send($request);
```
The original `Payment` resource properties and methods can be accessed through the wrapper class.
```php
// access property
$paymentWrapper->status;
// access method
$paymentWrapper->status();
```

View File

@ -0,0 +1,91 @@
# Retry strategies
The Mollie PHP client automatically retries requests that fail with retryable network errors. You can customize how many retries are performed and how long to wait between attempts by providing a retry strategy.
## Default behavior
- Strategy: `LinearRetryStrategy`
- Default max retries: `5` (in addition to the initial attempt)
- Default delay: linear backoff with a 1000ms increase per attempt
- Attempt 1 (first retry): 1000ms
- Attempt 2: 2000ms
- Attempt 3: 3000ms
- … up to the configured maximum
If all retries are exhausted, the last `Mollie\Api\Exceptions\RetryableNetworkRequestException` is thrown. Fatal middleware hooks, if configured, run once after retries are exhausted.
## Changing the defaults
To change the retry behavior, provide your own strategy instance to the client:
```php
use Mollie\Api\MollieApiClient;
use Mollie\Api\Http\Retry\LinearRetryStrategy;
$client = new MollieApiClient();
// Example: 2 retries with no delay (useful in tests)
$client->setRetryStrategy(new LinearRetryStrategy(2, 0));
// Example: 3 retries with 500ms linear increase (0.5s, 1.0s, 1.5s)
$client->setRetryStrategy(new LinearRetryStrategy(3, 500));
```
To effectively disable retries, set the max retries to `0`:
```php
$client->setRetryStrategy(new LinearRetryStrategy(0, 0));
```
## Creating your own strategy
Custom strategies implement the `Mollie\Api\Contracts\RetryStrategyContract` interface:
```php
namespace Mollie\Api\Contracts;
interface RetryStrategyContract
{
// Maximum number of retries after the initial attempt
public function maxRetries(): int;
// Delay in milliseconds before performing the given retry attempt
// $attempt starts at 1 for the first retry
public function delayBeforeAttemptMs(int $attempt): int;
}
```
### Example: Fixed delay strategy
```php
use Mollie\Api\Contracts\RetryStrategyContract;
class FixedDelayRetryStrategy implements RetryStrategyContract
{
public function __construct(
private int $maxRetries = 3,
private int $delayMs = 1000,
) {}
public function maxRetries(): int
{
return max(0, $this->maxRetries);
}
public function delayBeforeAttemptMs(int $attempt): int
{
// Same delay for every retry
return max(0, $this->delayMs);
}
}
// Usage
$client->setRetryStrategy(new FixedDelayRetryStrategy(3, 250));
```
You can implement any retry timing you prefer (e.g., exponential backoff with jitter, capped delays, etc.) as long as you adhere to the contract.
## When retries happen
Retries are performed only for exceptions that are considered retryable by the HTTP layer and wrapped as `Mollie\Api\Exceptions\RetryableNetworkRequestException`. Other exceptions are not retried and will be thrown immediately.

View File

@ -0,0 +1,239 @@
# Testing with Mollie API Client
## Test Mode Configuration
### Key Concepts
- Test mode is automatically determined by API key prefix (`test_` or `live_`)
- Explicit `testmode` parameter available for OAuth scenarios
- Configure at global client level or per individual request
### Global Configuration
```php
use Mollie\Api\MollieApiClient;
$mollie = new MollieApiClient();
$mollie->setApiKey("test_dHar4XY7LxsDOtmnkVtjNVWXLSlXsM");
$mollie->test(true); // Applies to all subsequent requests
```
### Per-Request Configuration
```php
// Payment request with test mode
$createPaymentRequest = new CreatePaymentRequest(/* ... */);
$mollie->send($createPaymentRequest->test(true));
// Endpoint collection example
$customer = $mollie->customers->get('cust_12345678', testmode: true);
```
## API Mocking
### Basic Usage
API responses can be simulated without network calls by using the `MollieApiClient::fake()` method. Each Request/Response pair passed to the fake method is consumed after a matching request.
> [!NOTE]
> To keep pairs available for reuse after they've been matched, use the `retainRequests` parameter: `MollieApiClient::fake([...], retainRequests: true)`
```php
use Mollie\Api\MollieApiClient;
use Mollie\Api\Fake\MockResponse;
use Mollie\Api\Http\Requests\GetPaymentRequest;
$client = MollieApiClient::fake([
GetPaymentRequest::class => new MockResponse(
body: [
'resource' => 'payment',
'id' => 'tr_xxxxxxxxxxxx',
'mode' => 'test',
'amount' => [
'value' => '20.00',
'currency' => 'EUR'
],
'description' => 'Test',
'status' => 'open',
// ...
],
status: 200
)
]);
$payment = $client->send(new GetPaymentRequest('tr_xxxxxxxxxxxx'));
```
### Sequence Mock Responses
To return different responses for the same request type, use `SequenceMockResponse` and pass `MockResponse` or `Closure` (which return `MockResponse` instances) in the order they should occur.
```php
$client = MollieApiClient::fake([
DynamicGetRequest::class => new SequenceMockResponse(
MockResponse::ok(['first' => 'response']),
MockResponse::ok(['second' => 'response']),
function (PendingRequest $pendingRequest) {
//...
return MockResponse::ok(['third' => 'response']);
}
)
])
To verify that a request was sent, use `assertSent` or `assertSentCount`.
```php
$client->send(new GetPaymentRequest('tr_xxxxxxxxxxxx', embedRefunds: true));
$client->assertSent(GetPaymentRequest::class);
$client->assertSent(function (PendingRequest $pendingRequest, Response $response) {
return $pendingRequest->query()->get('embed') === 'refunds';
});
$client->assertSentCount(1);
```
### MockResponse Options
Configure responses using:
- **Arrays**: Direct data structure
- **Strings**: JSON payloads or predefined fixture names
- **Callables**: Dynamic response generation *or* intercepting assertions
```php
// Array response
MockResponse::created([
'id' => 'tr_xxxxxxxxxxxx',
'amount' => ['value' => '20.00', 'currency' => 'EUR']
]);
// Fixture response
MockResponse::created('payment');
// Dynamic response
MockResponse::created(function (PendingRequest $request) {
return ['amount' => $request->hasParameter('amount') ? 10 : 20];
});
// Intercepting assertions
function (PendingRequest $request) use ($idempotencyKey) {
$this->assertEquals($idempotencyKey, $request->headers()->get('Idempotency-Key'));
return MockResponse::created('payment');
}
```
### Working with Collections
Create paginated list responses:
```php
use Mollie\Api\Resources\PaymentCollection;
$client = MollieApiClient::fake([
GetPaginatedPaymentsRequest::class => MockResponse::list(PaymentCollection::class)
->add([
'resource' => 'payment',
'id' => 'tr_xxxxxxxxxxxx',
'mode' => 'test',
'amount' => [
'value' => '20.00',
'currency' => 'EUR'
],
'description' => 'Test',
'status' => 'open',
// ...
])
->create()
]);
```
### Handling Embedded Resources
Simulate HAL+JSON embedded resources using the `_embedded` property:
**Key Concepts**
- Use `MockResponse::resource()` to start building a resource response
- Chain `embed()` calls to add related collections
- Maintain resource relationships with a fluent interface
```php
use Mollie\Api\Resources\Payment;
use Mollie\Api\Resources\RefundCollection;
use Mollie\Api\Resources\ChargebackCollection;
$client = MollieApiClient::fake([
GetPaymentRequest::class => MockResponse::resource(Payment::class)
->with([ // Main resource properties
'resource' => 'payment',
'id' => 'tr_xxxxxxxxxxxx',
'amount' => [
'value' => '20.00',
'currency' => 'EUR'
]
])
->embed(RefundCollection::class) // First embedded collection
->add([
'resource' => 'refund',
'id' => 're_12345',
'amount' => [
'value' => '10.00',
'currency' => 'EUR'
]
])
->embed(ChargebackCollection::class) // Second embedded collection
->add([
'resource' => 'chargeback',
'id' => 'chb_12345',
'amount' => [
'value' => '20.00',
'currency' => 'EUR'
]
])
->create()
]);
// Resulting response will contain:
// - Payment details in main body
// - Refunds in _embedded.refunds
// - Chargebacks in _embedded.chargebacks
```
### Error Response Mocking
Simulate API error responses using dedicated helper methods or the generic error builder:
**Common Error Shortcuts**
```php
use Mollie\Api\Fake\MockResponse;
// 404 Not Found
$client = MollieApiClient::fake([
GetPaymentRequest::class => MockResponse::notFound('No payment exists with token tr_xxxxxxxxxxx')
]);
// 422 Validation Error (with optional field reference)
$client = MollieApiClient::fake([
CreatePaymentRequest::class => MockResponse::unprocessableEntity(
detail: 'Amount must be at least €1.00',
field: 'amount'
)
]);
```
**Generic Error Builder**
```php
// Custom status code example
$response = MockResponse::error(
status: 403,
title: 'Forbidden',
detail: 'Insufficient permissions to access this resource'
);
// Special characters handling
$detail = 'Invalid parameter "recurringType" - did you mean "sequenceType"?';
$response = MockResponse::unprocessableEntity($detail, 'field');
```
**Error Response Structure**
All errors follow Mollie's standardized format:
```json
{
"status": 404,
"title": "Not Found",
"detail": "No payment exists with token tr_xxxxxxxxxxx",
"field": "amount" // Only present for validation errors
}
```

View File

@ -0,0 +1,136 @@
# Webhooks
Mollie uses webhooks to notify your application about events that occur in your Mollie account. This guide explains how to work with Mollie webhooks in your application securely and efficiently. Mollie currently supports two types of webhooks: the (soon to be labeled) "legacy webhooks" and "next-gen webhooks". This guide is about the next-gen webhooks.
## Overview
Webhooks are HTTP POST requests that Mollie sends to your application when specific events occur, such as when a payment is completed, failed, or refunded. This allows your application to respond to these events in real-time without constantly polling the Mollie API.
## Security
### Signature Verification
**Important**: Always verify webhook signatures to ensure requests come from Mollie and haven't been tampered with.
Mollie signs all webhook requests with an HMAC-SHA256 signature sent in the `X-Mollie-Signature` header. The SDK provides built-in signature verification:
```php
use Mollie\Api\Webhooks\SignatureValidator;
use Mollie\Api\Exceptions\InvalidSignatureException;
$signingSecret = "your_webhook_signing_secret_from_dashboard";
try {
$validator = new SignatureValidator($signingSecret);
/**
* Validate the webhook signature
*
* This method will throw an InvalidSignatureException if:
* - A signature header is present but doesn't contain a valid signature
* - The payload has been tampered with
*
* For PSR-7 requests, you can also use validateRequest($psr7Request)
*/
$isValid = $validator->validatePayload($requestBody, $signature);
if (!$isValid) {
// Handle invalid signature
http_response_code(400);
exit('Invalid signature');
}
// Signature is valid - proceed with webhook processing
} catch (InvalidSignatureException $e) {
// Log the invalid signature attempt for security monitoring
error_log("Invalid webhook signature: " . $e->getMessage());
http_response_code(400);
exit('Invalid signature');
}
```
#### Key Rotation
During key rotation or migration periods, you can verify signatures against multiple secrets:
```php
$signingSecrets = [
"current_secret",
"previous_secret" // Keep old secret during transition period
];
$validator = new SignatureValidator($signingSecrets);
$isValid = $validator->validatePayload($requestBody, $signature);
```
### Processing Webhook Payloads
Once you've verified the webhook signature, you can safely process the payload:
```php
use Mollie\Api\Webhooks\WebhookEventMapper;
use Mollie\Api\Webhooks\Events\PaymentLinkPaid;
// Process the webhook payload into an event object
$event = (new WebhookEventMapper())->processPayload($request->getParsedBody());
// Extract the entity ID (e.g., payment ID, customer ID, etc.)
$entityId = $event->entityData('id');
// Get the full resource object for direct interaction
// This only works if you subscribe to full event payloads
$resource = $event->entity()->asResource($mollie);
// Handle different event types
match (true) {
$event instanceof PaymentLinkPaid => $this->handlePaymentLinkPaid(),
$event instanceof BalanceTransactionCreated => $this->handleBalanceTransactionCreated(),
// ... handle other event types
};
```
#### Using custom webhook Events
If the API is ahead of this SDK's implementation of new Events, you can create your own Events as temporary workaround and pass it into the `WebhookEventMapper`
```php
// Event class
use Mollie\Api\Webhooks\Events\BaseEvent;
class SomeEventHappened extends BaseEvent
{
public static function type(): string
{
return 'some.event_happened'; // needs to match the eventType from the documentation
}
}
// passing into event mapper and processing payload
$event = (new WebhookEventMapper([
'some.event_happened' => SomeEventHappened::class
]))->processPayload($request->getParsedBody());
```
### Testing Webhooks
Testing webhooks is crucial to ensure your application handles all event types correctly. The SDK provides several tools to help you test webhook scenarios.
Use `MockEvent` to create realistic webhook payloads for testing:
```php
use Mollie\Api\Fake\MockEvent;
use Mollie\Api\Webhooks\Events\PaymentLinkPaid;
use Mollie\Api\Webhooks\Events\BalanceTransactionCreated;
// Create a mock PaymentLinkPaid event
$paymentLinkEventPayload = MockEvent::for(PaymentLinkPaid::class)
->entityId('pl_1234567890')
->full() // Include full resource data
->create();
// Create a mock BalanceTransactionCreated event
$balanceEventPayload = MockEvent::for(BalanceTransactionCreated::class)
->entityId('bt_9876543210')
->simple() // Webhook request without any resource data besides entityId
->create();
```

View File

@ -0,0 +1,67 @@
<?php
namespace Mollie\Api;
use Mollie\Api\Exceptions\IncompatiblePlatformException;
class CompatibilityChecker
{
/**
* @var string
*/
public const MIN_PHP_VERSION = '7.4';
public static function make(): self
{
return new self;
}
/**
* @return void
*
* @throws IncompatiblePlatformException
*/
public function checkCompatibility()
{
if (! $this->satisfiesPhpVersion()) {
throw new IncompatiblePlatformException(
'The client requires PHP version >= '.self::MIN_PHP_VERSION.', you have '.PHP_VERSION.'.',
IncompatiblePlatformException::INCOMPATIBLE_PHP_VERSION
);
}
if (! $this->satisfiesJsonExtension()) {
throw new IncompatiblePlatformException(
"PHP extension json is not enabled. Please make sure to enable 'json' in your PHP configuration.",
IncompatiblePlatformException::INCOMPATIBLE_JSON_EXTENSION
);
}
}
/**
* @return bool
*
* @codeCoverageIgnore
*/
public function satisfiesPhpVersion()
{
return (bool) version_compare(PHP_VERSION, self::MIN_PHP_VERSION, '>=');
}
/**
* @return bool
*
* @codeCoverageIgnore
*/
public function satisfiesJsonExtension()
{
// Check by extension_loaded
if (function_exists('extension_loaded') && extension_loaded('json')) {
return true;
} elseif (function_exists('json_encode')) {
return true;
}
return false;
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Mollie\Api;
use Mollie\Api\Resources\ResourceRegistry;
class Config
{
/**
* Resolver used to fetch the global ResourceRegistry, e.g. from a container.
*
* @var callable():ResourceRegistry|null
*/
private static $resourceRegistryResolver = null;
/**
* Set a resolver that returns a ResourceRegistry instance. Pass null to reset.
*
* @param callable():ResourceRegistry|null $resolver
*/
public static function setResourceRegistryResolver(?callable $resolver): void
{
self::$resourceRegistryResolver = $resolver;
}
/**
* Resolve the ResourceRegistry. Uses the resolver if set, otherwise defaults.
*/
public static function resourceRegistry(): ResourceRegistry
{
$resolver = self::$resourceRegistryResolver;
if (! is_callable($resolver)) {
return ResourceRegistry::default();
}
$registry = call_user_func($resolver);
if (! $registry instanceof ResourceRegistry) {
return ResourceRegistry::default();
}
return $registry;
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Mollie\Api\Contracts;
interface Arrayable
{
public function toArray(): array;
}

View File

@ -0,0 +1,12 @@
<?php
namespace Mollie\Api\Contracts;
interface Authenticatable
{
public function setApiKey(string $apiKey): self;
public function setAccessToken(string $accessToken): self;
public function getAuthenticator(): ?Authenticator;
}

View File

@ -0,0 +1,10 @@
<?php
namespace Mollie\Api\Contracts;
use Mollie\Api\Http\PendingRequest;
interface Authenticator
{
public function authenticate(PendingRequest $pendingRequest): void;
}

View File

@ -0,0 +1,28 @@
<?php
namespace Mollie\Api\Contracts;
use Mollie\Api\Http\Middleware;
use Mollie\Api\Http\Request;
interface Connector extends Authenticatable, IdempotencyContract, SupportsDebuggingContract, Testable
{
/**
* @return mixed
*/
public function send(Request $request);
public function resolveBaseUrl(): string;
public function headers(): Repository;
public function query(): Repository;
public function middleware(): Middleware;
public function addVersionString($versionString): self;
public function getVersionStrings(): array;
public function getHttpClient(): HttpAdapterContract;
}

View File

@ -0,0 +1,8 @@
<?php
namespace Mollie\Api\Contracts;
interface EmbeddedResourcesContract
{
public function getEmbeddedResourcesMap(): array;
}

View File

@ -0,0 +1,8 @@
<?php
namespace Mollie\Api\Contracts;
interface HasPayload
{
public function payload(): PayloadRepository;
}

View File

@ -0,0 +1,26 @@
<?php
namespace Mollie\Api\Contracts;
use Mollie\Api\Http\PendingRequest;
use Mollie\Api\Http\Response;
use Mollie\Api\Utils\Factories;
interface HttpAdapterContract
{
public function factories(): Factories;
/**
* Send a request to the specified Mollie api url.
*
* @throws \Mollie\Api\Exceptions\ApiException
*/
public function sendRequest(PendingRequest $pendingRequest): Response;
/**
* The version number for the underlying http client, if available.
*
* @example Guzzle/6.3
*/
public function version(): ?string;
}

View File

@ -0,0 +1,12 @@
<?php
namespace Mollie\Api\Contracts;
interface IdempotencyContract
{
public function getIdempotencyKey(): ?string;
public function resetIdempotencyKey(): self;
public function getIdempotencyKeyGenerator(): ?IdempotencyKeyGeneratorContract;
}

View File

@ -0,0 +1,8 @@
<?php
namespace Mollie\Api\Contracts;
interface IdempotencyKeyGeneratorContract
{
public function generate(): string;
}

View File

@ -0,0 +1,10 @@
<?php
namespace Mollie\Api\Contracts;
interface IsIteratable
{
public function iteratorEnabled(): bool;
public function iteratesBackwards(): bool;
}

View File

@ -0,0 +1,15 @@
<?php
namespace Mollie\Api\Contracts;
use Mollie\Api\Http\Response;
interface IsResponseAware extends ViableResponse
{
public function getResponse(): Response;
/**
* @return $this
*/
public function setResponse(Response $response);
}

View File

@ -0,0 +1,16 @@
<?php
namespace Mollie\Api\Contracts;
use Mollie\Api\Http\Response;
use Mollie\Api\Resources\BaseCollection;
use Mollie\Api\Resources\BaseResource;
use Mollie\Api\Resources\LazyCollection;
interface IsWrapper extends ViableResponse
{
/**
* @param Response|BaseResource|BaseCollection|LazyCollection $resource
*/
public static function fromResource($resource): self;
}

View File

@ -0,0 +1,11 @@
<?php
namespace Mollie\Api\Contracts;
interface MollieHttpAdapterPickerContract
{
/**
* @param \GuzzleHttp\ClientInterface|HttpAdapterContract $httpClient
*/
public function pickHttpAdapter($httpClient): HttpAdapterContract;
}

View File

@ -0,0 +1,14 @@
<?php
namespace Mollie\Api\Contracts;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;
interface PayloadRepository extends Repository
{
/**
* Convert the repository contents into a stream
*/
public function toStream(StreamFactoryInterface $streamFactory): StreamInterface;
}

View File

@ -0,0 +1,64 @@
<?php
namespace Mollie\Api\Contracts;
interface Repository
{
/**
* Set the entire repository data
*
* @param mixed $data Array for array repositories, mixed for payload repositories
* @return static
*/
public function set($data);
/**
* Get a value by key
*
* @param mixed $default
* @return mixed
*/
public function get(string $key, $default = null);
/**
* Check if a key exists
*/
public function has(string $key): bool;
/**
* Add a value to the repository
*
* @param mixed $value
* @return static
*/
public function add(string $key, $value);
/**
* Merge data into the repository
*
* @return static
*/
public function merge(array ...$data);
/**
* Remove a key from the repository
*/
public function remove(string $key);
/**
* Get all data from the repository
*
* @return mixed
*/
public function all();
/**
* Check if the repository is empty
*/
public function isEmpty(): bool;
/**
* Check if the repository is not empty
*/
public function isNotEmpty(): bool;
}

View File

@ -0,0 +1,13 @@
<?php
namespace Mollie\Api\Contracts;
use Mollie\Api\Http\PendingRequest;
interface RequestMiddleware
{
/**
* @return PendingRequest|void
*/
public function __invoke(PendingRequest $pendingRequest);
}

View File

@ -0,0 +1,7 @@
<?php
namespace Mollie\Api\Contracts;
interface Resolvable extends Arrayable
{
}

Some files were not shown because too many files have changed in this diff Show More