PluginProbe
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments / 3.6.1
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments v3.6.1
4.7.2 4.7.1 4.7.0 4.6.6 4.6.5 4.6.4 4.6.3 4.6.2 4.6.1 4.6.0 4.5.1 4.5.0 4.4.2 4.4.1 4.4.0 4.3.3 4.3.2 4.3.1 4.3.0 4.2.3 4.2.2 4.2.1 1.0.3 1.0.4 1.0.5 All 281 releases
← All changes | app/src/Controllers/Rest/VerificationCodeController.php +7 -162 4.7.23.6.1 View file →
@@ -16,15 +16,8 @@
16 16 */
17 17 protected $class = VerificationCode::class;
18 18
19 19 /**
20 - * Transient key prefix for the per-email resend window.
21 - *
22 - * @var string
23 - */
24 - const RESEND_TRANSIENT_PREFIX = 'sc_vcode_resend_';
25 -
26 - /**
27 20 * Create model.
28 21 *
29 22 * @param \WP_REST_Request $request Rest Request.
30 23 *
@@ -39,157 +32,24 @@
39 32 $user = $this->getUser( $request->get_param( 'login' ) );
40 33
41 34 // bail if no user.
42 35 if ( ! $user ) {
43 - return new \WP_Error( 'user_not_found', __( 'The user could not be found.', 'surecart' ), [ 'status' => 404 ] );
36 + return new \WP_Error( 'user_not_found', __( 'The user could not be found.', 'surecart' ) );
44 37 }
45 38
46 - $email = $user->user_email;
47 - $mode = $request->get_param( 'checkout_mode' );
48 -
49 - // A code was already sent and we're still inside the platform's resend
50 - // window. Resume that countdown instead of asking the platform again —
51 - // this is what stops duplicate verification emails on page reload, tab
52 - // switch, or re-entering the same email via "Change".
53 - $available_at = $this->getResendWindow( $email, $mode );
54 - if ( $available_at ) {
55 - return new VerificationCode(
56 - [
57 - 'email' => $email,
58 - 'email_sent' => false,
59 - 'resend_available_at' => $available_at,
60 - 'resend_available_in' => max( 0, $available_at - time() ),
61 - ]
62 - );
63 - }
64 -
65 - $created = $model->where( $request->get_query_params() )->create(
39 + return $model->where( $request->get_query_params() )->create(
66 40 [
67 - 'email' => $email,
41 + 'email' => $user->user_email,
68 42 ]
69 43 );
70 -
71 - if ( is_wp_error( $created ) ) {
72 - // Platform blocked the send as a duplicate. Mirror its backoff window
73 - // locally so the UI countdown stays accurate on the next request.
74 - $this->storeResendWindowFromError( $created, $email, $mode );
75 - return $created;
76 - }
77 -
78 - // Persist the platform-provided window so a reload or tab switch can
79 - // resume the countdown without triggering another send.
80 - $available_at = $this->normalizeToTimestamp( $created->resend_available_at ?? null );
81 - if ( $available_at ) {
82 - $this->setResendWindow( $email, $mode, $available_at );
83 - $created->resend_available_in = max( 0, $available_at - time() );
84 - }
85 - $created->email_sent = true;
86 -
87 - return $created;
88 44 }
89 45
90 46 /**
91 - * Build the transient key for an email + checkout mode.
92 - *
93 - * @param string $email Normalized against case so the key is stable.
94 - * @param string $mode Checkout mode (live/test) to avoid cross-mode collisions.
95 - *
96 - * @return string
97 - */
98 - public static function resendTransientKey( $email, $mode ) {
99 - return self::RESEND_TRANSIENT_PREFIX . md5( strtolower( $email ) . '|' . ( $mode ?: 'live' ) );
100 - }
101 -
102 - /**
103 - * Get the stored resend-available timestamp if it is still in the future.
104 - *
105 - * @param string $email Email address.
106 - * @param string $mode Checkout mode.
107 - *
108 - * @return int|false Unix timestamp, or false when no active window.
109 - */
110 - protected function getResendWindow( $email, $mode ) {
111 - $available_at = (int) get_transient( self::resendTransientKey( $email, $mode ) );
112 - return $available_at > time() ? $available_at : false;
113 - }
114 -
115 - /**
116 - * Store the resend-available timestamp, expiring the transient when it lapses.
117 - *
118 - * @param string $email Email address.
119 - * @param string $mode Checkout mode.
120 - * @param int $available_at Unix timestamp the next resend is allowed.
121 - *
122 - * @return void
123 - */
124 - protected function setResendWindow( $email, $mode, $available_at ) {
125 - $ttl = $available_at - time();
126 - if ( $ttl <= 0 ) {
127 - return;
128 - }
129 - set_transient( self::resendTransientKey( $email, $mode ), $available_at, $ttl );
130 - }
131 -
132 - /**
133 - * Clear the resend window for an email across both checkout modes.
134 - *
135 - * @param string $email Email address.
136 - *
137 - * @return void
138 - */
139 - protected function clearResendWindow( $email ) {
140 - foreach ( [ 'live', 'test' ] as $mode ) {
141 - delete_transient( self::resendTransientKey( $email, $mode ) );
142 - }
143 - }
144 -
145 - /**
146 - * Pull the backoff window out of a platform "blocked duplicate" error and store it.
147 - *
148 - * @param \WP_Error $error The translated platform error.
149 - * @param string $email Email address.
150 - * @param string $mode Checkout mode.
151 - *
152 - * @return void
153 - */
154 - protected function storeResendWindowFromError( $error, $email, $mode ) {
155 - foreach ( $error->get_error_codes() as $code ) {
156 - if ( 'verification_code.email.blocked_duplicate' !== $code ) {
157 - continue;
158 - }
159 - $data = $error->get_error_data( $code );
160 - $seconds = (int) ( $data['options']['seconds'] ?? 0 );
161 - if ( $seconds > 0 ) {
162 - $this->setResendWindow( $email, $mode, time() + $seconds );
163 - }
164 - return;
165 - }
166 - }
167 -
168 - /**
169 - * Normalize a platform timestamp (unix int or ISO 8601 string) to a unix timestamp.
170 - *
171 - * @param mixed $value Raw value from the platform.
172 - *
173 - * @return int|null
174 - */
175 - protected function normalizeToTimestamp( $value ) {
176 - if ( empty( $value ) ) {
177 - return null;
178 - }
179 - if ( is_numeric( $value ) ) {
180 - return (int) $value;
181 - }
182 - $parsed = strtotime( (string) $value );
183 - return $parsed ? $parsed : null;
184 - }
185 -
186 - /**
187 47 * Verify a verification code
188 48 *
189 49 * @param \WP_REST_Request $request Rest Request.
190 50 *
191 - * @return mixed|\WP_Error
51 + * @return \SureCart\Models\VerificationCode|\WP_Error
192 52 */
193 53 public function verify( \WP_REST_Request $request ) {
194 54 // run middleware.
195 55 $model = $this->middleware( new $this->class(), $request );
@@ -202,10 +62,9 @@
202 62 $user = $this->getUser( $request->get_param( 'login' ) );
203 63 if ( ! $user ) {
204 64 return new \WP_Error(
205 65 'invalid_email',
206 - __( 'There is no account with that username or email address.', 'surecart' ),
207 - [ 'status' => 404 ]
66 + __( 'There is no account with that username or email address.', 'surecart' )
208 67 );
209 68 }
210 69
211 70 // verify the code.
@@ -222,24 +81,17 @@
222 81 }
223 82
224 83 // code is invalid or not verified.
225 84 if ( empty( $verify->verified ) ) {
226 - return new \WP_Error( 'invalid_code', __( 'Invalid verification code', 'surecart' ), [ 'status' => 400 ] );
85 + return new \WP_Error( 'invalid_code', __( 'Invalid verification code', 'surecart' ) );
227 86 }
228 87
229 - // The code was consumed — clear the local resend window so a later
230 - // request gets a fresh code instead of resuming a countdown for a code
231 - // that no longer exists. Verify requests don't carry checkout_mode, so
232 - // clear both modes; if a genuine platform window remains, the next
233 - // create re-stores it from the blocked_duplicate error.
234 - $this->clearResendWindow( $user->user_email );
235 -
236 88 // get the user based on the login value.
237 89 $user = $this->getUser( $request->get_param( 'login' ) );
238 90
239 91 // bail if no user.
240 92 if ( ! $user ) {
241 - return new \WP_Error( 'user_not_found', __( 'The user could not be found.', 'surecart' ), [ 'status' => 404 ] );
93 + return new \WP_Error( 'user_not_found', __( 'The user could not be found.', 'surecart' ) );
242 94 }
243 95
244 96 // login the user.
245 97 $logged_in = $user->login();
@@ -246,15 +98,8 @@
246 98
247 99 if ( is_wp_error( $logged_in ) ) {
248 100 return $logged_in;
249 101 }
250 -
251 - $verify->name = $user->display_name ?? $user->user_login;
252 - $verify->avatar_url = get_avatar_url( $user->user_email, [ 'size' => 48 ] );
253 - $verify->nonce = ( wp_installing() && ! is_multisite() ) ? '' : wp_create_nonce( 'wp_rest' );
254 - $redirect_to = $request->get_param( 'redirect_to' );
255 - $redirect_url = ! empty( $redirect_to ) ? wp_validate_redirect( $redirect_to, false ) : null;
256 - $verify->redirect_url = apply_filters( 'sc_login_redirect_url', $redirect_url ); // this is the URL to redirect to after login.
257 102
258 103 // return the model.
259 104 return $verify;
260 105 }