PluginProbe
Timetics – Appointment Booking Calendar & Scheduling / 1.0.64
Timetics – Appointment Booking Calendar & Scheduling v1.0.64
1.0.64 1.0.62 1.0.63 1.0.61 1.0.60 1.0.59 1.0.58 1.0.57 1.0.56 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.19 1.0.2 1.0.20 1.0.21 All 65 releases
← All changes | core/integrations/stripe/stripe-payment.php +52 -0 1.0.61 → 1.0.64 View file →
@@ -5,8 +5,10 @@
5 5 * @package Timetics
6 6 */
7 7 namespace Timetics\Core\Integrations\Stripe;
8 8
9 +defined( 'ABSPATH' ) || exit;
10 +
9 11 /**
10 12 * Class StripePayment
11 13 */
12 14 class StripePayment {
@@ -187,6 +189,56 @@
187 189 return new \WP_Error( 'timetics_stripe_invalid_response', __( 'Unexpected Stripe response.', 'timetics' ) );
188 190 }
189 191
190 192 return $body;
193 + }
194 +
195 + /**
196 + * Cancel a Stripe PaymentIntent. Used by the unpaid-booking cleanup sweep
197 + * so a card can't be charged after we've already released the slot.
198 + * Stripe refuses this if the intent already succeeded — that failure is
199 + * the caller's signal to leave the booking alone instead of cancelling it.
200 + *
201 + * @param string $intent_id PaymentIntent id (pi_...).
202 + *
203 + * @return array|\WP_Error Decoded Stripe response, or WP_Error on failure.
204 + */
205 + public function cancel_payment_intent( $intent_id ) {
206 + $intent_id = is_string( $intent_id ) ? trim( $intent_id ) : '';
207 +
208 + if ( '' === $intent_id || strpos( $intent_id, 'pi_' ) !== 0 ) {
209 + return new \WP_Error( 'timetics_stripe_invalid_intent', __( 'Invalid Stripe payment intent id.', 'timetics' ) );
210 + }
211 +
212 + $secret = timetics_get_option( 'stripe_secret_key' );
213 +
214 + if ( empty( $secret ) ) {
215 + return new \WP_Error( 'timetics_stripe_missing_secret', __( 'Stripe secret key is not configured.', 'timetics' ) );
216 + }
217 +
218 + $response = wp_remote_post(
219 + $this->payment_intent_url . '/' . rawurlencode( $intent_id ) . '/cancel',
220 + [
221 + 'headers' => [
222 + 'Authorization' => 'Bearer ' . $secret,
223 + ],
224 + 'timeout' => 15,
225 + ]
226 + );
227 +
228 + if ( is_wp_error( $response ) ) {
229 + return $response;
230 + }
231 +
232 + $code = (int) wp_remote_retrieve_response_code( $response );
233 + $body = json_decode( wp_remote_retrieve_body( $response ), true );
234 +
235 + if ( $code < 200 || $code >= 300 ) {
236 + $message = is_array( $body ) && ! empty( $body['error']['message'] )
237 + ? $body['error']['message']
238 + : __( 'Stripe payment intent cancellation failed.', 'timetics' );
239 + return new \WP_Error( 'timetics_stripe_cancel_failed', $message, [ 'status' => $code ] );
240 + }
241 +
242 + return is_array( $body ) ? $body : [];
191 243 }
192 244 }