@artilingo/artiframe-cli 1.4.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/core-stubs/bin/SystemMethod.php +600 -521
  2. package/core-stubs/bin/ViewMethod.php +591 -393
  3. package/core-stubs/docs/de.html +4347 -4390
  4. package/core-stubs/docs/en.html +4347 -4389
  5. package/core-stubs/docs/es.html +4347 -4390
  6. package/core-stubs/docs/fr.html +4347 -4389
  7. package/core-stubs/docs/tr.html +138 -109
  8. package/package.json +1 -1
  9. package/src/App.php +8 -0
  10. package/src/Commands/AddCommand.php +1 -1
  11. package/src/Commands/ListCommand.php +2 -1
  12. package/src/Commands/MakeApiCommand.php +1 -1
  13. package/src/Commands/MakeClassCommand.php +1 -1
  14. package/src/Commands/MakeViewCommand.php +1 -1
  15. package/src/Commands/NewProjectCommand.php +37 -25
  16. package/src/Commands/RemoveCommand.php +21 -10
  17. package/src/Commands/ServeCommand.php +29 -9
  18. package/src/Commands/TableCommand.php +2 -1
  19. package/src/Commands/UpgradeCommand.php +209 -0
  20. package/src/Commands/VersionCommand.php +2 -1
  21. package/src/Lang/de.php +7 -1
  22. package/src/Lang/en.php +6 -0
  23. package/src/Lang/es.php +7 -1
  24. package/src/Lang/fr.php +6 -0
  25. package/src/Lang/tr.php +7 -1
  26. package/src/Services/Safeguard.php +25 -0
  27. package/stubs/service/image.stub +334 -334
  28. package/stubs/service/iyzico.stub +637 -637
  29. package/stubs/service/jwt.stub +312 -312
  30. package/stubs/service/phpmailer.stub +143 -143
  31. package/stubs/service/pusher.stub +232 -232
  32. package/stubs/service/qrcode.stub +169 -169
  33. package/stubs/service/redis.stub +361 -361
  34. package/stubs/service/s3.stub +377 -377
  35. package/stubs/service/sentry.stub +76 -106
  36. package/stubs/service/stripe.stub +526 -526
  37. package/stubs/service/twilio.stub +361 -361
  38. package/core-stubs/app/R2Manager.php +0 -130
