PluginProbe
Timetics – Appointment Booking Calendar & Scheduling / 1.0.62
Timetics – Appointment Booking Calendar & Scheduling v1.0.62
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 1.0.22 All 64 releases
timetics / core / integrations / stripe / api-stripe.php

api-stripe.php in Timetics – Appointment Booking Calendar & Scheduling 1.0.62, at core/integrations/stripe/api-stripe.php

188 lines 5.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Stripe payment api
4 *
5 * @package Timetics
6 */
7 namespace Timetics\Core\Integrations\Stripe;
8
9 defined( 'ABSPATH' ) || exit;
10
11 use Timetics\Base\Api;
12 use Timetics\Core\Bookings\Booking;
13 use Timetics\Utils\Singleton;
14 use WP_HTTP_Response;
15
16 /**
17 * Class Api Stripe
18 */
19 class Api_Stripe extends Api {
20 use Singleton;
21
22 /**
23 * Store api namespace
24 *
25 * @var string
26 */
27 protected $namespace = 'timetics/v1';
28
29 /**
30 * Store rest base
31 *
32 * @var string
33 */
34 protected $rest_base = 'stripe';
35
36 /**
37 * Register rest routes
38 *
39 * @return void
40 */
41 public function register_routes() {
42 /**
43 * Register route
44 *
45 * @var void
46 */
47 register_rest_route(
48 $this->namespace, $this->rest_base . '/payment', [
49 [
50 'methods' => \WP_REST_Server::CREATABLE,
51 'callback' => [ $this, 'create_payment' ],
52 'permission_callback' => function () {
53 return true;
54 },
55 ],
56 ]
57 );
58 }
59
60 /**
61 * Create stripe payment
62 *
63 * @param WP_Rest_Request $request
64 *
65 * @return JSON
66 */
67 public function create_payment( $request ) {
68 if ( $this->is_rate_limited() ) {
69 return new WP_HTTP_Response(
70 [
71 'success' => 0,
72 'status_code' => 429,
73 'message' => __( 'Too many requests. Please try again later.', 'timetics' ),
74 ],
75 429
76 );
77 }
78
79 $data = json_decode( $request->get_body(), true );
80
81 $amount = ! empty( $data['amount'] ) ? floatval( $data['amount'] ) : 0;
82 $currency = ! empty( $data['currency'] ) ? sanitize_text_field( $data['currency'] ) : '';
83 $booking_id = ! empty( $data['booking_id'] ) ? absint( $data['booking_id'] ) : 0;
84 $token = ! empty( $data['security_token'] ) ? sanitize_text_field( $data['security_token'] ) : '';
85
86 $metadata = [];
87
88 // The card form (StripePayment.js) creates this PaymentIntent up front, before
89 // a booking exists, purely from the meeting's price — so booking_id/token are
90 // optional here. Binding happens later via bind_payment_intent(), and the booking
91 // can only be marked paid there after its security_token is verified. An intent
92 // created without a booking can never complete a payment, so this cannot be used
93 // to steal funds; it can only let a caller create inert PaymentIntents in Stripe.
94 if ( $booking_id > 0 && '' !== $token ) {
95 $booking = new Booking( $booking_id );
96
97 if ( ! $booking->is_booking() ) {
98 return new WP_HTTP_Response(
99 [
100 'success' => 0,
101 'status_code' => 404,
102 'message' => __( 'Invalid booking id.', 'timetics' ),
103 ],
104 404
105 );
106 }
107
108 $stored = (string) $booking->get_security_token();
109
110 if ( '' === $stored || ! hash_equals( $stored, $token ) ) {
111 return new WP_HTTP_Response(
112 [
113 'success' => 0,
114 'status_code' => 403,
115 'message' => __( 'Invalid booking token.', 'timetics' ),
116 ],
117 403
118 );
119 }
120
121 $metadata['booking_id'] = $booking_id;
122 $metadata['security_token'] = $stored;
123
124 // Once bound, trust the booking's own total over whatever the client sent.
125 $amount = (float) $booking->get_total();
126 }
127
128 // Sanity bounds — reject nonsense amounts regardless of binding.
129 if ( $amount <= 0 || $amount > 1000000 || ! preg_match( '/^[A-Za-z]{3}$/', (string) $currency ) ) {
130 return new WP_HTTP_Response(
131 [
132 'success' => 0,
133 'status_code' => 400,
134 'message' => __( 'Invalid amount or currency.', 'timetics' ),
135 ],
136 400
137 );
138 }
139
140 $payment = new StripePayment();
141
142 $payment = $payment->create_payment(
143 [
144 'amount' => $amount * 100,
145 'currency' => $currency,
146 'metadata' => $metadata,
147 ]
148 );
149
150 if ( is_wp_error( $payment ) ) {
151 $response = [
152 'success' => 0,
153 'status_code' => 403,
154 'message' => $payment->get_error_message(),
155 ];
156
157 return new WP_HTTP_Response( $response, 403 );
158 }
159
160 return rest_ensure_response( $payment );
161 }
162
163 /**
164 * Simple per-IP fixed-window limiter for the public payment-intent route.
165 *
166 * @return bool
167 */
168 private function is_rate_limited() {
169 $ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
170
171 if ( '' === $ip ) {
172 return false;
173 }
174
175 $key = 'tt_stripe_rl_' . md5( $ip );
176 $count = (int) get_transient( $key );
177
178 if ( $count >= 20 ) {
179 return true;
180 }
181
182 set_transient( $key, $count + 1, MINUTE_IN_SECONDS * 10 );
183
184 return false;
185 }
186 }
187
188