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 +190 -5 1.0.10 → 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 {
@@ -25,23 +27,35 @@
25 27 * @return array
26 28 */
27 29 public function create_payment( $args = [] ) {
28 30 $defaults = [
29 - 'amount' => '',
30 - 'currency' => '',
31 - 'payment_method_types[]' => 'card',
31 + 'amount' => '',
32 + 'currency' => '',
33 + 'metadata' => [],
32 34 ];
33 35
34 - $args = wp_parse_args( $args, $defaults );
36 + $args = wp_parse_args( $args, $defaults );
37 +
38 + $metadata = is_array( $args['metadata'] ) ? $args['metadata'] : [];
39 + unset( $args['metadata'] );
40 +
35 41 $url = $this->payment_intent_url;
36 42 $secret = timetics_get_option( 'stripe_secret_key' );
43 + $body = build_query( $args ) . '&automatic_payment_methods[enabled]=true';
37 44
45 + foreach ( $metadata as $meta_key => $meta_value ) {
46 + if ( $meta_value === '' || $meta_value === null ) {
47 + continue;
48 + }
49 + $body .= '&metadata[' . rawurlencode( (string) $meta_key ) . ']=' . rawurlencode( (string) $meta_value );
50 + }
51 +
38 52 $params = [
39 53 'headers' => [
40 54 'Authorization' => 'Bearer ' . $secret,
41 55 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8',
42 56 ],
43 - 'body' => build_query( $args ),
57 + 'body' => $body,
44 58 ];
45 59
46 60 $response = wp_remote_post( $url, $params );
47 61
@@ -54,6 +68,177 @@
54 68 */
55 69 do_action( 'timetics/integrations/stripe/create_payment', $response );
56 70
57 71 return $response;
72 + }
73 +
74 + /**
75 + * Update an existing Stripe PaymentIntent's metadata server-side.
76 + * Used to bind a PaymentIntent to a booking after the booking is created
77 + * but before the customer confirms the payment.
78 + *
79 + * @param string $intent_id PaymentIntent id (pi_...).
80 + * @param array $metadata Key/value pairs to set on the intent.
81 + *
82 + * @return array|\WP_Error Decoded Stripe response, or WP_Error on failure.
83 + */
84 + public function update_payment_intent( $intent_id, $metadata = [] ) {
85 + $intent_id = is_string( $intent_id ) ? trim( $intent_id ) : '';
86 +
87 + if ( '' === $intent_id || strpos( $intent_id, 'pi_' ) !== 0 ) {
88 + return new \WP_Error( 'timetics_stripe_invalid_intent', __( 'Invalid Stripe payment intent id.', 'timetics' ) );
89 + }
90 +
91 + $secret = timetics_get_option( 'stripe_secret_key' );
92 +
93 + if ( empty( $secret ) ) {
94 + return new \WP_Error( 'timetics_stripe_missing_secret', __( 'Stripe secret key is not configured.', 'timetics' ) );
95 + }
96 +
97 + $body_parts = [];
98 + foreach ( (array) $metadata as $meta_key => $meta_value ) {
99 + if ( $meta_value === '' || $meta_value === null ) {
100 + continue;
101 + }
102 + $body_parts[] = 'metadata[' . rawurlencode( (string) $meta_key ) . ']=' . rawurlencode( (string) $meta_value );
103 + }
104 +
105 + if ( empty( $body_parts ) ) {
106 + return new \WP_Error( 'timetics_stripe_empty_metadata', __( 'No metadata provided.', 'timetics' ) );
107 + }
108 +
109 + $response = wp_remote_post(
110 + $this->payment_intent_url . '/' . rawurlencode( $intent_id ),
111 + [
112 + 'headers' => [
113 + 'Authorization' => 'Bearer ' . $secret,
114 + 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8',
115 + ],
116 + 'body' => implode( '&', $body_parts ),
117 + 'timeout' => 15,
118 + ]
119 + );
120 +
121 + if ( is_wp_error( $response ) ) {
122 + return $response;
123 + }
124 +
125 + $code = (int) wp_remote_retrieve_response_code( $response );
126 + $body = json_decode( wp_remote_retrieve_body( $response ), true );
127 +
128 + if ( $code < 200 || $code >= 300 ) {
129 + $message = is_array( $body ) && ! empty( $body['error']['message'] )
130 + ? $body['error']['message']
131 + : __( 'Stripe payment intent update failed.', 'timetics' );
132 + return new \WP_Error( 'timetics_stripe_update_failed', $message, [ 'status' => $code ] );
133 + }
134 +
135 + if ( ! is_array( $body ) ) {
136 + return new \WP_Error( 'timetics_stripe_invalid_response', __( 'Unexpected Stripe response.', 'timetics' ) );
137 + }
138 +
139 + return $body;
140 + }
141 +
142 + /**
143 + * Retrieve a Stripe PaymentIntent server-side for verification.
144 + *
145 + * @param string $intent_id PaymentIntent id (pi_...).
146 + *
147 + * @return array|\WP_Error Decoded Stripe response, or WP_Error on transport / API failure.
148 + */
149 + public function retrieve_payment_intent( $intent_id ) {
150 + $intent_id = is_string( $intent_id ) ? trim( $intent_id ) : '';
151 +
152 + if ( '' === $intent_id || strpos( $intent_id, 'pi_' ) !== 0 ) {
153 + return new \WP_Error( 'timetics_stripe_invalid_intent', __( 'Invalid Stripe payment intent id.', 'timetics' ) );
154 + }
155 +
156 + $secret = timetics_get_option( 'stripe_secret_key' );
157 +
158 + if ( empty( $secret ) ) {
159 + return new \WP_Error( 'timetics_stripe_missing_secret', __( 'Stripe secret key is not configured.', 'timetics' ) );
160 + }
161 +
162 + $url = $this->payment_intent_url . '/' . rawurlencode( $intent_id );
163 +
164 + $response = wp_remote_get(
165 + $url,
166 + [
167 + 'headers' => [
168 + 'Authorization' => 'Bearer ' . $secret,
169 + ],
170 + 'timeout' => 15,
171 + ]
172 + );
173 +
174 + if ( is_wp_error( $response ) ) {
175 + return $response;
176 + }
177 +
178 + $code = (int) wp_remote_retrieve_response_code( $response );
179 + $body = json_decode( wp_remote_retrieve_body( $response ), true );
180 +
181 + if ( $code < 200 || $code >= 300 ) {
182 + $message = is_array( $body ) && ! empty( $body['error']['message'] )
183 + ? $body['error']['message']
184 + : __( 'Stripe payment intent retrieval failed.', 'timetics' );
185 + return new \WP_Error( 'timetics_stripe_retrieve_failed', $message, [ 'status' => $code ] );
186 + }
187 +
188 + if ( ! is_array( $body ) ) {
189 + return new \WP_Error( 'timetics_stripe_invalid_response', __( 'Unexpected Stripe response.', 'timetics' ) );
190 + }
191 +
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 : [];
58 243 }
59 244 }