6 private function config(): array {
7 $file = dirname(__DIR__) .
'/cfg/payment.php';
13 $paypal = is_array(
$cfg) ? (
$cfg[
'paypal'] ?? array()) : array();
16 if (function_exists(
'dbx')) {
17 $shopCfg =
dbx()->get_config(
'dbxShop');
18 if (is_array($shopCfg)) {
19 $paypal = array_merge($paypal, array(
20 'enabled' => !empty($shopCfg[
'payment_paypal_enabled']),
21 'mode' => (
string)($shopCfg[
'payment_paypal_mode'] ?? ($paypal[
'mode'] ??
'sandbox')),
22 'client_id' => (
string)($shopCfg[
'payment_paypal_client_id'] ?? ($paypal[
'client_id'] ??
'')),
23 'client_secret' => (
string)($shopCfg[
'payment_paypal_client_secret'] ?? ($paypal[
'client_secret'] ??
'')),
24 'brand_name' => (
string)($shopCfg[
'payment_paypal_brand_name'] ?? ($paypal[
'brand_name'] ??
'dbXapp')),
25 'currency' => (
string)($shopCfg[
'payment_paypal_currency'] ?? $shopCfg[
'default_currency'] ?? ($paypal[
'currency'] ??
'EUR')),
34 $cfg = $this->config();
35 return !empty(
$cfg[
'enabled'])
36 && trim((
string)(
$cfg[
'client_id'] ??
'')) !==
''
37 && trim((
string)(
$cfg[
'client_secret'] ??
'')) !==
'';
40 public function mode(): string {
41 $cfg = $this->config();
42 return (
string)(
$cfg[
'mode'] ??
'sandbox') ===
'live' ?
'live' :
'sandbox';
46 return
'PayPal ist vorbereitet. Aktivieren Sie PayPal unter Shop > Einstellungen und tragen Sie Client-ID und Secret ein.';
49 private function apiBase(): string {
50 $cfg = $this->config();
51 return (
string)(
$cfg[
'mode'] ??
'sandbox') ===
'live'
52 ?
'https://api-m.paypal.com'
53 :
'https://api-m.sandbox.paypal.com';
56 private function request(
string $method,
string $path, array $headers = array(), ?array $payload =
null, ?
string $basicUser =
null, ?
string $basicPassword =
null): array {
57 $ch = curl_init($this->apiBase() . $path);
58 $method = strtoupper($method);
59 $body = $payload !==
null ? json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : null;
60 $httpHeaders = $headers;
62 curl_setopt($ch, CURLOPT_RETURNTRANSFER,
true);
63 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
64 curl_setopt($ch, CURLOPT_TIMEOUT, 25);
65 curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeaders);
66 if ($basicUser !==
null) {
67 curl_setopt($ch, CURLOPT_USERPWD, $basicUser .
':' . (
string)$basicPassword);
70 curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
73 $raw = curl_exec($ch);
74 $status = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
75 $err = curl_error($ch);
78 if ($raw ===
false || $err !==
'') {
79 throw new \RuntimeException(
'PayPal-Verbindung fehlgeschlagen: ' . $err);
82 $data = json_decode((
string)$raw,
true);
83 if (!is_array($data)) {
84 $data = array(
'raw' => (
string)$raw);
86 if ($status < 200 || $status >= 300) {
87 $msg = $data[
'message'] ?? $data[
'name'] ?? (
'HTTP ' . $status);
88 throw new \RuntimeException(
'PayPal-Fehler: ' . $msg);
93 private function accessToken(): string {
94 $cfg = $this->config();
95 $clientId = trim((
string)(
$cfg[
'client_id'] ??
''));
96 $secret = trim((
string)(
$cfg[
'client_secret'] ??
''));
97 if ($clientId ===
'' || $secret ===
'') {
98 throw new \RuntimeException(
'PayPal-Zugangsdaten fehlen.');
101 $ch = curl_init($this->apiBase() .
'/v1/oauth2/token');
102 curl_setopt($ch, CURLOPT_RETURNTRANSFER,
true);
103 curl_setopt($ch, CURLOPT_POST,
true);
104 curl_setopt($ch, CURLOPT_POSTFIELDS,
'grant_type=client_credentials');
105 curl_setopt($ch, CURLOPT_USERPWD, $clientId .
':' . $secret);
106 curl_setopt($ch, CURLOPT_HTTPHEADER, array(
107 'Accept: application/json',
108 'Accept-Language: de_DE',
109 'Content-Type: application/x-www-form-urlencoded',
111 curl_setopt($ch, CURLOPT_TIMEOUT, 25);
112 $raw = curl_exec($ch);
113 $status = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
114 $err = curl_error($ch);
117 if ($raw ===
false || $err !==
'') {
118 throw new \RuntimeException(
'PayPal-Token konnte nicht geladen werden: ' . $err);
120 $data = json_decode((
string)$raw,
true);
121 if ($status < 200 || $status >= 300 || !is_array($data) || empty($data[
'access_token'])) {
122 throw new \RuntimeException(
'PayPal-Token wurde abgelehnt.');
124 return (
string)$data[
'access_token'];
127 public function createOrder(array $order,
string $returnUrl,
string $cancelUrl): array {
128 $cfg = $this->config();
129 $token = $this->accessToken();
130 $currency = (string)(
$cfg[
'currency'] ?? $order[
'currency'] ??
'EUR');
131 $amount = number_format((
float)($order[
'total_gross'] ?? 0), 2,
'.',
'');
134 'intent' =>
'CAPTURE',
135 'purchase_units' => array(array(
136 'reference_id' => (
string)($order[
'order_no'] ??
''),
137 'description' =>
'dbXapp Bestellung ' . (
string)($order[
'order_no'] ??
''),
138 'invoice_id' => (
string)($order[
'order_no'] ??
''),
140 'currency_code' => $currency,
144 'payment_source' => array(
146 'experience_context' => array(
147 'brand_name' => (
string)(
$cfg[
'brand_name'] ??
'dbXapp'),
149 'shipping_preference' =>
'NO_SHIPPING',
150 'user_action' =>
'PAY_NOW',
151 'return_url' => $returnUrl,
152 'cancel_url' => $cancelUrl,
158 return $this->request(
'POST',
'/v2/checkout/orders', array(
159 'Content-Type: application/json',
160 'Authorization: Bearer ' . $token,
161 'Prefer: return=representation',
165 public function capture(
string $paypalOrderId): array {
166 $token = $this->accessToken();
167 return $this->request(
'POST',
'/v2/checkout/orders/' . rawurlencode($paypalOrderId) .
'/capture', array(
168 'Content-Type: application/json',
169 'Authorization: Bearer ' . $token,
170 'Prefer: return=representation',
175 foreach (($paypalOrder[
'links'] ?? array()) as $link) {
176 if (($link[
'rel'] ??
'') ===
'approve' && !empty($link[
'href'])) {
177 return (
string)$link[
'href'];
187 'mode' => $this->
mode(),
188 'message' =>
'PayPal ist nicht vollstaendig konfiguriert.',
193 $token = $this->accessToken();
195 'ok' => $token !==
'',
196 'mode' => $this->mode(),
197 'message' => $token !==
''
198 ?
'PayPal OAuth Token wurde erfolgreich geladen.'
199 :
'PayPal OAuth Token ist leer.',
201 }
catch (\Throwable $e) {
204 'mode' => $this->mode(),
205 'message' => $e->getMessage(),