From 60de6ae998d196b137f90bc7796a9fc899ddbb07 Mon Sep 17 00:00:00 2001 From: Jepp9350 <2jepp9350@gmail.com> Date: Tue, 22 Apr 2025 10:17:06 +0200 Subject: [PATCH] Add Stripe payment capture functionality Introduced methods to handle Stripe payment capture and updated related classes and endpoints. Refactored constants for payment processors and integrated order collections to reflect Stripe payments. Ensured robust error handling and session validation for payment operations. --- .../stripe_endpoint_payment_intents.php | 6 +- .../objects/collected_order_invoices_o.php | 40 +++++++++++- services/nginx/app/objects/orders_o.php | 16 +++++ services/nginx/app/routes/ordersRoute.php | 65 ++++++++++++++++++- 4 files changed, 121 insertions(+), 6 deletions(-) diff --git a/services/nginx/app/modules/stripe/endpoints/stripe_endpoint_payment_intents.php b/services/nginx/app/modules/stripe/endpoints/stripe_endpoint_payment_intents.php index a2dcd2d4..453014da 100644 --- a/services/nginx/app/modules/stripe/endpoints/stripe_endpoint_payment_intents.php +++ b/services/nginx/app/modules/stripe/endpoints/stripe_endpoint_payment_intents.php @@ -26,13 +26,15 @@ class stripe_endpoint_payment_intents /** * Get a payment intent by ID * @param string $id + * @param array|null $params + * @param array|null $opts * @return PaymentIntent * @throws ApiErrorException * @throws Exception */ - public function get(string $id): PaymentIntent + public function get(string $id, array $params = null, array $opts = null): PaymentIntent { - return self::getClient()->paymentIntents->retrieve($id); + return self::getClient()->paymentIntents->retrieve($id, $params, $opts); } /** diff --git a/services/nginx/app/objects/collected_order_invoices_o.php b/services/nginx/app/objects/collected_order_invoices_o.php index 7e6d3f6c..117766bd 100644 --- a/services/nginx/app/objects/collected_order_invoices_o.php +++ b/services/nginx/app/objects/collected_order_invoices_o.php @@ -8,6 +8,10 @@ use classes\object_property; use Exception; use traits\db_object_t; +const ECONOMIC_PROCESSOR = 1; +const STRIPE_PROCESSOR = 2; +const OTHER_PROCESSOR = 3; + class collected_order_invoices_o extends db { use db_object_t; @@ -30,9 +34,9 @@ class collected_order_invoices_o extends db * @var array|string[] */ public array $processors = [ - 1 => 'E-conomic', - 2 => 'Stripe', - 3 => 'Other, without tracking', + ECONOMIC_PROCESSOR => 'E-conomic', + STRIPE_PROCESSOR => 'Stripe', + OTHER_PROCESSOR => 'Other, without tracking', ]; public function structure(): void @@ -965,4 +969,34 @@ class collected_order_invoices_o extends db { return $price * 1.20; } + + /** + * Set the invoice collection as paid with Stripe + * @throws Exception If the request was not successful + * @throws Exception If the invoice collection is not set + * @throws Exception If the invoice collection is already closed + * @throws Exception If the invoice collection is already booked + */ + public function paidWithStripe(string $stripe_payment_intent_id): void + { + // Require the invoice collection to be selected + self::requireSelected(); + // Require the invoice collection to be open + self::requireOpen(); + // Require the processor to be Stripe (or null) + if (!empty($this->processor->value()) && $this->processor->value() != 2) { + throw new Exception('Invoice collection is not paid with Stripe'); + } + // Set the stripe payment intent id + $this->external_id->set($stripe_payment_intent_id); + // Set the processor to Stripe + $this->processor->set(STRIPE_PROCESSOR); + // Set the closed at date (if not already set) + if (empty($this->closed_at->value())) { + $this->closed_at->set(date('Y-m-d H:i:s')); + } + + // Object changed + self::objectChanged(); + } } \ No newline at end of file diff --git a/services/nginx/app/objects/orders_o.php b/services/nginx/app/objects/orders_o.php index 6fc61c43..42bf1c79 100644 --- a/services/nginx/app/objects/orders_o.php +++ b/services/nginx/app/objects/orders_o.php @@ -515,4 +515,20 @@ class orders_o extends db } return $count; } + + /** + * Get the order collection for the order + * @return collected_order_invoices_o The order collection + * @throws Exception If the order is not selected + */ + public function getOrderCollection(): collected_order_invoices_o + { + self::requireSelected(); + $order_collection = new collected_order_invoices_o(); + $order_collection->select($this->invoice_collection_id->value()); + if (!$order_collection->exists()) { + throw new Exception('Order collection not found'); + } + return $order_collection; + } } \ No newline at end of file diff --git a/services/nginx/app/routes/ordersRoute.php b/services/nginx/app/routes/ordersRoute.php index fbbdeb9a..a07d411a 100644 --- a/services/nginx/app/routes/ordersRoute.php +++ b/services/nginx/app/routes/ordersRoute.php @@ -403,7 +403,10 @@ class ordersRoute $stripe = new stripe(); try { $payment_intent = $stripe->payment_intents->get( - $stripe_payment_intents->payment_intent_id->value() + $stripe_payment_intents->payment_intent_id->value(), + [ + //'expand' => ['latest_charge'], // This is used to get the latest charge, that can be used to check if the payment has been refunded. + ] ); } catch (\Stripe\Exception\InvalidRequestException $e) { $response->error('Payment intent not found', 400); @@ -465,6 +468,66 @@ class ordersRoute } }); + $this->post('/orders/module/stripe/payment_intent/capture', function () { + // Require the user to be logged in + global $response; + $this->requirePermission('confirm_payment_intent'); + // Get the user object + $user = (new authentication())->get_user(); + // Check if the request was successful + if ($user) { + // Get the post data + $data = json_decode(file_get_contents('php://input'), true); + // Check if the required fields are set + if (!isset($data['id'])) { + $response->error('ID is required', 400); + } + // Get the current order + $order = (new orders_o())->getOrderById((int)$data['id']); + // Check if the order exists + if (!$order->exists()) { + $response->error('Order not found', 400); + } + // Check if the order has a payment intent + $stripe_payment_intents = new stripe_payment_intents_o(); + if (!$stripe_payment_intents->doesOrderHavePaymentIntent((int)$order->id)) { + $response->error('Order does not have a payment intent', 400); + } + $stripe_payment_intents->selectOrderPaymentIntent((int)$order->id); + // Confirm the payment intent + $stripe = new stripe(); + try { + $paymentIntent = $stripe->payment_intents->capture( + $stripe_payment_intents->payment_intent_id->value(), + [] // Since we are capturing the payment, we don't need to pass any data + ); + // Log the incident + (new logs_o())->add('orders', $order->department_id->value(), 1, $user->id, 'CONFIRM_PAYMENT_INTENT', 'Successfully confirmed a payment intent (ID: ' . $data['id'] . ')'); + // Check if the payment intent was successful + if ($paymentIntent->status !== 'succeeded') { + $response->error('Payment intent not successful', 400); + } else { + // Update the order collection to reflect the payment + $order_collection = $order->getOrderCollection(); + $order_collection->paidWithStripe($paymentIntent->id); + } + // Return a success message + $response->success($paymentIntent->toJSON()); + } catch (\Stripe\Exception\InvalidRequestException $e) { + $response->error('Payment intent not found', 400); + } + } else { + // Log the incident + (new logs_o())->add('orders', 'global', 1, 0, 'CONFIRM_PAYMENT_INTENT', 'No user found, or invalid session'); + // Return an error + $response->error('Invalid session', 400); + } + }, + [ + 'confirm_payment_intent' => 'Confirm a payment intent' + ] + ); + $this->post('/orders/module/stripe/debug/simulate_payment', function () { // Require the user to be logged in global $response;