@@ -1,526 +1,526 @@
1
- <?php
2
-
3
- namespace Service;
4
-
5
- /**
6
- * Stripe Payment Service
7
- *
8
- * Provides a clean wrapper around the stripe-php SDK for payment processing,
9
- * customer management, subscriptions, product creation, and webhook handling.
10
- *
11
- * Required environment variables:
12
- * STRIPE_SECRET_KEY — Your Stripe secret API key
13
- * STRIPE_PUBLISHABLE_KEY — Your Stripe publishable key (for front-end reference)
14
- * STRIPE_WEBHOOK_SECRET — Webhook endpoint signing secret
15
- *
16
- * @package Service
17
- */
18
- class Stripe
19
- {
20
- /**
21
- * Stripe publishable key (exposed for front-end usage).
22
- *
23
- * @var string
24
- */
25
- private string $publishableKey;
26
-
27
- /**
28
- * Webhook signing secret.
29
- *
30
- * @var string
31
- */
32
- private string $webhookSecret;
33
-
34
- /**
35
- * Create a new Stripe service instance.
36
- *
37
- * Sets the API key globally for the Stripe SDK and stores
38
- * the publishable key and webhook secret for later use.
39
- */
40
- public function __construct()
41
- {
42
- \Stripe\Stripe::setApiKey($_ENV['STRIPE_SECRET_KEY']);
43
- $this->publishableKey = $_ENV['STRIPE_PUBLISHABLE_KEY'];
44
- $this->webhookSecret = $_ENV['STRIPE_WEBHOOK_SECRET'];
45
- }
46
-
47
- /**
48
- * Create a PaymentIntent.
49
- *
50
- * Use this for the recommended Stripe payment flow where the client
51
- * confirms the payment on the front-end using the returned client_secret.
52
- *
53
- * @param float $amount Amount in major currency units (e.g. 19.99)
54
- * @param string $currency ISO currency code (default: usd)
55
- * @param array $metadata Arbitrary key-value metadata
56
- * @return array Standardised response with clientSecret and paymentIntentId
57
- */
58
- public function createPaymentIntent(float $amount, string $currency = 'usd', array $metadata = []): array
59
- {
60
- try {
61
- $intent = \Stripe\PaymentIntent::create([
62
- 'amount' => (int) round($amount * 100),
63
- 'currency' => strtolower($currency),
64
- 'metadata' => $metadata,
65
- 'automatic_payment_methods' => ['enabled' => true],
66
- ]);
67
-
68
- return [
69
- 'status' => 'success',
70
- 'message' => 'PaymentIntent created.',
71
- 'data' => [
72
- 'paymentIntentId' => $intent->id,
73
- 'clientSecret' => $intent->client_secret,
74
- 'amount' => $intent->amount,
75
- 'currency' => $intent->currency,
76
- 'status' => $intent->status,
77
- ],
78
- ];
79
- } catch (\Stripe\Exception\ApiErrorException $e) {
80
- return [
81
- 'status' => 'error',
82
- 'message' => $e->getMessage(),
83
- 'data' => ['stripeCode' => $e->getStripeCode()],
84
- ];
85
- } catch (\Exception $e) {
86
- return [
87
- 'status' => 'error',
88
- 'message' => $e->getMessage(),
89
- 'data' => [],
90
- ];
91
- }
92
- }
93
-
94
- /**
95
- * Confirm an existing PaymentIntent server-side.
96
- *
97
- * Typically the client confirms, but this allows server-side confirmation
98
- * for scenarios like off-session payments.
99
- *
100
- * @param string $paymentIntentId The PaymentIntent ID to confirm
101
- * @return array Standardised response
102
- */
103
- public function confirmPayment(string $paymentIntentId): array
104
- {
105
- try {
106
- $intent = \Stripe\PaymentIntent::retrieve($paymentIntentId);
107
- $intent->confirm();
108
-
109
- return [
110
- 'status' => 'success',
111
- 'message' => 'PaymentIntent confirmed.',
112
- 'data' => [
113
- 'paymentIntentId' => $intent->id,
114
- 'status' => $intent->status,
115
- 'amount' => $intent->amount,
116
- 'currency' => $intent->currency,
117
- ],
118
- ];
119
- } catch (\Stripe\Exception\ApiErrorException $e) {
120
- return [
121
- 'status' => 'error',
122
- 'message' => $e->getMessage(),
123
- 'data' => ['stripeCode' => $e->getStripeCode()],
124
- ];
125
- } catch (\Exception $e) {
126
- return [
127
- 'status' => 'error',
128
- 'message' => $e->getMessage(),
129
- 'data' => [],
130
- ];
131
- }
132
- }
133
-
134
- /**
135
- * Create a direct charge (legacy flow).
136
- *
137
- * Use createPaymentIntent() for new integrations. This method is provided
138
- * for backward compatibility with token/source-based flows.
139
- *
140
- * @param float $amount Amount in major currency units
141
- * @param string $source Token or source ID (tok_xxx or src_xxx)
142
- * @param string $currency ISO currency code (default: usd)
143
- * @param string $description Charge description
144
- * @return array Standardised response with chargeId
145
- */
146
- public function charge(float $amount, string $source, string $currency = 'usd', string $description = ''): array
147
- {
148
- try {
149
- $charge = \Stripe\Charge::create([
150
- 'amount' => (int) round($amount * 100),
151
- 'currency' => strtolower($currency),
152
- 'source' => $source,
153
- 'description' => $description,
154
- ]);
155
-
156
- return [
157
- 'status' => 'success',
158
- 'message' => 'Charge created successfully.',
159
- 'data' => [
160
- 'chargeId' => $charge->id,
161
- 'amount' => $charge->amount,
162
- 'currency' => $charge->currency,
163
- 'paid' => $charge->paid,
164
- 'status' => $charge->status,
165
- 'receipt' => $charge->receipt_url,
166
- ],
167
- ];
168
- } catch (\Stripe\Exception\CardException $e) {
169
- return [
170
- 'status' => 'error',
171
- 'message' => $e->getMessage(),
172
- 'data' => [
173
- 'stripeCode' => $e->getStripeCode(),
174
- 'declineCode' => $e->getDeclineCode(),
175
- ],
176
- ];
177
- } catch (\Stripe\Exception\ApiErrorException $e) {
178
- return [
179
- 'status' => 'error',
180
- 'message' => $e->getMessage(),
181
- 'data' => ['stripeCode' => $e->getStripeCode()],
182
- ];
183
- } catch (\Exception $e) {
184
- return [
185
- 'status' => 'error',
186
- 'message' => $e->getMessage(),
187
- 'data' => [],
188
- ];
189
- }
190
- }
191
-
192
- /**
193
- * Refund a charge.
194
- *
195
- * @param string $chargeId The charge ID to refund
196
- * @param float|null $amount Partial refund amount (null = full refund)
197
- * @return array Standardised response with refundId
198
- */
199
- public function refund(string $chargeId, float $amount = null): array
200
- {
201
- try {
202
- $params = ['charge' => $chargeId];
203
-
204
- if ($amount !== null) {
205
- $params['amount'] = (int) round($amount * 100);
206
- }
207
-
208
- $refund = \Stripe\Refund::create($params);
209
-
210
- return [
211
- 'status' => 'success',
212
- 'message' => 'Refund processed successfully.',
213
- 'data' => [
214
- 'refundId' => $refund->id,
215
- 'amount' => $refund->amount,
216
- 'currency' => $refund->currency,
217
- 'status' => $refund->status,
218
- 'chargeId' => $refund->charge,
219
- ],
220
- ];
221
- } catch (\Stripe\Exception\ApiErrorException $e) {
222
- return [
223
- 'status' => 'error',
224
- 'message' => $e->getMessage(),
225
- 'data' => ['stripeCode' => $e->getStripeCode()],
226
- ];
227
- } catch (\Exception $e) {
228
- return [
229
- 'status' => 'error',
230
- 'message' => $e->getMessage(),
231
- 'data' => [],
232
- ];
233
- }
234
- }
235
-
236
- /**
237
- * Create a Stripe Customer.
238
- *
239
- * Customers are required for subscriptions and saved payment methods.
240
- *
241
- * @param string $email Customer e-mail
242
- * @param string $name Customer full name
243
- * @param array $metadata Arbitrary key-value metadata
244
- * @return array Standardised response with customerId
245
- */
246
- public function createCustomer(string $email, string $name = '', array $metadata = []): array
247
- {
248
- try {
249
- $params = [
250
- 'email' => $email,
251
- 'metadata' => $metadata,
252
- ];
253
-
254
- if ($name !== '') {
255
- $params['name'] = $name;
256
- }
257
-
258
- $customer = \Stripe\Customer::create($params);
259
-
260
- return [
261
- 'status' => 'success',
262
- 'message' => 'Customer created successfully.',
263
- 'data' => [
264
- 'customerId' => $customer->id,
265
- 'email' => $customer->email,
266
- 'name' => $customer->name,
267
- 'created' => date('Y-m-d H:i:s', $customer->created),
268
- ],
269
- ];
270
- } catch (\Stripe\Exception\ApiErrorException $e) {
271
- return [
272
- 'status' => 'error',
273
- 'message' => $e->getMessage(),
274
- 'data' => ['stripeCode' => $e->getStripeCode()],
275
- ];
276
- } catch (\Exception $e) {
277
- return [
278
- 'status' => 'error',
279
- 'message' => $e->getMessage(),
280
- 'data' => [],
281
- ];
282
- }
283
- }
284
-
285
- /**
286
- * Create a subscription for a customer.
287
- *
288
- * The customer must already have a default payment method attached.
289
- *
290
- * @param string $customerId Stripe customer ID
291
- * @param string $priceId Stripe price ID (price_xxx)
292
- * @return array Standardised response with subscriptionId
293
- */
294
- public function subscribe(string $customerId, string $priceId): array
295
- {
296
- try {
297
- $subscription = \Stripe\Subscription::create([
298
- 'customer' => $customerId,
299
- 'items' => [['price' => $priceId]],
300
- 'payment_behavior' => 'default_incomplete',
301
- 'expand' => ['latest_invoice.payment_intent'],
302
- ]);
303
-
304
- $latestInvoice = $subscription->latest_invoice;
305
- $clientSecret = null;
306
-
307
- if (is_object($latestInvoice) && $latestInvoice->payment_intent) {
308
- $clientSecret = $latestInvoice->payment_intent->client_secret;
309
- }
310
-
311
- return [
312
- 'status' => 'success',
313
- 'message' => 'Subscription created.',
314
- 'data' => [
315
- 'subscriptionId' => $subscription->id,
316
- 'status' => $subscription->status,
317
- 'currentPeriodStart' => date('Y-m-d H:i:s', $subscription->current_period_start),
318
- 'currentPeriodEnd' => date('Y-m-d H:i:s', $subscription->current_period_end),
319
- 'clientSecret' => $clientSecret,
320
- ],
321
- ];
322
- } catch (\Stripe\Exception\ApiErrorException $e) {
323
- return [
324
- 'status' => 'error',
325
- 'message' => $e->getMessage(),
326
- 'data' => ['stripeCode' => $e->getStripeCode()],
327
- ];
328
- } catch (\Exception $e) {
329
- return [
330
- 'status' => 'error',
331
- 'message' => $e->getMessage(),
332
- 'data' => [],
333
- ];
334
- }
335
- }
336
-
337
- /**
338
- * Cancel a subscription.
339
- *
340
- * Cancels immediately. For end-of-period cancellation, modify the
341
- * subscription's cancel_at_period_end flag instead.
342
- *
343
- * @param string $subscriptionId Stripe subscription ID
344
- * @return array Standardised response
345
- */
346
- public function cancelSubscription(string $subscriptionId): array
347
- {
348
- try {
349
- $subscription = \Stripe\Subscription::retrieve($subscriptionId);
350
- $subscription->cancel();
351
-
352
- return [
353
- 'status' => 'success',
354
- 'message' => 'Subscription cancelled.',
355
- 'data' => [
356
- 'subscriptionId' => $subscription->id,
357
- 'status' => $subscription->status,
358
- 'canceledAt' => $subscription->canceled_at
359
- ? date('Y-m-d H:i:s', $subscription->canceled_at)
360
- : null,
361
- ],
362
- ];
363
- } catch (\Stripe\Exception\ApiErrorException $e) {
364
- return [
365
- 'status' => 'error',
366
- 'message' => $e->getMessage(),
367
- 'data' => ['stripeCode' => $e->getStripeCode()],
368
- ];
369
- } catch (\Exception $e) {
370
- return [
371
- 'status' => 'error',
372
- 'message' => $e->getMessage(),
373
- 'data' => [],
374
- ];
375
- }
376
- }
377
-
378
- /**
379
- * Create a Product and its associated Price in one call.
380
- *
381
- * Simplifies the two-step Stripe flow of creating a product first,
382
- * then attaching a recurring or one-time price to it.
383
- *
384
- * @param string $name Product name
385
- * @param float $price Unit price in major currency units
386
- * @param string $currency ISO currency code (default: usd)
387
- * @return array Standardised response with productId and priceId
388
- */
389
- public function createProduct(string $name, float $price, string $currency = 'usd'): array
390
- {
391
- try {
392
- $product = \Stripe\Product::create([
393
- 'name' => $name,
394
- ]);
395
-
396
- $priceObj = \Stripe\Price::create([
397
- 'product' => $product->id,
398
- 'unit_amount' => (int) round($price * 100),
399
- 'currency' => strtolower($currency),
400
- ]);
401
-
402
- return [
403
- 'status' => 'success',
404
- 'message' => 'Product and price created.',
405
- 'data' => [
406
- 'productId' => $product->id,
407
- 'priceId' => $priceObj->id,
408
- 'name' => $product->name,
409
- 'amount' => $priceObj->unit_amount,
410
- 'currency' => $priceObj->currency,
411
- ],
412
- ];
413
- } catch (\Stripe\Exception\ApiErrorException $e) {
414
- return [
415
- 'status' => 'error',
416
- 'message' => $e->getMessage(),
417
- 'data' => ['stripeCode' => $e->getStripeCode()],
418
- ];
419
- } catch (\Exception $e) {
420
- return [
421
- 'status' => 'error',
422
- 'message' => $e->getMessage(),
423
- 'data' => [],
424
- ];
425
- }
426
- }
427
-
428
- /**
429
- * Verify and parse a Stripe webhook event.
430
- *
431
- * Validates the webhook signature using the signing secret and returns
432
- * the parsed event data. Call this from your webhook endpoint handler.
433
- *
434
- * @param string $payload Raw request body (file_get_contents('php://input'))
435
- * @param string $signature Stripe-Signature header value
436
- * @return array Standardised response with event type and data
437
- */
438
- public function webhook(string $payload, string $signature): array
439
- {
440
- try {
441
- $event = \Stripe\Webhook::constructEvent(
442
- $payload,
443
- $signature,
444
- $this->webhookSecret
445
- );
446
-
447
- return [
448
- 'status' => 'success',
449
- 'message' => 'Webhook verified.',
450
- 'data' => [
451
- 'eventId' => $event->id,
452
- 'eventType' => $event->type,
453
- 'created' => date('Y-m-d H:i:s', $event->created),
454
- 'object' => $event->data->object->toArray(),
455
- ],
456
- ];
457
- } catch (\Stripe\Exception\SignatureVerificationException $e) {
458
- return [
459
- 'status' => 'error',
460
- 'message' => 'Webhook signature verification failed: ' . $e->getMessage(),
461
- 'data' => [],
462
- ];
463
- } catch (\UnexpectedValueException $e) {
464
- return [
465
- 'status' => 'error',
466
- 'message' => 'Invalid webhook payload: ' . $e->getMessage(),
467
- 'data' => [],
468
- ];
469
- } catch (\Exception $e) {
470
- return [
471
- 'status' => 'error',
472
- 'message' => $e->getMessage(),
473
- 'data' => [],
474
- ];
475
- }
476
- }
477
-
478
- /**
479
- * List recent PaymentIntents.
480
- *
481
- * @param int $limit Number of results to return (max 100, default 10)
482
- * @return array Standardised response with array of payment intents
483
- */
484
- public function listPayments(int $limit = 10): array
485
- {
486
- try {
487
- $intents = \Stripe\PaymentIntent::all([
488
- 'limit' => min($limit, 100),
489
- ]);
490
-
491
- $payments = [];
492
- foreach ($intents->data as $intent) {
493
- $payments[] = [
494
- 'paymentIntentId' => $intent->id,
495
- 'amount' => $intent->amount,
496
- 'currency' => $intent->currency,
497
- 'status' => $intent->status,
498
- 'created' => date('Y-m-d H:i:s', $intent->created),
499
- 'description' => $intent->description,
500
- 'metadata' => $intent->metadata->toArray(),
501
- ];
502
- }
503
-
504
- return [
505
- 'status' => 'success',
506
- 'message' => count($payments) . ' payment(s) retrieved.',
507
- 'data' => [
508
- 'payments' => $payments,
509
- 'hasMore' => $intents->has_more,
510
- ],
511
- ];
512
- } catch (\Stripe\Exception\ApiErrorException $e) {
513
- return [
514
- 'status' => 'error',
515
- 'message' => $e->getMessage(),
516
- 'data' => ['stripeCode' => $e->getStripeCode()],
517
- ];
518
- } catch (\Exception $e) {
519
- return [
520
- 'status' => 'error',
521
- 'message' => $e->getMessage(),
522
- 'data' => [],
523
- ];
524
- }
525
- }
526
- }
1
+ <?php
2
+
3
+ namespace Src\Service;
4
+
5
+ /**
6
+ * Stripe Payment Service
7
+ *
8
+ * Provides a clean wrapper around the stripe-php SDK for payment processing,
9
+ * customer management, subscriptions, product creation, and webhook handling.
10
+ *
11
+ * Required environment variables:
12
+ * STRIPE_SECRET_KEY — Your Stripe secret API key
13
+ * STRIPE_PUBLISHABLE_KEY — Your Stripe publishable key (for front-end reference)
14
+ * STRIPE_WEBHOOK_SECRET — Webhook endpoint signing secret
15
+ *
16
+ * @package Service
17
+ */
18
+ class Stripe
19
+ {
20
+ /**
21
+ * Stripe publishable key (exposed for front-end usage).
22
+ *
23
+ * @var string
24
+ */
25
+ private string $publishableKey;
26
+
27
+ /**
28
+ * Webhook signing secret.
29
+ *
30
+ * @var string
31
+ */
32
+ private string $webhookSecret;
33
+
34
+ /**
35
+ * Create a new Stripe service instance.
36
+ *
37
+ * Sets the API key globally for the Stripe SDK and stores
38
+ * the publishable key and webhook secret for later use.
39
+ */
40
+ public function __construct()
41
+ {
42
+ \Stripe\Stripe::setApiKey($_ENV['STRIPE_SECRET_KEY']);
43
+ $this->publishableKey = $_ENV['STRIPE_PUBLISHABLE_KEY'];
44
+ $this->webhookSecret = $_ENV['STRIPE_WEBHOOK_SECRET'];
45
+ }
46
+
47
+ /**
48
+ * Create a PaymentIntent.
49
+ *
50
+ * Use this for the recommended Stripe payment flow where the client
51
+ * confirms the payment on the front-end using the returned client_secret.
52
+ *
53
+ * @param float $amount Amount in major currency units (e.g. 19.99)
54
+ * @param string $currency ISO currency code (default: usd)
55
+ * @param array $metadata Arbitrary key-value metadata
56
+ * @return array Standardised response with clientSecret and paymentIntentId
57
+ */
58
+ public function createPaymentIntent(float $amount, string $currency = 'usd', array $metadata = []): array
59
+ {
60
+ try {
61
+ $intent = \Stripe\PaymentIntent::create([
62
+ 'amount' => (int) round($amount * 100),
63
+ 'currency' => strtolower($currency),
64
+ 'metadata' => $metadata,
65
+ 'automatic_payment_methods' => ['enabled' => true],
66
+ ]);
67
+
68
+ return [
69
+ 'status' => 'success',
70
+ 'message' => 'PaymentIntent created.',
71
+ 'data' => [
72
+ 'paymentIntentId' => $intent->id,
73
+ 'clientSecret' => $intent->client_secret,
74
+ 'amount' => $intent->amount,
75
+ 'currency' => $intent->currency,
76
+ 'status' => $intent->status,
77
+ ],
78
+ ];
79
+ } catch (\Stripe\Exception\ApiErrorException $e) {
80
+ return [
81
+ 'status' => 'error',
82
+ 'message' => $e->getMessage(),
83
+ 'data' => ['stripeCode' => $e->getStripeCode()],
84
+ ];
85
+ } catch (\Exception $e) {
86
+ return [
87
+ 'status' => 'error',
88
+ 'message' => $e->getMessage(),
89
+ 'data' => [],
90
+ ];
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Confirm an existing PaymentIntent server-side.
96
+ *
97
+ * Typically the client confirms, but this allows server-side confirmation
98
+ * for scenarios like off-session payments.
99
+ *
100
+ * @param string $paymentIntentId The PaymentIntent ID to confirm
101
+ * @return array Standardised response
102
+ */
103
+ public function confirmPayment(string $paymentIntentId): array
104
+ {
105
+ try {
106
+ $intent = \Stripe\PaymentIntent::retrieve($paymentIntentId);
107
+ $intent->confirm();
108
+
109
+ return [
110
+ 'status' => 'success',
111
+ 'message' => 'PaymentIntent confirmed.',
112
+ 'data' => [
113
+ 'paymentIntentId' => $intent->id,
114
+ 'status' => $intent->status,
115
+ 'amount' => $intent->amount,
116
+ 'currency' => $intent->currency,
117
+ ],
118
+ ];
119
+ } catch (\Stripe\Exception\ApiErrorException $e) {
120
+ return [
121
+ 'status' => 'error',
122
+ 'message' => $e->getMessage(),
123
+ 'data' => ['stripeCode' => $e->getStripeCode()],
124
+ ];
125
+ } catch (\Exception $e) {
126
+ return [
127
+ 'status' => 'error',
128
+ 'message' => $e->getMessage(),
129
+ 'data' => [],
130
+ ];
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Create a direct charge (legacy flow).
136
+ *
137
+ * Use createPaymentIntent() for new integrations. This method is provided
138
+ * for backward compatibility with token/source-based flows.
139
+ *
140
+ * @param float $amount Amount in major currency units
141
+ * @param string $source Token or source ID (tok_xxx or src_xxx)
142
+ * @param string $currency ISO currency code (default: usd)
143
+ * @param string $description Charge description
144
+ * @return array Standardised response with chargeId
145
+ */
146
+ public function charge(float $amount, string $source, string $currency = 'usd', string $description = ''): array
147
+ {
148
+ try {
149
+ $charge = \Stripe\Charge::create([
150
+ 'amount' => (int) round($amount * 100),
151
+ 'currency' => strtolower($currency),
152
+ 'source' => $source,
153
+ 'description' => $description,
154
+ ]);
155
+
156
+ return [
157
+ 'status' => 'success',
158
+ 'message' => 'Charge created successfully.',
159
+ 'data' => [
160
+ 'chargeId' => $charge->id,
161
+ 'amount' => $charge->amount,
162
+ 'currency' => $charge->currency,
163
+ 'paid' => $charge->paid,
164
+ 'status' => $charge->status,
165
+ 'receipt' => $charge->receipt_url,
166
+ ],
167
+ ];
168
+ } catch (\Stripe\Exception\CardException $e) {
169
+ return [
170
+ 'status' => 'error',
171
+ 'message' => $e->getMessage(),
172
+ 'data' => [
173
+ 'stripeCode' => $e->getStripeCode(),
174
+ 'declineCode' => $e->getDeclineCode(),
175
+ ],
176
+ ];
177
+ } catch (\Stripe\Exception\ApiErrorException $e) {
178
+ return [
179
+ 'status' => 'error',
180
+ 'message' => $e->getMessage(),
181
+ 'data' => ['stripeCode' => $e->getStripeCode()],
182
+ ];
183
+ } catch (\Exception $e) {
184
+ return [
185
+ 'status' => 'error',
186
+ 'message' => $e->getMessage(),
187
+ 'data' => [],
188
+ ];
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Refund a charge.
194
+ *
195
+ * @param string $chargeId The charge ID to refund
196
+ * @param float|null $amount Partial refund amount (null = full refund)
197
+ * @return array Standardised response with refundId
198
+ */
199
+ public function refund(string $chargeId, float $amount = null): array
200
+ {
201
+ try {
202
+ $params = ['charge' => $chargeId];
203
+
204
+ if ($amount !== null) {
205
+ $params['amount'] = (int) round($amount * 100);
206
+ }
207
+
208
+ $refund = \Stripe\Refund::create($params);
209
+
210
+ return [
211
+ 'status' => 'success',
212
+ 'message' => 'Refund processed successfully.',
213
+ 'data' => [
214
+ 'refundId' => $refund->id,
215
+ 'amount' => $refund->amount,
216
+ 'currency' => $refund->currency,
217
+ 'status' => $refund->status,
218
+ 'chargeId' => $refund->charge,
219
+ ],
220
+ ];
221
+ } catch (\Stripe\Exception\ApiErrorException $e) {
222
+ return [
223
+ 'status' => 'error',
224
+ 'message' => $e->getMessage(),
225
+ 'data' => ['stripeCode' => $e->getStripeCode()],
226
+ ];
227
+ } catch (\Exception $e) {
228
+ return [
229
+ 'status' => 'error',
230
+ 'message' => $e->getMessage(),
231
+ 'data' => [],
232
+ ];
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Create a Stripe Customer.
238
+ *
239
+ * Customers are required for subscriptions and saved payment methods.
240
+ *
241
+ * @param string $email Customer e-mail
242
+ * @param string $name Customer full name
243
+ * @param array $metadata Arbitrary key-value metadata
244
+ * @return array Standardised response with customerId
245
+ */
246
+ public function createCustomer(string $email, string $name = '', array $metadata = []): array
247
+ {
248
+ try {
249
+ $params = [
250
+ 'email' => $email,
251
+ 'metadata' => $metadata,
252
+ ];
253
+
254
+ if ($name !== '') {
255
+ $params['name'] = $name;
256
+ }
257
+
258
+ $customer = \Stripe\Customer::create($params);
259
+
260
+ return [
261
+ 'status' => 'success',
262
+ 'message' => 'Customer created successfully.',
263
+ 'data' => [
264
+ 'customerId' => $customer->id,
265
+ 'email' => $customer->email,
266
+ 'name' => $customer->name,
267
+ 'created' => date('Y-m-d H:i:s', $customer->created),
268
+ ],
269
+ ];
270
+ } catch (\Stripe\Exception\ApiErrorException $e) {
271
+ return [
272
+ 'status' => 'error',
273
+ 'message' => $e->getMessage(),
274
+ 'data' => ['stripeCode' => $e->getStripeCode()],
275
+ ];
276
+ } catch (\Exception $e) {
277
+ return [
278
+ 'status' => 'error',
279
+ 'message' => $e->getMessage(),
280
+ 'data' => [],
281
+ ];
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Create a subscription for a customer.
287
+ *
288
+ * The customer must already have a default payment method attached.
289
+ *
290
+ * @param string $customerId Stripe customer ID
291
+ * @param string $priceId Stripe price ID (price_xxx)
292
+ * @return array Standardised response with subscriptionId
293
+ */
294
+ public function subscribe(string $customerId, string $priceId): array
295
+ {
296
+ try {
297
+ $subscription = \Stripe\Subscription::create([
298
+ 'customer' => $customerId,
299
+ 'items' => [['price' => $priceId]],
300
+ 'payment_behavior' => 'default_incomplete',
301
+ 'expand' => ['latest_invoice.payment_intent'],
302
+ ]);
303
+
304
+ $latestInvoice = $subscription->latest_invoice;
305
+ $clientSecret = null;
306
+
307
+ if (is_object($latestInvoice) && $latestInvoice->payment_intent) {
308
+ $clientSecret = $latestInvoice->payment_intent->client_secret;
309
+ }
310
+
311
+ return [
312
+ 'status' => 'success',
313
+ 'message' => 'Subscription created.',
314
+ 'data' => [
315
+ 'subscriptionId' => $subscription->id,
316
+ 'status' => $subscription->status,
317
+ 'currentPeriodStart' => date('Y-m-d H:i:s', $subscription->current_period_start),
318
+ 'currentPeriodEnd' => date('Y-m-d H:i:s', $subscription->current_period_end),
319
+ 'clientSecret' => $clientSecret,
320
+ ],
321
+ ];
322
+ } catch (\Stripe\Exception\ApiErrorException $e) {
323
+ return [
324
+ 'status' => 'error',
325
+ 'message' => $e->getMessage(),
326
+ 'data' => ['stripeCode' => $e->getStripeCode()],
327
+ ];
328
+ } catch (\Exception $e) {
329
+ return [
330
+ 'status' => 'error',
331
+ 'message' => $e->getMessage(),
332
+ 'data' => [],
333
+ ];
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Cancel a subscription.
339
+ *
340
+ * Cancels immediately. For end-of-period cancellation, modify the
341
+ * subscription's cancel_at_period_end flag instead.
342
+ *
343
+ * @param string $subscriptionId Stripe subscription ID
344
+ * @return array Standardised response
345
+ */
346
+ public function cancelSubscription(string $subscriptionId): array
347
+ {
348
+ try {
349
+ $subscription = \Stripe\Subscription::retrieve($subscriptionId);
350
+ $subscription->cancel();
351
+
352
+ return [
353
+ 'status' => 'success',
354
+ 'message' => 'Subscription cancelled.',
355
+ 'data' => [
356
+ 'subscriptionId' => $subscription->id,
357
+ 'status' => $subscription->status,
358
+ 'canceledAt' => $subscription->canceled_at
359
+ ? date('Y-m-d H:i:s', $subscription->canceled_at)
360
+ : null,
361
+ ],
362
+ ];
363
+ } catch (\Stripe\Exception\ApiErrorException $e) {
364
+ return [
365
+ 'status' => 'error',
366
+ 'message' => $e->getMessage(),
367
+ 'data' => ['stripeCode' => $e->getStripeCode()],
368
+ ];
369
+ } catch (\Exception $e) {
370
+ return [
371
+ 'status' => 'error',
372
+ 'message' => $e->getMessage(),
373
+ 'data' => [],
374
+ ];
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Create a Product and its associated Price in one call.
380
+ *
381
+ * Simplifies the two-step Stripe flow of creating a product first,
382
+ * then attaching a recurring or one-time price to it.
383
+ *
384
+ * @param string $name Product name
385
+ * @param float $price Unit price in major currency units
386
+ * @param string $currency ISO currency code (default: usd)
387
+ * @return array Standardised response with productId and priceId
388
+ */
389
+ public function createProduct(string $name, float $price, string $currency = 'usd'): array
390
+ {
391
+ try {
392
+ $product = \Stripe\Product::create([
393
+ 'name' => $name,
394
+ ]);
395
+
396
+ $priceObj = \Stripe\Price::create([
397
+ 'product' => $product->id,
398
+ 'unit_amount' => (int) round($price * 100),
399
+ 'currency' => strtolower($currency),
400
+ ]);
401
+
402
+ return [
403
+ 'status' => 'success',
404
+ 'message' => 'Product and price created.',
405
+ 'data' => [
406
+ 'productId' => $product->id,
407
+ 'priceId' => $priceObj->id,
408
+ 'name' => $product->name,
409
+ 'amount' => $priceObj->unit_amount,
410
+ 'currency' => $priceObj->currency,
411
+ ],
412
+ ];
413
+ } catch (\Stripe\Exception\ApiErrorException $e) {
414
+ return [
415
+ 'status' => 'error',
416
+ 'message' => $e->getMessage(),
417
+ 'data' => ['stripeCode' => $e->getStripeCode()],
418
+ ];
419
+ } catch (\Exception $e) {
420
+ return [
421
+ 'status' => 'error',
422
+ 'message' => $e->getMessage(),
423
+ 'data' => [],
424
+ ];
425
+ }
426
+ }
427
+
428
+ /**
429
+ * Verify and parse a Stripe webhook event.
430
+ *
431
+ * Validates the webhook signature using the signing secret and returns
432
+ * the parsed event data. Call this from your webhook endpoint handler.
433
+ *
434
+ * @param string $payload Raw request body (file_get_contents('php://input'))
435
+ * @param string $signature Stripe-Signature header value
436
+ * @return array Standardised response with event type and data
437
+ */
438
+ public function webhook(string $payload, string $signature): array
439
+ {
440
+ try {
441
+ $event = \Stripe\Webhook::constructEvent(
442
+ $payload,
443
+ $signature,
444
+ $this->webhookSecret
445
+ );
446
+
447
+ return [
448
+ 'status' => 'success',
449
+ 'message' => 'Webhook verified.',
450
+ 'data' => [
451
+ 'eventId' => $event->id,
452
+ 'eventType' => $event->type,
453
+ 'created' => date('Y-m-d H:i:s', $event->created),
454
+ 'object' => $event->data->object->toArray(),
455
+ ],
456
+ ];
457
+ } catch (\Stripe\Exception\SignatureVerificationException $e) {
458
+ return [
459
+ 'status' => 'error',
460
+ 'message' => 'Webhook signature verification failed: ' . $e->getMessage(),
461
+ 'data' => [],
462
+ ];
463
+ } catch (\UnexpectedValueException $e) {
464
+ return [
465
+ 'status' => 'error',
466
+ 'message' => 'Invalid webhook payload: ' . $e->getMessage(),
467
+ 'data' => [],
468
+ ];
469
+ } catch (\Exception $e) {
470
+ return [
471
+ 'status' => 'error',
472
+ 'message' => $e->getMessage(),
473
+ 'data' => [],
474
+ ];
475
+ }
476
+ }
477
+
478
+ /**
479
+ * List recent PaymentIntents.
480
+ *
481
+ * @param int $limit Number of results to return (max 100, default 10)
482
+ * @return array Standardised response with array of payment intents
483
+ */
484
+ public function listPayments(int $limit = 10): array
485
+ {
486
+ try {
487
+ $intents = \Stripe\PaymentIntent::all([
488
+ 'limit' => min($limit, 100),
489
+ ]);
490
+
491
+ $payments = [];
492
+ foreach ($intents->data as $intent) {
493
+ $payments[] = [
494
+ 'paymentIntentId' => $intent->id,
495
+ 'amount' => $intent->amount,
496
+ 'currency' => $intent->currency,
497
+ 'status' => $intent->status,
498
+ 'created' => date('Y-m-d H:i:s', $intent->created),
499
+ 'description' => $intent->description,
500
+ 'metadata' => $intent->metadata->toArray(),
501
+ ];
502
+ }
503
+
504
+ return [
505
+ 'status' => 'success',
506
+ 'message' => count($payments) . ' payment(s) retrieved.',
507
+ 'data' => [
508
+ 'payments' => $payments,
509
+ 'hasMore' => $intents->has_more,
510
+ ],
511
+ ];
512
+ } catch (\Stripe\Exception\ApiErrorException $e) {
513
+ return [
514
+ 'status' => 'error',
515
+ 'message' => $e->getMessage(),
516
+ 'data' => ['stripeCode' => $e->getStripeCode()],
517
+ ];
518
+ } catch (\Exception $e) {
519
+ return [
520
+ 'status' => 'error',
521
+ 'message' => $e->getMessage(),
522
+ 'data' => [],
523
+ ];
524
+ }
525
+ }
526
+ }