[MKT_949]:feat/cloudflare turnstile integration - #394
Conversation
|
The markdown table format seems to not be correct @jaaaaavier |
sg-gs
left a comment
There was a problem hiding this comment.
Please add the list of new environment variables in the PR's description @jaaaaavier
There was a problem hiding this comment.
if turnstileToken is now the first option, maybe it should be required, not optional. WDYT?
| const MAX_TURNSTILE_ATTEMPTS = 3; | ||
| const TURNSTILE_RETRY_BASE_DELAY_MS = 300; | ||
| const RETRYABLE_TURNSTILE_ERROR_CODE = 'internal-error'; |
There was a problem hiding this comment.
Maybe we can also extract the shared constants between this file and the captcha one to a constants.ts.
| hostname?: string; | ||
| }; | ||
|
|
||
| export class TurnstileUnavailableError extends Error {} |
There was a problem hiding this comment.
Add the necessary info here with:
super(message = '...') {
this.message = message;
this.name = 'TurnstileUnavailableError';
Object.setPrototypeOf(this, TurnstileUnavailableError.prototype);
}
Context: https://github.com/internxt/payments-server/blob/master/src/errors/UsersErrors.ts#L12
| jest.mock('../../../src/Logger', () => ({ | ||
| __esModule: true, | ||
| default: { | ||
| warn: jest.fn(), | ||
| info: jest.fn(), | ||
| error: jest.fn(), | ||
| }, | ||
| })); |
There was a problem hiding this comment.
I think you can use spyOn for Logger instead of mocking it.
| return await requestTurnstile(turnstileToken); | ||
| } catch (err) { | ||
| const isLastAttempt = attempt === MAX_TURNSTILE_ATTEMPTS - 1; | ||
| const isRetryable = isTransientNetworkError(err) || err instanceof TurnstileUnavailableError; |
There was a problem hiding this comment.
Extract this to a function. Something like:
function isRetryableTurnstileError(err: unknown): boolean {
return isTransientNetworkError(err) || err instanceof TurnstileUnavailableError;
}
|
sg-gs
left a comment
There was a problem hiding this comment.
Adding the environment variables @jaaaaavier
|
Secrets updated @jaaaaavier Turnstile Widget updated to consider |



Changes
The purpose of this PR is to integrate Cloudflare Turnstile into the repo. To do this, we’ve done the following:
assertCaptchafile. Here, we basically decide whether to use the newverifyTurnstilefunction or the oldverifyRecaptchafunction. This allows us to keep what we already had and know works as a fallbackverifyTurnstilefile, we’ve set up everything needed to verify with Cloudflare that the token is correct. This file was created based on the official Cloudflare Turnstile documentation.isTransientNetworkErrormethod to thenetworkRetryfile, since we’ll be using it in different parts of the code.verifyRecaptchawith theasserCaptchadescribed above, markedcaptchaTokenas optional, and addedturnstileTokenin the same way; now, one of these two must be requiredTest
Cloudflare publishes a set of dummy keys: public sitekey/secret pairs that force a deterministic
outcome (always pass, always fail, force an interactive challenge) while still going through the real
siteverifyAPI. They let us exercise the whole chain — browser widget → drive-web → payments →Cloudflare — without creating a widget in the dashboard.
Each scenario was run through a full checkout, checking three places: the request payload in the
browser, the payments logs, and the resulting HTTP status.
1x…BB(pass, invisible) + secret1x…AA(pass)POST /checkout/customerandPOST /checkout/payment-intentboth carry aturnstileTokenalongside the reCAPTCHAcaptchaToken. No fallback warning in the logs. Payment completes.2x…AA(always fails)Turnstile rejected the token: invalid-input-response→ 403, and reCAPTCHA is never consulted.responseTime: 60msconfirms no retries were attempted, i.e. the code was correctly treated as terminal rather than transient.2x…BB(always fails, invisible)600010; noturnstileTokenin the payload; payment completes through reCAPTCHA.generateTurnstileToken()returnsundefined, checkout is unaffected and falls back to reCAPTCHA.I can add some images or video in the task if needed but i think we will need some QA
Notes
The enviroment variables required are:
TURNSTILE_ENDPOINT=https://challenges.cloudflare.com/turnstile/v0/siteverifyTURNSTILE_SECRET=the one used on ai-server