PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.27
Booktics – Appointment Booking Calendar for Service Businesses v1.0.27
1.0.29 1.0.28 1.0.27 1.0.26 1.0.25 1.0.24 1.0.23 1.0.22 1.0.21 1.0.20 1.0.19 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.2 1.0.3 All 31 releases
booktics / core / module / stripe-addon / service / stripe-payment-method.php

stripe-payment-method.php in Booktics – Appointment Booking Calendar for Service Businesses 1.0.27, at core/module/stripe-addon/service/stripe-payment-method.php

246 lines 8.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Booktics\Module\Stripe_Addon\Service;
4
5 use Booktics\Models\Order_Model;
6 use Booktics\Module\Stripe_Addon\Handler\Stripe_Handler;
7 use Booktics\Payment\Payment_Methods\Abstract_Payment_Method;
8 use Booktics\Payment\Payment_Status;
9 use Exception;
10 use Stripe\PaymentIntent;
11 use Stripe\Refund;
12 use Stripe\Stripe;
13
14 class Stripe_Payment_Method extends Abstract_Payment_Method {
15
16 /**
17 * Initialize the Stripe SDK with the secret key.
18 *
19 * @return void
20 */
21 private function init_stripe_sdk() {
22 Stripe::setApiKey( Stripe_Handler::get_stripe_secret_key() );
23 }
24
25 /**
26 * Get the Stripe connect account URL.
27 *
28 * @return string The connect URL
29 */
30 public static function connect_account( $type = 'test' ) {
31 return ( new Stripe_Handler() )->get_connect_url( $type );
32 }
33
34 /**
35 * Get the Stripe account ID from settings.
36 *
37 * @return string|null The account ID
38 */
39 public static function account_id() {
40 return booktics_get_option( 'stripe_account_id' );
41 }
42
43 /**
44 * Get the Stripe account type from settings.
45 *
46 * @return string|null The account type
47 */
48 public static function account_type() {
49 return booktics_get_option( 'stripe_account_type', 'test');
50 }
51
52 /**
53 * Process a payment through Stripe.
54 *
55 * @param Order_Model $order The order to process payment for
56 *
57 * @return array The payment processing result
58 */
59 public function process_payment( $order ): array {
60 $this->init_stripe_sdk();
61
62 $amount = $order->total ?? 0;
63 $currency = $order->currency ?? strtolower( booktics_get_option( 'currency', 'USD' ) );
64 try {
65 $intent = PaymentIntent::create( [
66 'amount' => $amount * 100,
67 'currency' => $currency,
68 'description' => $order->order_no ?? '',
69 'automatic_payment_methods' => [
70 'enabled' => true,
71 ],
72 'metadata' => [
73 'order_id' => $order->id,
74 ],
75 ] );
76
77 return [
78 'status' => 'success',
79 'payment_intent' => $intent->id,
80 'client_secret' => $intent->client_secret,
81 ];
82 } catch ( Exception $e ) {
83 if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
84 error_log($e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
85 }
86 return [
87 'status' => 'error',
88 'message' => $e->getMessage(),
89 ];
90 }
91 }
92
93 /**
94 * Validate the payment data.
95 *
96 * @return array Validation result
97 */
98 public function validate_payment_data(): array {
99 $client_secret = $this->request_data['client_secret'] ?? null;
100
101 if ( empty( $client_secret ) ) {
102 return [
103 'status' => 'error',
104 'message' => __( 'Missing client_secret for Stripe payment.', 'booktics' )
105 ];
106 }
107
108 return [ 'status' => 'success' ];
109 }
110
111 /**
112 * Get the payment status from Stripe.
113 *
114 * @param Order_Model $order The order to check status for
115 *
116 * @return string The payment status
117 */
118 public function get_payment_status( $order ): string {
119 $this->init_stripe_sdk();
120
121 $payment_intent_id = $order->payment_intent_id ?? null;
122
123 if ( ! $payment_intent_id ) {
124 return Payment_Status::FAILED;
125 }
126
127 try {
128 $intent = PaymentIntent::retrieve( $payment_intent_id );
129
130 // Map Stripe status to Booktics status
131 switch ($intent->status) {
132 case 'succeeded':
133 return Payment_Status::COMPLETED;
134 case 'processing':
135 case 'requires_payment_method':
136 case 'requires_confirmation':
137 case 'requires_action':
138 case 'requires_capture':
139 return Payment_Status::PROCESSING;
140 case 'canceled':
141 return Payment_Status::FAILED;
142 case 'requires_refund':
143 case 'refunded':
144 return Payment_Status::REFUNDED;
145 default:
146 return Payment_Status::FAILED;
147 }
148 } catch ( Exception $e ) {
149 return Payment_Status::FAILED;
150 }
151 }
152
153 /**
154 * Decide the reservation action for a stale unpaid order using Stripe's raw
155 * PaymentIntent lifecycle state.
156 *
157 * Unlike get_payment_status(), which collapses several distinct Stripe states
158 * into "processing", this reads the raw status so a customer who is mid-payment
159 * (e.g. on a bank 3D-Secure / OTP screen) is never canceled underneath them,
160 * even with a short reservation timeout.
161 *
162 * @param Order_Model $order The order whose held slot is being evaluated.
163 *
164 * @return string One of the RESERVATION_* constants.
165 */
166 public function resolve_reservation_action( $order ): string {
167 $this->init_stripe_sdk();
168
169 $payment_intent_id = $order->payment_intent_id ?? null;
170
171 if ( ! $payment_intent_id ) {
172 // No intent was ever created — nothing charged, safe to free the slot.
173 return self::RESERVATION_RELEASE;
174 }
175
176 try {
177 $intent = PaymentIntent::retrieve( $payment_intent_id );
178
179 switch ( $intent->status ) {
180 case 'succeeded':
181 // Money was captured; only the client-side confirmation was lost.
182 return self::RESERVATION_REPAIR;
183
184 case 'requires_payment_method':
185 case 'canceled':
186 // Customer never provided a working payment method or the intent
187 // was canceled. The booking is genuinely abandoned — release it.
188 return self::RESERVATION_RELEASE;
189
190 case 'processing':
191 case 'requires_action':
192 case 'requires_confirmation':
193 case 'requires_capture':
194 // Payment is genuinely still in progress (bank verification, async
195 // capture, etc.). Keep the slot and re-check on a later sweep.
196 return self::RESERVATION_WAIT;
197
198 default:
199 // Unknown/unexpected state — do not cancel on uncertainty.
200 return self::RESERVATION_WAIT;
201 }
202 } catch ( Exception $e ) {
203 // Transient API error: err on the safe side and keep the booking held.
204 return self::RESERVATION_WAIT;
205 }
206 }
207
208 /**
209 * Process a refund through Stripe.
210 *
211 * @param Order_Model $order The order to process refund for
212 *
213 * @return array The refund processing result
214 */
215 public function process_refund( $order ) {
216 $this->init_stripe_sdk();
217 $payment_intent_id = $order->payment_intent_id ?? null;
218
219 if ( ! $payment_intent_id ) {
220 return [
221 'status' => 'error',
222 'message' => __( 'Missing payment_intent_id for Stripe payment.', 'booktics' )
223 ];
224 }
225 $amount = $order->total ?? 0;
226 try {
227 $refund = Refund::create( [
228 'payment_intent' => $payment_intent_id,
229 'amount' => $amount ? intval( $amount * 100 ) : null,
230 'stripe_account' => self::account_id(),
231 ] );
232
233 return [
234 'status' => 'success',
235 'refund_id' => $refund->id,
236 'refund_status' => $refund->status,
237 'message' => __( 'Refund initiated successfully.', 'booktics' ),
238 ];
239 } catch ( \Exception $e ) {
240 return [
241 'status' => 'error',
242 'message' => $e->getMessage(),
243 ];
244 }
245 }
246 }