From a698e9a331e1004c792bfbfade23818765c67ec7 Mon Sep 17 00:00:00 2001
From: lk316 <51822928+lk316@users.noreply.github.com>
Date: Tue, 28 Jul 2026 19:06:00 +0300
Subject: [PATCH 1/4] use native hash assign
---
.../public/lib/commands/verify.liquid | 3 +--
.../public/lib/commands/verify.liquid | 3 +--
.../public/lib/commands/verify.liquid | 3 +--
.../public/lib/helpers/score_check.liquid | 9 ++++-----
.../public/lib/commands/verify.liquid | 3 +--
.../app/lib/test/captcha/verify_build_test.liquid | 10 +++++-----
.../app/lib/test/captcha/verify_check_test.liquid | 8 ++++----
.../captcha/verify_execute_fail_closed_test.liquid | 2 +-
.../captchas/public/lib/commands/captcha/verify.liquid | 3 +--
9 files changed, 19 insertions(+), 25 deletions(-)
diff --git a/pos-module-captchas-hcaptcha/modules/captchas_hcaptcha/public/lib/commands/verify.liquid b/pos-module-captchas-hcaptcha/modules/captchas_hcaptcha/public/lib/commands/verify.liquid
index 7465baa..2787fdf 100644
--- a/pos-module-captchas-hcaptcha/modules/captchas_hcaptcha/public/lib/commands/verify.liquid
+++ b/pos-module-captchas-hcaptcha/modules/captchas_hcaptcha/public/lib/commands/verify.liquid
@@ -8,8 +8,7 @@
@returns {object} object with success (boolean), response (hash), valid, and errors.
{% enddoc %}
{% liquid
- assign payload = '{}' | parse_json
- assign payload = payload | hash_merge: secret: object.secret, token: object.token, remote_ip: object.remote_ip, sitekey: object.expected_sitekey
+ assign payload = { "secret": object.secret, "token": object.token, "remote_ip": object.remote_ip, "sitekey": object.expected_sitekey }
graphql result = 'modules/captchas_hcaptcha/api_call/send', data: payload, name: 'modules/captchas_hcaptcha/siteverify'
diff --git a/pos-module-captchas-recaptcha/modules/captchas_recaptcha/public/lib/commands/verify.liquid b/pos-module-captchas-recaptcha/modules/captchas_recaptcha/public/lib/commands/verify.liquid
index 633a459..eeeb96a 100644
--- a/pos-module-captchas-recaptcha/modules/captchas_recaptcha/public/lib/commands/verify.liquid
+++ b/pos-module-captchas-recaptcha/modules/captchas_recaptcha/public/lib/commands/verify.liquid
@@ -6,8 +6,7 @@
@returns {object} object with success (boolean), response (hash), valid, and errors.
{% enddoc %}
{% liquid
- assign payload = '{}' | parse_json
- assign payload = payload | hash_merge: secret: object.secret, token: object.token, remote_ip: object.remote_ip
+ assign payload = { "secret": object.secret, "token": object.token, "remote_ip": object.remote_ip }
graphql result = 'modules/captchas_recaptcha/api_call/send', data: payload, name: 'modules/captchas_recaptcha/siteverify'
diff --git a/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/commands/verify.liquid b/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/commands/verify.liquid
index 84e5343..e1a7777 100644
--- a/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/commands/verify.liquid
+++ b/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/commands/verify.liquid
@@ -9,8 +9,7 @@
@returns {object} object with success (boolean), score (number), action (string), response (hash), valid, and errors.
{% enddoc %}
{% liquid
- assign payload = '{}' | parse_json
- assign payload = payload | hash_merge: secret: object.secret, token: object.token, remote_ip: object.remote_ip
+ assign payload = { "secret": object.secret, "token": object.token, "remote_ip": object.remote_ip }
graphql result = 'modules/captchas_recaptcha3/api_call/send', data: payload, name: 'modules/captchas_recaptcha3/siteverify'
diff --git a/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/helpers/score_check.liquid b/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/helpers/score_check.liquid
index 0fd0907..95db966 100644
--- a/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/helpers/score_check.liquid
+++ b/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/helpers/score_check.liquid
@@ -10,7 +10,6 @@
{% enddoc %}
{% liquid
function c = 'modules/captchas/helpers/errors_hash', errors: errors
- assign result = '{}' | parse_json
# `!= true` (not `== false`): a nil/missing success field must fail closed too — otherwise a
# 200 JSON body without `success` would fall through to the score check and, with min_score 0,
@@ -18,7 +17,7 @@
if body.success != true
assign messages = 'modules/captchas/errors.verification_failed' | split: '|'
assign merged = c | hash_merge: captcha: messages
- assign result = result | hash_merge: valid: false, errors: merged
+ assign result = { "valid": false, "errors": merged }
return result
endif
@@ -26,7 +25,7 @@
if body.action != expected_action
assign messages = 'modules/captchas/errors.action_mismatch' | split: '|'
assign merged = c | hash_merge: captcha: messages
- assign result = result | hash_merge: valid: false, errors: merged
+ assign result = { "valid": false, "errors": merged }
return result
endif
endif
@@ -35,11 +34,11 @@
assign score = body.score | default: 0.0 | plus: 0.0
if score >= threshold
- assign result = result | hash_merge: valid: true, errors: c
+ assign result = { "valid": true, "errors": c }
else
assign messages = 'modules/captchas/errors.low_score' | split: '|'
assign merged = c | hash_merge: captcha: messages
- assign result = result | hash_merge: valid: false, errors: merged
+ assign result = { "valid": false, "errors": merged }
endif
return result
diff --git a/pos-module-captchas-turnstile/modules/captchas_turnstile/public/lib/commands/verify.liquid b/pos-module-captchas-turnstile/modules/captchas_turnstile/public/lib/commands/verify.liquid
index 384608e..c5b8de6 100644
--- a/pos-module-captchas-turnstile/modules/captchas_turnstile/public/lib/commands/verify.liquid
+++ b/pos-module-captchas-turnstile/modules/captchas_turnstile/public/lib/commands/verify.liquid
@@ -6,8 +6,7 @@
@returns {object} object with success (boolean), response (hash), valid, and errors.
{% enddoc %}
{% liquid
- assign payload = '{}' | parse_json
- assign payload = payload | hash_merge: secret: object.secret, token: object.token, remote_ip: object.remote_ip
+ assign payload = { "secret": object.secret, "token": object.token, "remote_ip": object.remote_ip }
graphql result = 'modules/captchas_turnstile/api_call/send', data: payload, name: 'modules/captchas_turnstile/siteverify'
diff --git a/pos-module-captchas/app/lib/test/captcha/verify_build_test.liquid b/pos-module-captchas/app/lib/test/captcha/verify_build_test.liquid
index cf48d25..9210f17 100644
--- a/pos-module-captchas/app/lib/test/captcha/verify_build_test.liquid
+++ b/pos-module-captchas/app/lib/test/captcha/verify_build_test.liquid
@@ -16,29 +16,29 @@
function contract = 'modules/tests/assertions/equal', contract: contract, given: b1.secret, expected: '', field_name: 'no_secret_is_blank'
# an explicit secret is preserved through build
- assign o1b = '{}' | parse_json | hash_merge: secret: 'sk_explicit_test'
+ assign o1b = { "secret": "sk_explicit_test" }
function b1b = 'modules/captchas/commands/captcha/verify/build', object: o1b
function contract = 'modules/tests/assertions/equal', contract: contract, given: b1b.secret, expected: 'sk_explicit_test', field_name: 'explicit_secret_preserved'
# an explicit provider is preserved, and the default token field comes from the provider
# module's config helper (dynamic dispatch)
- assign o2 = '{}' | parse_json | hash_merge: provider: 'test'
+ assign o2 = { "provider": "test" }
function b2 = 'modules/captchas/commands/captcha/verify/build', object: o2
function contract = 'modules/tests/assertions/equal', contract: contract, given: b2.provider, expected: 'test', field_name: 'explicit_provider'
function contract = 'modules/tests/assertions/equal', contract: contract, given: b2.token_field, expected: 'x-test-captcha-response', field_name: 'token_field_from_provider_config'
# an unavailable provider must not be dispatched to for config: token_field stays blank
- assign o3 = '{}' | parse_json | hash_merge: provider: 'bogus'
+ assign o3 = { "provider": "bogus" }
function b3 = 'modules/captchas/commands/captcha/verify/build', object: o3
function contract = 'modules/tests/assertions/blank', contract: contract, field_name: 'token_field', object: b3
# an explicit response_field_name overrides the provider default
- assign o4 = '{}' | parse_json | hash_merge: provider: 'test', response_field_name: 'my-custom-field'
+ assign o4 = { "provider": "test", "response_field_name": "my-custom-field" }
function b4 = 'modules/captchas/commands/captcha/verify/build', object: o4
function contract = 'modules/tests/assertions/equal', contract: contract, given: b4.token_field, expected: 'my-custom-field', field_name: 'explicit_response_field_name'
# an explicit token is honoured (used instead of reading context.params)
- assign o5 = '{}' | parse_json | hash_merge: provider: 'test', token: 'explicit-token-123'
+ assign o5 = { "provider": "test", "token": "explicit-token-123" }
function b5 = 'modules/captchas/commands/captcha/verify/build', object: o5
function contract = 'modules/tests/assertions/equal', contract: contract, given: b5.token, expected: 'explicit-token-123', field_name: 'explicit_token'
%}
diff --git a/pos-module-captchas/app/lib/test/captcha/verify_check_test.liquid b/pos-module-captchas/app/lib/test/captcha/verify_check_test.liquid
index 061fad3..7a71e5f 100644
--- a/pos-module-captchas/app/lib/test/captcha/verify_check_test.liquid
+++ b/pos-module-captchas/app/lib/test/captcha/verify_check_test.liquid
@@ -5,24 +5,24 @@
{% endcomment %}
{% liquid
# happy path: token + secret + available provider -> valid
- assign ok = '{}' | parse_json | hash_merge: provider: 'test', secret: 'sk_test', token: 'tok_test'
+ assign ok = { "provider": "test", "secret": "sk_test", "token": "tok_test" }
function ok = 'modules/captchas/commands/captcha/verify/check', object: ok
function contract = 'modules/tests/assertions/valid_object', contract: contract, object: ok, field_name: 'all_present'
# missing token -> invalid, with a token error
- assign no_token = '{}' | parse_json | hash_merge: provider: 'test', secret: 'sk_test'
+ assign no_token = { "provider": "test", "secret": "sk_test" }
function no_token = 'modules/captchas/commands/captcha/verify/check', object: no_token
function contract = 'modules/tests/assertions/not_valid_object', contract: contract, field_name: 'missing_token', object: no_token
function contract = 'modules/tests/assertions/presence', contract: contract, field_name: 'token', object: no_token.errors
# missing secret -> invalid, with a secret error
- assign no_secret = '{}' | parse_json | hash_merge: provider: 'test', token: 'tok_test'
+ assign no_secret = { "provider": "test", "token": "tok_test" }
function no_secret = 'modules/captchas/commands/captcha/verify/check', object: no_secret
function contract = 'modules/tests/assertions/not_valid_object', contract: contract, field_name: 'missing_secret', object: no_secret
function contract = 'modules/tests/assertions/presence', contract: contract, field_name: 'secret', object: no_secret.errors
# unavailable provider (no such module installed) -> invalid, with a provider error
- assign bad = '{}' | parse_json | hash_merge: provider: 'bogus', secret: 'sk_test', token: 'tok_test'
+ assign bad = { "provider": "bogus", "secret": "sk_test", "token": "tok_test" }
function bad = 'modules/captchas/commands/captcha/verify/check', object: bad
function contract = 'modules/tests/assertions/not_valid_object', contract: contract, field_name: 'unsupported_provider', object: bad
function contract = 'modules/tests/assertions/presence', contract: contract, field_name: 'provider', object: bad.errors
diff --git a/pos-module-captchas/app/lib/test/captcha/verify_execute_fail_closed_test.liquid b/pos-module-captchas/app/lib/test/captcha/verify_execute_fail_closed_test.liquid
index 7831017..514bd27 100644
--- a/pos-module-captchas/app/lib/test/captcha/verify_execute_fail_closed_test.liquid
+++ b/pos-module-captchas/app/lib/test/captcha/verify_execute_fail_closed_test.liquid
@@ -8,7 +8,7 @@
{% endcomment %}
{% liquid
# simulate an object that slipped past check: unknown provider, yet valid: true
- assign slipped = '{}' | parse_json | hash_merge: provider: 'bogus', secret: 'sk_test', token: 'tok_test', valid: true
+ assign slipped = { "provider": "bogus", "secret": "sk_test", "token": "tok_test", "valid": true }
function result = 'modules/captchas/commands/captcha/verify/execute', object: slipped
function contract = 'modules/tests/assertions/not_valid_object', contract: contract, field_name: 'execute_fails_closed', object: result
diff --git a/pos-module-captchas/modules/captchas/public/lib/commands/captcha/verify.liquid b/pos-module-captchas/modules/captchas/public/lib/commands/captcha/verify.liquid
index b7e3edc..7f2806b 100644
--- a/pos-module-captchas/modules/captchas/public/lib/commands/captcha/verify.liquid
+++ b/pos-module-captchas/modules/captchas/public/lib/commands/captcha/verify.liquid
@@ -23,8 +23,7 @@
@returns {object} { valid, success, provider, response, errors } (plus score/action for score-based providers).
{% enddoc %}
{% liquid
- assign object = '{}' | parse_json
- assign object = object | hash_merge: provider: provider, secret: secret, token: token, response_field_name: response_field_name, remote_ip: remote_ip, expected_sitekey: expected_sitekey, expected_hostname: expected_hostname, min_score: min_score, expected_action: expected_action
+ assign object = { "provider": provider, "secret": secret, "token": token, "response_field_name": response_field_name, "remote_ip": remote_ip, "expected_sitekey": expected_sitekey, "expected_hostname": expected_hostname, "min_score": min_score, "expected_action": expected_action }
function object = 'modules/captchas/commands/captcha/verify/build', object: object
function object = 'modules/captchas/commands/captcha/verify/check', object: object
From a7a31a78e8264b0375b59aefa6e0d40e3ecb6347 Mon Sep 17 00:00:00 2001
From: lk316 <51822928+lk316@users.noreply.github.com>
Date: Tue, 28 Jul 2026 19:20:48 +0300
Subject: [PATCH 2/4] use native arrays instead of split
---
.../captchas_hcaptcha/public/lib/commands/verify.liquid | 8 ++++----
.../captchas_recaptcha/public/lib/commands/verify.liquid | 8 ++++----
.../captchas_recaptcha3/public/lib/commands/verify.liquid | 6 +++---
.../public/lib/helpers/score_check.liquid | 6 +++---
.../captchas_turnstile/public/lib/commands/verify.liquid | 8 ++++----
.../app/lib/test/captcha/translations_test.liquid | 2 +-
.../captchas_test/public/lib/commands/verify.liquid | 2 +-
.../public/lib/commands/captcha/verify/check.liquid | 6 +++---
.../public/lib/commands/captcha/verify/execute.liquid | 4 ++--
9 files changed, 25 insertions(+), 25 deletions(-)
diff --git a/pos-module-captchas-hcaptcha/modules/captchas_hcaptcha/public/lib/commands/verify.liquid b/pos-module-captchas-hcaptcha/modules/captchas_hcaptcha/public/lib/commands/verify.liquid
index 2787fdf..13ab868 100644
--- a/pos-module-captchas-hcaptcha/modules/captchas_hcaptcha/public/lib/commands/verify.liquid
+++ b/pos-module-captchas-hcaptcha/modules/captchas_hcaptcha/public/lib/commands/verify.liquid
@@ -16,7 +16,7 @@
function c = 'modules/captchas/helpers/errors_hash', errors: object.errors
if call.errors != blank
- assign messages = 'modules/captchas/errors.request_failed' | split: '|'
+ assign messages = ["modules/captchas/errors.request_failed"]
assign object.errors = c | hash_merge: captcha: messages
assign object.success = false
assign object.valid = false
@@ -25,7 +25,7 @@
assign status = call.response.status
if status != 200
- assign messages = 'modules/captchas/errors.request_failed' | split: '|'
+ assign messages = ["modules/captchas/errors.request_failed"]
assign object.errors = c | hash_merge: captcha: messages
assign object.success = false
assign object.valid = false
@@ -38,7 +38,7 @@
assign raw_body = call.response.body | strip
assign first_char = raw_body | slice: 0, 1
if first_char != '{'
- assign messages = 'modules/captchas/errors.request_failed' | split: '|'
+ assign messages = ["modules/captchas/errors.request_failed"]
assign object.errors = c | hash_merge: captcha: messages
assign object.success = false
assign object.valid = false
@@ -52,7 +52,7 @@
if body.success
assign object.valid = true
else
- assign messages = 'modules/captchas/errors.verification_failed' | split: '|'
+ assign messages = ["modules/captchas/errors.verification_failed"]
assign object.errors = c | hash_merge: captcha: messages
assign object.valid = false
endif
diff --git a/pos-module-captchas-recaptcha/modules/captchas_recaptcha/public/lib/commands/verify.liquid b/pos-module-captchas-recaptcha/modules/captchas_recaptcha/public/lib/commands/verify.liquid
index eeeb96a..be4b04c 100644
--- a/pos-module-captchas-recaptcha/modules/captchas_recaptcha/public/lib/commands/verify.liquid
+++ b/pos-module-captchas-recaptcha/modules/captchas_recaptcha/public/lib/commands/verify.liquid
@@ -14,7 +14,7 @@
function c = 'modules/captchas/helpers/errors_hash', errors: object.errors
if call.errors != blank
- assign messages = 'modules/captchas/errors.request_failed' | split: '|'
+ assign messages = ["modules/captchas/errors.request_failed"]
assign object.errors = c | hash_merge: captcha: messages
assign object.success = false
assign object.valid = false
@@ -23,7 +23,7 @@
assign status = call.response.status
if status != 200
- assign messages = 'modules/captchas/errors.request_failed' | split: '|'
+ assign messages = ["modules/captchas/errors.request_failed"]
assign object.errors = c | hash_merge: captcha: messages
assign object.success = false
assign object.valid = false
@@ -36,7 +36,7 @@
assign raw_body = call.response.body | strip
assign first_char = raw_body | slice: 0, 1
if first_char != '{'
- assign messages = 'modules/captchas/errors.request_failed' | split: '|'
+ assign messages = ["modules/captchas/errors.request_failed"]
assign object.errors = c | hash_merge: captcha: messages
assign object.success = false
assign object.valid = false
@@ -50,7 +50,7 @@
if body.success
assign object.valid = true
else
- assign messages = 'modules/captchas/errors.verification_failed' | split: '|'
+ assign messages = ["modules/captchas/errors.verification_failed"]
assign object.errors = c | hash_merge: captcha: messages
assign object.valid = false
endif
diff --git a/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/commands/verify.liquid b/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/commands/verify.liquid
index e1a7777..317395f 100644
--- a/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/commands/verify.liquid
+++ b/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/commands/verify.liquid
@@ -17,7 +17,7 @@
function c = 'modules/captchas/helpers/errors_hash', errors: object.errors
if call.errors != blank
- assign messages = 'modules/captchas/errors.request_failed' | split: '|'
+ assign messages = ["modules/captchas/errors.request_failed"]
assign object.errors = c | hash_merge: captcha: messages
assign object.success = false
assign object.valid = false
@@ -26,7 +26,7 @@
assign status = call.response.status
if status != 200
- assign messages = 'modules/captchas/errors.request_failed' | split: '|'
+ assign messages = ["modules/captchas/errors.request_failed"]
assign object.errors = c | hash_merge: captcha: messages
assign object.success = false
assign object.valid = false
@@ -39,7 +39,7 @@
assign raw_body = call.response.body | strip
assign first_char = raw_body | slice: 0, 1
if first_char != '{'
- assign messages = 'modules/captchas/errors.request_failed' | split: '|'
+ assign messages = ["modules/captchas/errors.request_failed"]
assign object.errors = c | hash_merge: captcha: messages
assign object.success = false
assign object.valid = false
diff --git a/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/helpers/score_check.liquid b/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/helpers/score_check.liquid
index 95db966..9afdcf6 100644
--- a/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/helpers/score_check.liquid
+++ b/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/helpers/score_check.liquid
@@ -15,7 +15,7 @@
# 200 JSON body without `success` would fall through to the score check and, with min_score 0,
# pass without any provider verdict.
if body.success != true
- assign messages = 'modules/captchas/errors.verification_failed' | split: '|'
+ assign messages = ["modules/captchas/errors.verification_failed"]
assign merged = c | hash_merge: captcha: messages
assign result = { "valid": false, "errors": merged }
return result
@@ -23,7 +23,7 @@
if expected_action != blank
if body.action != expected_action
- assign messages = 'modules/captchas/errors.action_mismatch' | split: '|'
+ assign messages = ["modules/captchas/errors.action_mismatch"]
assign merged = c | hash_merge: captcha: messages
assign result = { "valid": false, "errors": merged }
return result
@@ -36,7 +36,7 @@
if score >= threshold
assign result = { "valid": true, "errors": c }
else
- assign messages = 'modules/captchas/errors.low_score' | split: '|'
+ assign messages = ["modules/captchas/errors.low_score"]
assign merged = c | hash_merge: captcha: messages
assign result = { "valid": false, "errors": merged }
endif
diff --git a/pos-module-captchas-turnstile/modules/captchas_turnstile/public/lib/commands/verify.liquid b/pos-module-captchas-turnstile/modules/captchas_turnstile/public/lib/commands/verify.liquid
index c5b8de6..a5c8353 100644
--- a/pos-module-captchas-turnstile/modules/captchas_turnstile/public/lib/commands/verify.liquid
+++ b/pos-module-captchas-turnstile/modules/captchas_turnstile/public/lib/commands/verify.liquid
@@ -14,7 +14,7 @@
function c = 'modules/captchas/helpers/errors_hash', errors: object.errors
if call.errors != blank
- assign messages = 'modules/captchas/errors.request_failed' | split: '|'
+ assign messages = ["modules/captchas/errors.request_failed"]
assign object.errors = c | hash_merge: captcha: messages
assign object.success = false
assign object.valid = false
@@ -23,7 +23,7 @@
assign status = call.response.status
if status != 200
- assign messages = 'modules/captchas/errors.request_failed' | split: '|'
+ assign messages = ["modules/captchas/errors.request_failed"]
assign object.errors = c | hash_merge: captcha: messages
assign object.success = false
assign object.valid = false
@@ -36,7 +36,7 @@
assign raw_body = call.response.body | strip
assign first_char = raw_body | slice: 0, 1
if first_char != '{'
- assign messages = 'modules/captchas/errors.request_failed' | split: '|'
+ assign messages = ["modules/captchas/errors.request_failed"]
assign object.errors = c | hash_merge: captcha: messages
assign object.success = false
assign object.valid = false
@@ -50,7 +50,7 @@
if body.success
assign object.valid = true
else
- assign messages = 'modules/captchas/errors.verification_failed' | split: '|'
+ assign messages = ["modules/captchas/errors.verification_failed"]
assign object.errors = c | hash_merge: captcha: messages
assign object.valid = false
endif
diff --git a/pos-module-captchas/app/lib/test/captcha/translations_test.liquid b/pos-module-captchas/app/lib/test/captcha/translations_test.liquid
index b168d7d..7427275 100644
--- a/pos-module-captchas/app/lib/test/captcha/translations_test.liquid
+++ b/pos-module-captchas/app/lib/test/captcha/translations_test.liquid
@@ -7,7 +7,7 @@
Keep this list in sync with the keys used in commands/captcha and translations/en.yml.
{% endcomment %}
{% liquid
- assign keys = 'token_missing,secret_missing,unsupported_provider,verification_failed,request_failed,low_score,action_mismatch,hostname_mismatch' | split: ','
+ assign keys = ["token_missing", "secret_missing", "unsupported_provider", "verification_failed", "request_failed", "low_score", "action_mismatch", "hostname_mismatch"]
for key in keys
assign full_key = 'modules/captchas/errors.' | append: key
diff --git a/pos-module-captchas/app/modules/captchas_test/public/lib/commands/verify.liquid b/pos-module-captchas/app/modules/captchas_test/public/lib/commands/verify.liquid
index 1d18af6..9b02efb 100644
--- a/pos-module-captchas/app/modules/captchas_test/public/lib/commands/verify.liquid
+++ b/pos-module-captchas/app/modules/captchas_test/public/lib/commands/verify.liquid
@@ -16,7 +16,7 @@
endif
assign response = '{"success": false}' | parse_json
- assign messages = 'modules/captchas/errors.verification_failed' | split: '|'
+ assign messages = ["modules/captchas/errors.verification_failed"]
assign errors = c | hash_merge: captcha: messages
assign object = object | hash_merge: response: response, success: false, valid: false, errors: errors
return object
diff --git a/pos-module-captchas/modules/captchas/public/lib/commands/captcha/verify/check.liquid b/pos-module-captchas/modules/captchas/public/lib/commands/captcha/verify/check.liquid
index 5b09160..a18775e 100644
--- a/pos-module-captchas/modules/captchas/public/lib/commands/captcha/verify/check.liquid
+++ b/pos-module-captchas/modules/captchas/public/lib/commands/captcha/verify/check.liquid
@@ -9,17 +9,17 @@
function provider_supported = 'modules/captchas/helpers/provider_available', provider: object.provider
if object.token == blank
- assign messages = 'modules/captchas/errors.token_missing' | split: '|'
+ assign messages = ["modules/captchas/errors.token_missing"]
assign c = c | hash_merge: token: messages
endif
if object.secret == blank
- assign messages = 'modules/captchas/errors.secret_missing' | split: '|'
+ assign messages = ["modules/captchas/errors.secret_missing"]
assign c = c | hash_merge: secret: messages
endif
unless provider_supported
- assign messages = 'modules/captchas/errors.unsupported_provider' | split: '|'
+ assign messages = ["modules/captchas/errors.unsupported_provider"]
assign c = c | hash_merge: provider: messages
endunless
diff --git a/pos-module-captchas/modules/captchas/public/lib/commands/captcha/verify/execute.liquid b/pos-module-captchas/modules/captchas/public/lib/commands/captcha/verify/execute.liquid
index dc2da84..4ab6b1a 100644
--- a/pos-module-captchas/modules/captchas/public/lib/commands/captcha/verify/execute.liquid
+++ b/pos-module-captchas/modules/captchas/public/lib/commands/captcha/verify/execute.liquid
@@ -15,7 +15,7 @@
function provider_is_available = 'modules/captchas/helpers/provider_available', provider: object.provider
unless provider_is_available
function c = 'modules/captchas/helpers/errors_hash', errors: object.errors
- assign messages = 'modules/captchas/errors.unsupported_provider' | split: '|'
+ assign messages = ["modules/captchas/errors.unsupported_provider"]
assign object.errors = c | hash_merge: provider: messages
assign object.success = false
assign object.valid = false
@@ -29,7 +29,7 @@
function hostname_allowed = 'modules/captchas/helpers/hostname_allowed', hostname: object.response.hostname, expected_hostname: object.expected_hostname
unless hostname_allowed
function c = 'modules/captchas/helpers/errors_hash', errors: object.errors
- assign messages = 'modules/captchas/errors.hostname_mismatch' | split: '|'
+ assign messages = ["modules/captchas/errors.hostname_mismatch"]
assign object.errors = c | hash_merge: captcha: messages
assign object.valid = false
endunless
From c7df06be3e34ea7874ab48efb954ad355495d6de Mon Sep 17 00:00:00 2001
From: lk316 <51822928+lk316@users.noreply.github.com>
Date: Wed, 29 Jul 2026 18:03:36 +0300
Subject: [PATCH 3/4] translations messages added instead of keys
---
pos-module-captchas-hcaptcha/CHANGELOG.md | 6 +++
pos-module-captchas-hcaptcha/README.md | 3 ++
.../test/captchas_hcaptcha/verify_test.liquid | 17 ++++++++
.../app/views/pages/verify.liquid | 8 +++-
.../public/lib/commands/verify.liquid | 20 +++++-----
pos-module-captchas-hcaptcha/pos-module.json | 4 +-
pos-module-captchas-recaptcha/CHANGELOG.md | 6 +++
pos-module-captchas-recaptcha/README.md | 3 ++
.../captchas_recaptcha/verify_test.liquid | 17 ++++++++
.../app/views/pages/verify.liquid | 8 +++-
.../public/lib/commands/verify.liquid | 20 +++++-----
pos-module-captchas-recaptcha/pos-module.json | 4 +-
pos-module-captchas-recaptcha3/CHANGELOG.md | 7 ++++
pos-module-captchas-recaptcha3/README.md | 5 ++-
.../score_check_test.liquid | 16 ++++++++
.../app/views/pages/verify.liquid | 8 +++-
.../public/lib/commands/verify.liquid | 16 ++++----
.../public/lib/helpers/score_check.liquid | 16 ++++----
.../pos-module.json | 4 +-
pos-module-captchas-turnstile/CHANGELOG.md | 6 +++
pos-module-captchas-turnstile/README.md | 3 ++
.../captchas_turnstile/verify_test.liquid | 17 ++++++++
.../app/views/pages/verify.liquid | 8 +++-
.../public/lib/commands/verify.liquid | 20 +++++-----
pos-module-captchas-turnstile/pos-module.json | 4 +-
pos-module-captchas/CHANGELOG.md | 14 +++++++
pos-module-captchas/README.md | 40 ++++++++++++++-----
.../lib/test/captcha/add_error_test.liquid | 36 +++++++++++++++++
.../lib/test/captcha/errors_hash_test.liquid | 2 +-
.../lib/test/captcha/translations_test.liquid | 8 ++--
.../public/lib/commands/verify.liquid | 5 +--
.../app/views/pages/captcha-verify.liquid | 9 ++++-
.../lib/commands/captcha/verify/check.liquid | 12 +++---
.../commands/captcha/verify/execute.liquid | 12 +++---
.../public/lib/helpers/add_error.liquid | 24 +++++++++++
pos-module-captchas/pos-module.json | 2 +-
36 files changed, 310 insertions(+), 100 deletions(-)
create mode 100644 pos-module-captchas/app/lib/test/captcha/add_error_test.liquid
create mode 100644 pos-module-captchas/modules/captchas/public/lib/helpers/add_error.liquid
diff --git a/pos-module-captchas-hcaptcha/CHANGELOG.md b/pos-module-captchas-hcaptcha/CHANGELOG.md
index 761651f..fc45747 100644
--- a/pos-module-captchas-hcaptcha/CHANGELOG.md
+++ b/pos-module-captchas-hcaptcha/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## 1.1.0
+
+- Errors are now recorded with `modules/captchas/helpers/add_error`, so `result.errors` holds
+ translated messages instead of `modules/captchas/errors.*` keys — drop any `| t` you apply
+ to them. Requires `captchas ^1.1.0`.
+
## 1.0.0
Initial release. hCaptcha provider for the
diff --git a/pos-module-captchas-hcaptcha/README.md b/pos-module-captchas-hcaptcha/README.md
index c583264..41dbfda 100644
--- a/pos-module-captchas-hcaptcha/README.md
+++ b/pos-module-captchas-hcaptcha/README.md
@@ -95,6 +95,9 @@ non-JSON bodies (`modules/captchas/errors.request_failed`). A `success: false` a
`modules/captchas/errors.verification_failed`. `expected_hostname` (handled by the
abstraction) works with the `hostname` field hCaptcha returns.
+Errors are stored via `modules/captchas/helpers/add_error`, which translates the message as it
+is set — `result.errors.captcha` holds display-ready text, not translation keys.
+
> ⚠️ **Billing** — the server-side verify call is a normal, billable platformOS API call;
> each `verify` performs one outbound `siteverify` request.
diff --git a/pos-module-captchas-hcaptcha/app/lib/test/captchas_hcaptcha/verify_test.liquid b/pos-module-captchas-hcaptcha/app/lib/test/captchas_hcaptcha/verify_test.liquid
index d973b51..b4bdd1a 100644
--- a/pos-module-captchas-hcaptcha/app/lib/test/captchas_hcaptcha/verify_test.liquid
+++ b/pos-module-captchas-hcaptcha/app/lib/test/captchas_hcaptcha/verify_test.liquid
@@ -13,4 +13,21 @@
function r_fail = 'modules/captchas/commands/captcha/verify', provider: 'hcaptcha', secret: '0x0000000000000000000000000000000000000000', token: 'not-a-real-token'
function contract = 'modules/tests/assertions/not_valid_object', contract: contract, field_name: 'garbage_token_fails', object: r_fail
function contract = 'modules/tests/assertions/presence', contract: contract, field_name: 'captcha', object: r_fail.errors
+
+ # The message is stored translated, not as a raw key: this provider module sets the error via
+ # modules/captchas/helpers/add_error, which resolves a modules/captchas/* key from inside
+ # modules/captchas_hcaptcha. Callers render errors verbatim, so a raw key would leak to users.
+ # Reuses r_fail — no extra billable call.
+ assign fail_key = 'modules/captchas/errors.verification_failed'
+ assign fail_message = r_fail.errors.captcha | first
+ assign expected_message = fail_key | t
+ function contract = 'modules/tests/assertions/equal', contract: contract, given: fail_message, expected: expected_message, field_name: 'garbage_token_message_translated'
+
+ # The check above alone would pass if `| t` silently returned the key on both sides, so assert
+ # the stored value is not the key itself — that is the failure mode this guards against.
+ assign is_raw_key = false
+ if fail_message == fail_key
+ assign is_raw_key = true
+ endif
+ function contract = 'modules/tests/assertions/equal', contract: contract, given: is_raw_key, expected: false, field_name: 'garbage_token_message_not_raw_key'
%}
diff --git a/pos-module-captchas-hcaptcha/app/views/pages/verify.liquid b/pos-module-captchas-hcaptcha/app/views/pages/verify.liquid
index 4802697..d36c140 100644
--- a/pos-module-captchas-hcaptcha/app/views/pages/verify.liquid
+++ b/pos-module-captchas-hcaptcha/app/views/pages/verify.liquid
@@ -25,10 +25,14 @@ method: post
Submitted message: {{ context.params.message }}
{% else %}
❌ Captcha failed ({{ result.provider }})
+ {% comment %}
+ result.errors holds already-translated messages (the commands translate at set time, via
+ modules/captchas/helpers/add_error), so they render as-is — no `| t` here.
+ {% endcomment %}
{% for error in result.errors %}
- {% for key in error[1] %}
- - {{ key | t }}
+ {% for message in error[1] %}
+ - {{ message }}
{% endfor %}
{% endfor %}
diff --git a/pos-module-captchas-hcaptcha/modules/captchas_hcaptcha/public/lib/commands/verify.liquid b/pos-module-captchas-hcaptcha/modules/captchas_hcaptcha/public/lib/commands/verify.liquid
index 13ab868..96115f7 100644
--- a/pos-module-captchas-hcaptcha/modules/captchas_hcaptcha/public/lib/commands/verify.liquid
+++ b/pos-module-captchas-hcaptcha/modules/captchas_hcaptcha/public/lib/commands/verify.liquid
@@ -5,7 +5,8 @@
@param {object} object - validated object: { secret, token, remote_ip, expected_sitekey, errors }.
expected_sitekey (optional) is forwarded as hCaptcha's `sitekey` param — hCaptcha secrets are
account-wide, so this is what stops a token issued for another sitekey from being redeemed here.
- @returns {object} object with success (boolean), response (hash), valid, and errors.
+ @returns {object} object with success (boolean), response (hash), valid, and errors
+ (translated messages, via modules/captchas/helpers/add_error).
{% enddoc %}
{% liquid
assign payload = { "secret": object.secret, "token": object.token, "remote_ip": object.remote_ip, "sitekey": object.expected_sitekey }
@@ -13,11 +14,10 @@
graphql result = 'modules/captchas_hcaptcha/api_call/send', data: payload, name: 'modules/captchas_hcaptcha/siteverify'
assign call = result.api_call_send
- function c = 'modules/captchas/helpers/errors_hash', errors: object.errors
if call.errors != blank
- assign messages = ["modules/captchas/errors.request_failed"]
- assign object.errors = c | hash_merge: captcha: messages
+ function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
+ assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
@@ -25,8 +25,8 @@
assign status = call.response.status
if status != 200
- assign messages = ["modules/captchas/errors.request_failed"]
- assign object.errors = c | hash_merge: captcha: messages
+ function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
+ assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
@@ -38,8 +38,8 @@
assign raw_body = call.response.body | strip
assign first_char = raw_body | slice: 0, 1
if first_char != '{'
- assign messages = ["modules/captchas/errors.request_failed"]
- assign object.errors = c | hash_merge: captcha: messages
+ function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
+ assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
@@ -52,8 +52,8 @@
if body.success
assign object.valid = true
else
- assign messages = ["modules/captchas/errors.verification_failed"]
- assign object.errors = c | hash_merge: captcha: messages
+ function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.verification_failed'
+ assign object.errors = updated_errors
assign object.valid = false
endif
diff --git a/pos-module-captchas-hcaptcha/pos-module.json b/pos-module-captchas-hcaptcha/pos-module.json
index 1e5e3a5..8587c30 100644
--- a/pos-module-captchas-hcaptcha/pos-module.json
+++ b/pos-module-captchas-hcaptcha/pos-module.json
@@ -1,9 +1,9 @@
{
"name": "pOS Captchas hCaptcha",
"machine_name": "captchas_hcaptcha",
- "version": "1.0.0",
+ "version": "1.1.0",
"dependencies": {
- "captchas": "^1.0.0"
+ "captchas": "^1.1.0"
},
"devDependencies": {
"tests": "^1.3.4"
diff --git a/pos-module-captchas-recaptcha/CHANGELOG.md b/pos-module-captchas-recaptcha/CHANGELOG.md
index ccbc8e2..d5be26b 100644
--- a/pos-module-captchas-recaptcha/CHANGELOG.md
+++ b/pos-module-captchas-recaptcha/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## 1.1.0
+
+- Errors are now recorded with `modules/captchas/helpers/add_error`, so `result.errors` holds
+ translated messages instead of `modules/captchas/errors.*` keys — drop any `| t` you apply
+ to them. Requires `captchas ^1.1.0`.
+
## 1.0.0
Initial release. Google reCAPTCHA v2 (checkbox / invisible) provider for the
diff --git a/pos-module-captchas-recaptcha/README.md b/pos-module-captchas-recaptcha/README.md
index 4ed1455..85e4985 100644
--- a/pos-module-captchas-recaptcha/README.md
+++ b/pos-module-captchas-recaptcha/README.md
@@ -100,6 +100,9 @@ responses, and non-JSON bodies (`modules/captchas/errors.request_failed`). A
`expected_hostname` (handled by the abstraction) works with the `hostname` field
reCAPTCHA returns.
+Errors are stored via `modules/captchas/helpers/add_error`, which translates the message as it
+is set — `result.errors.captcha` holds display-ready text, not translation keys.
+
> ⚠️ **Billing** — the server-side verify call is a normal, billable platformOS API call;
> each `verify` performs one outbound `siteverify` request.
diff --git a/pos-module-captchas-recaptcha/app/lib/test/captchas_recaptcha/verify_test.liquid b/pos-module-captchas-recaptcha/app/lib/test/captchas_recaptcha/verify_test.liquid
index 0308e9e..0424857 100644
--- a/pos-module-captchas-recaptcha/app/lib/test/captchas_recaptcha/verify_test.liquid
+++ b/pos-module-captchas-recaptcha/app/lib/test/captchas_recaptcha/verify_test.liquid
@@ -13,4 +13,21 @@
function r_fail = 'modules/captchas/commands/captcha/verify', provider: 'recaptcha', secret: 'not-a-real-secret', token: 'dummy-token-for-test-keys'
function contract = 'modules/tests/assertions/not_valid_object', contract: contract, field_name: 'bad_secret_fails', object: r_fail
function contract = 'modules/tests/assertions/presence', contract: contract, field_name: 'captcha', object: r_fail.errors
+
+ # The message is stored translated, not as a raw key: this provider module sets the error via
+ # modules/captchas/helpers/add_error, which resolves a modules/captchas/* key from inside
+ # modules/captchas_recaptcha. Callers render errors verbatim, so a raw key would leak to users.
+ # Reuses r_fail — no extra billable call.
+ assign fail_key = 'modules/captchas/errors.verification_failed'
+ assign fail_message = r_fail.errors.captcha | first
+ assign expected_message = fail_key | t
+ function contract = 'modules/tests/assertions/equal', contract: contract, given: fail_message, expected: expected_message, field_name: 'bad_secret_message_translated'
+
+ # The check above alone would pass if `| t` silently returned the key on both sides, so assert
+ # the stored value is not the key itself — that is the failure mode this guards against.
+ assign is_raw_key = false
+ if fail_message == fail_key
+ assign is_raw_key = true
+ endif
+ function contract = 'modules/tests/assertions/equal', contract: contract, given: is_raw_key, expected: false, field_name: 'bad_secret_message_not_raw_key'
%}
diff --git a/pos-module-captchas-recaptcha/app/views/pages/verify.liquid b/pos-module-captchas-recaptcha/app/views/pages/verify.liquid
index 5bab788..bb29dff 100644
--- a/pos-module-captchas-recaptcha/app/views/pages/verify.liquid
+++ b/pos-module-captchas-recaptcha/app/views/pages/verify.liquid
@@ -21,10 +21,14 @@ method: post
Submitted message: {{ context.params.message }}
{% else %}
❌ Captcha failed ({{ result.provider }})
+ {% comment %}
+ result.errors holds already-translated messages (the commands translate at set time, via
+ modules/captchas/helpers/add_error), so they render as-is — no `| t` here.
+ {% endcomment %}
{% for error in result.errors %}
- {% for key in error[1] %}
- - {{ key | t }}
+ {% for message in error[1] %}
+ - {{ message }}
{% endfor %}
{% endfor %}
diff --git a/pos-module-captchas-recaptcha/modules/captchas_recaptcha/public/lib/commands/verify.liquid b/pos-module-captchas-recaptcha/modules/captchas_recaptcha/public/lib/commands/verify.liquid
index be4b04c..6fe0f58 100644
--- a/pos-module-captchas-recaptcha/modules/captchas_recaptcha/public/lib/commands/verify.liquid
+++ b/pos-module-captchas-recaptcha/modules/captchas_recaptcha/public/lib/commands/verify.liquid
@@ -3,7 +3,8 @@
api_call_send and normalize the result. Part of the captchas provider contract —
dispatched by modules/captchas/commands/captcha/verify/execute.
@param {object} object - validated object: { secret, token, remote_ip, errors }.
- @returns {object} object with success (boolean), response (hash), valid, and errors.
+ @returns {object} object with success (boolean), response (hash), valid, and errors
+ (translated messages, via modules/captchas/helpers/add_error).
{% enddoc %}
{% liquid
assign payload = { "secret": object.secret, "token": object.token, "remote_ip": object.remote_ip }
@@ -11,11 +12,10 @@
graphql result = 'modules/captchas_recaptcha/api_call/send', data: payload, name: 'modules/captchas_recaptcha/siteverify'
assign call = result.api_call_send
- function c = 'modules/captchas/helpers/errors_hash', errors: object.errors
if call.errors != blank
- assign messages = ["modules/captchas/errors.request_failed"]
- assign object.errors = c | hash_merge: captcha: messages
+ function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
+ assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
@@ -23,8 +23,8 @@
assign status = call.response.status
if status != 200
- assign messages = ["modules/captchas/errors.request_failed"]
- assign object.errors = c | hash_merge: captcha: messages
+ function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
+ assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
@@ -36,8 +36,8 @@
assign raw_body = call.response.body | strip
assign first_char = raw_body | slice: 0, 1
if first_char != '{'
- assign messages = ["modules/captchas/errors.request_failed"]
- assign object.errors = c | hash_merge: captcha: messages
+ function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
+ assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
@@ -50,8 +50,8 @@
if body.success
assign object.valid = true
else
- assign messages = ["modules/captchas/errors.verification_failed"]
- assign object.errors = c | hash_merge: captcha: messages
+ function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.verification_failed'
+ assign object.errors = updated_errors
assign object.valid = false
endif
diff --git a/pos-module-captchas-recaptcha/pos-module.json b/pos-module-captchas-recaptcha/pos-module.json
index 12e0ec8..2dc472c 100644
--- a/pos-module-captchas-recaptcha/pos-module.json
+++ b/pos-module-captchas-recaptcha/pos-module.json
@@ -1,9 +1,9 @@
{
"name": "pOS Captchas reCAPTCHA v2",
"machine_name": "captchas_recaptcha",
- "version": "1.0.0",
+ "version": "1.1.0",
"dependencies": {
- "captchas": "^1.0.0"
+ "captchas": "^1.1.0"
},
"devDependencies": {
"tests": "^1.3.4"
diff --git a/pos-module-captchas-recaptcha3/CHANGELOG.md b/pos-module-captchas-recaptcha3/CHANGELOG.md
index c87de6d..92842e5 100644
--- a/pos-module-captchas-recaptcha3/CHANGELOG.md
+++ b/pos-module-captchas-recaptcha3/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog
+## 1.1.0
+
+- Errors are now recorded with `modules/captchas/helpers/add_error` (in both `commands/verify`
+ and `helpers/score_check`), so `result.errors` holds translated messages instead of
+ `modules/captchas/errors.*` keys — drop any `| t` you apply to them. Requires
+ `captchas ^1.1.0`.
+
## 1.0.0
Initial release. Google reCAPTCHA v3 (invisible, score-based) provider for the
diff --git a/pos-module-captchas-recaptcha3/README.md b/pos-module-captchas-recaptcha3/README.md
index 89d80f3..236ce23 100644
--- a/pos-module-captchas-recaptcha3/README.md
+++ b/pos-module-captchas-recaptcha3/README.md
@@ -96,12 +96,15 @@ challenge fails client-side, the form submits token-less and verify fails closed
and non-JSON bodies (`modules/captchas/errors.request_failed`), then applies the score/action
gate (`modules/captchas_recaptcha3/helpers/score_check`):
-| Outcome | Error key |
+| Outcome | Message key |
|---|---|
| provider `success` false/missing | `modules/captchas/errors.verification_failed` |
| action ≠ `expected_action` | `modules/captchas/errors.action_mismatch` |
| score < `min_score` | `modules/captchas/errors.low_score` |
+Errors are stored via `modules/captchas/helpers/add_error`, which translates the message as it
+is set — `result.errors.captcha` holds display-ready text, not the keys above.
+
`expected_hostname` (handled by the abstraction) works with the `hostname` field the
response includes.
diff --git a/pos-module-captchas-recaptcha3/app/lib/test/captchas_recaptcha3/score_check_test.liquid b/pos-module-captchas-recaptcha3/app/lib/test/captchas_recaptcha3/score_check_test.liquid
index d651074..ec3431f 100644
--- a/pos-module-captchas-recaptcha3/app/lib/test/captchas_recaptcha3/score_check_test.liquid
+++ b/pos-module-captchas-recaptcha3/app/lib/test/captchas_recaptcha3/score_check_test.liquid
@@ -17,6 +17,22 @@
function contract = 'modules/tests/assertions/equal', contract: contract, given: r2.valid, expected: false, field_name: 'low_score_fails'
function contract = 'modules/tests/assertions/presence', contract: contract, field_name: 'captcha', object: r2.errors
+ # The message is stored translated, not as a raw key: score_check sets the error via
+ # modules/captchas/helpers/add_error, which resolves a modules/captchas/* key from inside
+ # modules/captchas_recaptcha3. Callers render errors verbatim, so a raw key would leak to users.
+ assign low_score_key = 'modules/captchas/errors.low_score'
+ assign low_score_message = r2.errors.captcha | first
+ assign expected_message = low_score_key | t
+ function contract = 'modules/tests/assertions/equal', contract: contract, given: low_score_message, expected: expected_message, field_name: 'low_score_message_translated'
+
+ # The check above alone would pass if `| t` silently returned the key on both sides, so assert
+ # the stored value is not the key itself — that is the failure mode this guards against.
+ assign is_raw_key = false
+ if low_score_message == low_score_key
+ assign is_raw_key = true
+ endif
+ function contract = 'modules/tests/assertions/equal', contract: contract, given: is_raw_key, expected: false, field_name: 'low_score_message_not_raw_key'
+
# a custom min_score lowers the bar
function r3 = 'modules/captchas_recaptcha3/helpers/score_check', body: body_low, min_score: 0.2, errors: no_errors
function contract = 'modules/tests/assertions/equal', contract: contract, given: r3.valid, expected: true, field_name: 'custom_min_score'
diff --git a/pos-module-captchas-recaptcha3/app/views/pages/verify.liquid b/pos-module-captchas-recaptcha3/app/views/pages/verify.liquid
index 3af583c..a65d5b3 100644
--- a/pos-module-captchas-recaptcha3/app/views/pages/verify.liquid
+++ b/pos-module-captchas-recaptcha3/app/views/pages/verify.liquid
@@ -23,10 +23,14 @@ method: post
Submitted message: {{ context.params.message }}
{% else %}
❌ Captcha failed ({{ result.provider }})
+ {% comment %}
+ result.errors holds already-translated messages (the commands translate at set time, via
+ modules/captchas/helpers/add_error), so they render as-is — no `| t` here.
+ {% endcomment %}
{% for error in result.errors %}
- {% for key in error[1] %}
- - {{ key | t }}
+ {% for message in error[1] %}
+ - {{ message }}
{% endfor %}
{% endfor %}
diff --git a/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/commands/verify.liquid b/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/commands/verify.liquid
index 317395f..07aa86f 100644
--- a/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/commands/verify.liquid
+++ b/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/commands/verify.liquid
@@ -6,7 +6,8 @@
modules/captchas/commands/captcha/verify/execute.
@param {object} object - validated object: { secret, token, remote_ip, errors, min_score, expected_action }.
min_score defaults to 0.5; expected_action is optional.
- @returns {object} object with success (boolean), score (number), action (string), response (hash), valid, and errors.
+ @returns {object} object with success (boolean), score (number), action (string), response (hash),
+ valid, and errors (translated messages, via modules/captchas/helpers/add_error).
{% enddoc %}
{% liquid
assign payload = { "secret": object.secret, "token": object.token, "remote_ip": object.remote_ip }
@@ -14,11 +15,10 @@
graphql result = 'modules/captchas_recaptcha3/api_call/send', data: payload, name: 'modules/captchas_recaptcha3/siteverify'
assign call = result.api_call_send
- function c = 'modules/captchas/helpers/errors_hash', errors: object.errors
if call.errors != blank
- assign messages = ["modules/captchas/errors.request_failed"]
- assign object.errors = c | hash_merge: captcha: messages
+ function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
+ assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
@@ -26,8 +26,8 @@
assign status = call.response.status
if status != 200
- assign messages = ["modules/captchas/errors.request_failed"]
- assign object.errors = c | hash_merge: captcha: messages
+ function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
+ assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
@@ -39,8 +39,8 @@
assign raw_body = call.response.body | strip
assign first_char = raw_body | slice: 0, 1
if first_char != '{'
- assign messages = ["modules/captchas/errors.request_failed"]
- assign object.errors = c | hash_merge: captcha: messages
+ function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
+ assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
diff --git a/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/helpers/score_check.liquid b/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/helpers/score_check.liquid
index 9afdcf6..91b4a3a 100644
--- a/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/helpers/score_check.liquid
+++ b/pos-module-captchas-recaptcha3/modules/captchas_recaptcha3/public/lib/helpers/score_check.liquid
@@ -6,25 +6,22 @@
@param {number} [min_score] - minimum score required to pass (default 0.5)
@param {string} [expected_action] - when present, body.action must equal it
@param {object} [errors] - existing errors hash to merge into
- @returns {object} { valid (boolean), errors (hash) }
+ @returns {object} { valid (boolean), errors (hash of translated messages, via
+ modules/captchas/helpers/add_error) }
{% enddoc %}
{% liquid
- function c = 'modules/captchas/helpers/errors_hash', errors: errors
-
# `!= true` (not `== false`): a nil/missing success field must fail closed too — otherwise a
# 200 JSON body without `success` would fall through to the score check and, with min_score 0,
# pass without any provider verdict.
if body.success != true
- assign messages = ["modules/captchas/errors.verification_failed"]
- assign merged = c | hash_merge: captcha: messages
+ function merged = 'modules/captchas/helpers/add_error', errors: errors, field_name: 'captcha', key: 'modules/captchas/errors.verification_failed'
assign result = { "valid": false, "errors": merged }
return result
endif
if expected_action != blank
if body.action != expected_action
- assign messages = ["modules/captchas/errors.action_mismatch"]
- assign merged = c | hash_merge: captcha: messages
+ function merged = 'modules/captchas/helpers/add_error', errors: errors, field_name: 'captcha', key: 'modules/captchas/errors.action_mismatch'
assign result = { "valid": false, "errors": merged }
return result
endif
@@ -34,10 +31,11 @@
assign score = body.score | default: 0.0 | plus: 0.0
if score >= threshold
+ # The pass path still normalizes: errors may arrive nil/empty and callers merge into it.
+ function c = 'modules/captchas/helpers/errors_hash', errors: errors
assign result = { "valid": true, "errors": c }
else
- assign messages = ["modules/captchas/errors.low_score"]
- assign merged = c | hash_merge: captcha: messages
+ function merged = 'modules/captchas/helpers/add_error', errors: errors, field_name: 'captcha', key: 'modules/captchas/errors.low_score'
assign result = { "valid": false, "errors": merged }
endif
diff --git a/pos-module-captchas-recaptcha3/pos-module.json b/pos-module-captchas-recaptcha3/pos-module.json
index bde02b7..589c5da 100644
--- a/pos-module-captchas-recaptcha3/pos-module.json
+++ b/pos-module-captchas-recaptcha3/pos-module.json
@@ -1,9 +1,9 @@
{
"name": "pOS Captchas reCAPTCHA v3",
"machine_name": "captchas_recaptcha3",
- "version": "1.0.0",
+ "version": "1.1.0",
"dependencies": {
- "captchas": "^1.0.0"
+ "captchas": "^1.1.0"
},
"devDependencies": {
"tests": "^1.3.4"
diff --git a/pos-module-captchas-turnstile/CHANGELOG.md b/pos-module-captchas-turnstile/CHANGELOG.md
index b1a9090..19e6385 100644
--- a/pos-module-captchas-turnstile/CHANGELOG.md
+++ b/pos-module-captchas-turnstile/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## 1.1.0
+
+- Errors are now recorded with `modules/captchas/helpers/add_error`, so `result.errors` holds
+ translated messages instead of `modules/captchas/errors.*` keys — drop any `| t` you apply
+ to them. Requires `captchas ^1.1.0`.
+
## 1.0.0
Initial release. Cloudflare Turnstile provider for the
diff --git a/pos-module-captchas-turnstile/README.md b/pos-module-captchas-turnstile/README.md
index de13074..2121e52 100644
--- a/pos-module-captchas-turnstile/README.md
+++ b/pos-module-captchas-turnstile/README.md
@@ -94,6 +94,9 @@ answer maps to `modules/captchas/errors.verification_failed`. Turnstile secrets
per-widget, so `expected_sitekey` is not needed; `expected_hostname` (handled by the
abstraction) works with the `hostname` field Turnstile returns.
+Errors are stored via `modules/captchas/helpers/add_error`, which translates the message as it
+is set — `result.errors.captcha` holds display-ready text, not translation keys.
+
> ⚠️ **Billing** — the server-side verify call is a normal, billable platformOS API call;
> each `verify` performs one outbound `siteverify` request.
diff --git a/pos-module-captchas-turnstile/app/lib/test/captchas_turnstile/verify_test.liquid b/pos-module-captchas-turnstile/app/lib/test/captchas_turnstile/verify_test.liquid
index 1ae616d..cce7614 100644
--- a/pos-module-captchas-turnstile/app/lib/test/captchas_turnstile/verify_test.liquid
+++ b/pos-module-captchas-turnstile/app/lib/test/captchas_turnstile/verify_test.liquid
@@ -13,4 +13,21 @@
function r_fail = 'modules/captchas/commands/captcha/verify', provider: 'turnstile', secret: '2x0000000000000000000000000000000AA', token: 'XXXX.DUMMY.TOKEN.XXXX'
function contract = 'modules/tests/assertions/not_valid_object', contract: contract, field_name: 'testkey_fail', object: r_fail
function contract = 'modules/tests/assertions/presence', contract: contract, field_name: 'captcha', object: r_fail.errors
+
+ # The message is stored translated, not as a raw key: this provider module sets the error via
+ # modules/captchas/helpers/add_error, which resolves a modules/captchas/* key from inside
+ # modules/captchas_turnstile. Callers render errors verbatim, so a raw key would leak to users.
+ # Reuses r_fail — no extra billable call.
+ assign fail_key = 'modules/captchas/errors.verification_failed'
+ assign fail_message = r_fail.errors.captcha | first
+ assign expected_message = fail_key | t
+ function contract = 'modules/tests/assertions/equal', contract: contract, given: fail_message, expected: expected_message, field_name: 'testkey_fail_message_translated'
+
+ # The check above alone would pass if `| t` silently returned the key on both sides, so assert
+ # the stored value is not the key itself — that is the failure mode this guards against.
+ assign is_raw_key = false
+ if fail_message == fail_key
+ assign is_raw_key = true
+ endif
+ function contract = 'modules/tests/assertions/equal', contract: contract, given: is_raw_key, expected: false, field_name: 'testkey_fail_message_not_raw_key'
%}
diff --git a/pos-module-captchas-turnstile/app/views/pages/verify.liquid b/pos-module-captchas-turnstile/app/views/pages/verify.liquid
index 47a1436..1449cf8 100644
--- a/pos-module-captchas-turnstile/app/views/pages/verify.liquid
+++ b/pos-module-captchas-turnstile/app/views/pages/verify.liquid
@@ -22,10 +22,14 @@ method: post
Submitted message: {{ context.params.message }}
{% else %}
❌ Captcha failed ({{ result.provider }})
+ {% comment %}
+ result.errors holds already-translated messages (the commands translate at set time, via
+ modules/captchas/helpers/add_error), so they render as-is — no `| t` here.
+ {% endcomment %}
{% for error in result.errors %}
- {% for key in error[1] %}
- - {{ key | t }}
+ {% for message in error[1] %}
+ - {{ message }}
{% endfor %}
{% endfor %}
diff --git a/pos-module-captchas-turnstile/modules/captchas_turnstile/public/lib/commands/verify.liquid b/pos-module-captchas-turnstile/modules/captchas_turnstile/public/lib/commands/verify.liquid
index a5c8353..caf5c94 100644
--- a/pos-module-captchas-turnstile/modules/captchas_turnstile/public/lib/commands/verify.liquid
+++ b/pos-module-captchas-turnstile/modules/captchas_turnstile/public/lib/commands/verify.liquid
@@ -3,7 +3,8 @@
api_call_send and normalize the result. Part of the captchas provider contract —
dispatched by modules/captchas/commands/captcha/verify/execute.
@param {object} object - validated object: { secret, token, remote_ip, errors }.
- @returns {object} object with success (boolean), response (hash), valid, and errors.
+ @returns {object} object with success (boolean), response (hash), valid, and errors
+ (translated messages, via modules/captchas/helpers/add_error).
{% enddoc %}
{% liquid
assign payload = { "secret": object.secret, "token": object.token, "remote_ip": object.remote_ip }
@@ -11,11 +12,10 @@
graphql result = 'modules/captchas_turnstile/api_call/send', data: payload, name: 'modules/captchas_turnstile/siteverify'
assign call = result.api_call_send
- function c = 'modules/captchas/helpers/errors_hash', errors: object.errors
if call.errors != blank
- assign messages = ["modules/captchas/errors.request_failed"]
- assign object.errors = c | hash_merge: captcha: messages
+ function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
+ assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
@@ -23,8 +23,8 @@
assign status = call.response.status
if status != 200
- assign messages = ["modules/captchas/errors.request_failed"]
- assign object.errors = c | hash_merge: captcha: messages
+ function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
+ assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
@@ -36,8 +36,8 @@
assign raw_body = call.response.body | strip
assign first_char = raw_body | slice: 0, 1
if first_char != '{'
- assign messages = ["modules/captchas/errors.request_failed"]
- assign object.errors = c | hash_merge: captcha: messages
+ function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
+ assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
@@ -50,8 +50,8 @@
if body.success
assign object.valid = true
else
- assign messages = ["modules/captchas/errors.verification_failed"]
- assign object.errors = c | hash_merge: captcha: messages
+ function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.verification_failed'
+ assign object.errors = updated_errors
assign object.valid = false
endif
diff --git a/pos-module-captchas-turnstile/pos-module.json b/pos-module-captchas-turnstile/pos-module.json
index 4d7056f..7eebac4 100644
--- a/pos-module-captchas-turnstile/pos-module.json
+++ b/pos-module-captchas-turnstile/pos-module.json
@@ -1,9 +1,9 @@
{
"name": "pOS Captchas Turnstile",
"machine_name": "captchas_turnstile",
- "version": "1.0.0",
+ "version": "1.1.0",
"dependencies": {
- "captchas": "^1.0.0"
+ "captchas": "^1.1.0"
},
"devDependencies": {
"tests": "^1.3.4"
diff --git a/pos-module-captchas/CHANGELOG.md b/pos-module-captchas/CHANGELOG.md
index f8d5fb9..4dea297 100644
--- a/pos-module-captchas/CHANGELOG.md
+++ b/pos-module-captchas/CHANGELOG.md
@@ -1,5 +1,19 @@
# Changelog
+## 1.1.0
+
+- `object.errors` now holds **translated messages** instead of `modules/captchas/errors.*`
+ translation keys, matching `modules/core/helpers/register_error` and the rest of the
+ platformOS modules. Callers can pass `result.errors[field]` straight to
+ `modules/common-styling/forms/error_list` (which renders each entry raw); previously that
+ printed a raw key to end users.
+- New `modules/captchas/helpers/add_error` — translates a `modules/captchas/errors.*` key and
+ **appends** it to a field, so multiple errors can accumulate on one field (the previous
+ `hash_merge` replaced the field's array). This is now the documented way for provider
+ modules to record errors; see the provider contract in the README.
+- **Migration:** drop any `| t` you apply to values from `result.errors` — the strings are
+ already translated. Provider modules must depend on `captchas ^1.1.0`.
+
## 1.0.0
Initial release.
diff --git a/pos-module-captchas/README.md b/pos-module-captchas/README.md
index 6f26bdd..b999900 100644
--- a/pos-module-captchas/README.md
+++ b/pos-module-captchas/README.md
@@ -105,11 +105,19 @@ provider or key (see [Keys are caller-supplied](#keys-are-caller-supplied)):
%}
```
-Render errors with `| t` — `result.errors` maps fields to arrays of translation keys:
+`result.errors` maps fields to arrays of **already-translated messages** (the commands
+translate at set time, like `modules/core/helpers/register_error`), so they render as-is — no
+`| t` at the call site, and they can go straight into the shared form-error partial:
+
+```liquid
+{% render 'modules/common-styling/forms/error_list', name: 'captcha', errors: result.errors.captcha %}
+```
+
+Or render them all:
```liquid
{% for error in result.errors %}
- {% for key in error[1] %}{{ key | t }}
{% endfor %}
+ {% for message in error[1] %}{{ message }}
{% endfor %}
{% endfor %}
```
@@ -196,11 +204,14 @@ Returns:
| `token_field` | The form field the token was read from. |
| `response` | Parsed provider response (hash). |
| `score` / `action` | Score-based providers only. |
-| `errors` | Hash of field → array of translation keys (`modules/captchas/errors.*`). |
+| `errors` | Hash of field (`token`, `secret`, `provider`, `captcha`) → array of translated messages, ready to display. |
-Error keys — all shipped by this module (en + pl), so callers deal with one namespace
-regardless of provider: `token_missing`, `secret_missing`, `unsupported_provider`,
-`request_failed`, `verification_failed`, `hostname_mismatch`, `low_score`, `action_mismatch`.
+Messages are translated when the error is set, so no `| t` is needed at the call site — see
+[Usage](#usage). The underlying keys (`modules/captchas/errors.*`) are all
+shipped by this module (en + pl), so the copy is one namespace regardless of provider:
+`token_missing`, `secret_missing`, `unsupported_provider`, `request_failed`,
+`verification_failed`, `hostname_mismatch`, `low_score`, `action_mismatch`. Override any of
+them the usual way — copy `modules/captchas/public/translations/.yml` into your app.
## How verification works
@@ -226,7 +237,7 @@ exempt** from API usage billing. Each `verify` performs one outbound `siteverify
## Writing a provider module
A provider is a separate platformOS module named `captchas_` (``: lowercase
-`a-z0-9_`), with `"dependencies": { "captchas": "^1.0.0" }`. The abstraction dispatches to
+`a-z0-9_`), with `"dependencies": { "captchas": "^1.1.0" }`. The abstraction dispatches to
it purely by naming convention — no registration anywhere. It must expose:
1. **`modules/captchas_/widget`** (`public/views/partials/widget.liquid`) — receives
@@ -244,9 +255,18 @@ it purely by naming convention — no registration anywhere. It must expose:
- `success` — the provider's raw verdict,
- `valid` — the final boolean; **fail closed** on transport errors, non-200 responses,
non-JSON bodies, and a missing success field,
- - `errors` — merged via `modules/captchas/helpers/errors_hash`, using
- `modules/captchas/errors.*` keys (`request_failed`, `verification_failed`, and for
- score-based providers `low_score` / `action_mismatch`).
+ - `errors` — set via `modules/captchas/helpers/add_error`, which translates the message and
+ appends it to the field. Pass `errors: object.errors`, `field_name: 'captcha'`, and a
+ `modules/captchas/errors.*` key (`request_failed`, `verification_failed`, and for
+ score-based providers `low_score` / `action_mismatch`):
+
+ ```liquid
+ function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
+ assign object.errors = updated_errors
+ ```
+
+ Do not store the raw key: callers display `object.errors` directly. Add your own keys in
+ your module's translations if you need provider-specific copy.
The incoming `object` carries `secret`, `token`, `remote_ip`, `expected_sitekey`,
`min_score`, `expected_action`, and the current `errors` — read what applies.
diff --git a/pos-module-captchas/app/lib/test/captcha/add_error_test.liquid b/pos-module-captchas/app/lib/test/captcha/add_error_test.liquid
new file mode 100644
index 0000000..2902edc
--- /dev/null
+++ b/pos-module-captchas/app/lib/test/captcha/add_error_test.liquid
@@ -0,0 +1,36 @@
+{% comment %}
+ Unit test (no network): the add_error helper. The contract it locks in is that errors hold
+ *translated messages*, not translation keys — the same convention as
+ modules/core/helpers/register_error. That is what lets a caller pass object.errors[field]
+ straight to modules/common-styling/forms/error_list, which renders each entry raw: storing
+ keys here would print "modules/captchas/errors.token_missing" to end users.
+ Also covers appending, since several errors can land on one field.
+{% endcomment %}
+{% liquid
+ assign token_key = 'modules/captchas/errors.token_missing'
+
+ # nil errors input -> a real hash with the field populated
+ function e1 = 'modules/captchas/helpers/add_error', field_name: 'token', key: token_key
+ function contract = 'modules/tests/assertions/presence', contract: contract, field_name: 'token', object: e1
+
+ # the stored value is the translated message
+ assign stored = e1.token | first
+ assign translated = token_key | t
+ function contract = 'modules/tests/assertions/equal', contract: contract, given: stored, expected: translated, field_name: 'stores_translated_message'
+
+ # ...and specifically not the raw key
+ assign is_raw_key = false
+ if stored == token_key
+ assign is_raw_key = true
+ endif
+ function contract = 'modules/tests/assertions/equal', contract: contract, given: is_raw_key, expected: false, field_name: 'does_not_store_raw_key'
+
+ # appends rather than replaces: two errors on the same field both survive
+ function e2 = 'modules/captchas/helpers/add_error', errors: e1, field_name: 'token', key: 'modules/captchas/errors.verification_failed'
+ function contract = 'modules/tests/assertions/equal', contract: contract, given: e2.token.size, expected: 2, field_name: 'appends_to_same_field'
+
+ # an existing field survives a second field being added
+ function e3 = 'modules/captchas/helpers/add_error', errors: e2, field_name: 'secret', key: 'modules/captchas/errors.secret_missing'
+ function contract = 'modules/tests/assertions/presence', contract: contract, field_name: 'token', object: e3
+ function contract = 'modules/tests/assertions/presence', contract: contract, field_name: 'secret', object: e3
+%}
diff --git a/pos-module-captchas/app/lib/test/captcha/errors_hash_test.liquid b/pos-module-captchas/app/lib/test/captcha/errors_hash_test.liquid
index 34f4451..465da01 100644
--- a/pos-module-captchas/app/lib/test/captcha/errors_hash_test.liquid
+++ b/pos-module-captchas/app/lib/test/captcha/errors_hash_test.liquid
@@ -17,7 +17,7 @@
function contract = 'modules/tests/assertions/equal', contract: contract, given: merged_empty.secret, expected: 'y', field_name: 'empty_hash_mergeable'
# a populated hash passes through: existing keys survive a subsequent merge
- assign existing = '{"token":["modules/captchas/errors.token_missing"]}' | parse_json
+ assign existing = '{"token":["The captcha challenge was not completed."]}' | parse_json
function preserved = 'modules/captchas/helpers/errors_hash', errors: existing
function contract = 'modules/tests/assertions/presence', contract: contract, field_name: 'token', object: preserved
assign merged_more = preserved | hash_merge: secret: 'z'
diff --git a/pos-module-captchas/app/lib/test/captcha/translations_test.liquid b/pos-module-captchas/app/lib/test/captcha/translations_test.liquid
index 7427275..4dcfc0f 100644
--- a/pos-module-captchas/app/lib/test/captcha/translations_test.liquid
+++ b/pos-module-captchas/app/lib/test/captcha/translations_test.liquid
@@ -1,9 +1,9 @@
{% comment %}
- Unit test (no network): every error message key the captcha commands store in
- object.errors must resolve to a real translation. Module translation keys are
+ Unit test (no network): every error message key the captcha commands hand to
+ helpers/add_error must resolve to a real translation. Module translation keys are
auto-prefixed (modules/captchas/) and a missing key fails only at runtime — the
- t filter silently returns the key itself — so a typo here would show a raw
- 'modules/captchas/errors.*' string to end users at display time.
+ t filter silently returns the key itself — so a typo here would put a raw
+ 'modules/captchas/errors.*' string into object.errors, which callers display verbatim.
Keep this list in sync with the keys used in commands/captcha and translations/en.yml.
{% endcomment %}
{% liquid
diff --git a/pos-module-captchas/app/modules/captchas_test/public/lib/commands/verify.liquid b/pos-module-captchas/app/modules/captchas_test/public/lib/commands/verify.liquid
index 9b02efb..46eb4fd 100644
--- a/pos-module-captchas/app/modules/captchas_test/public/lib/commands/verify.liquid
+++ b/pos-module-captchas/app/modules/captchas_test/public/lib/commands/verify.liquid
@@ -7,8 +7,6 @@
@returns {object} object with response, success, valid and errors set
{% enddoc %}
{% liquid
- function c = 'modules/captchas/helpers/errors_hash', errors: object.errors
-
if object.token == 'pass'
assign response = '{"success": true, "hostname": "test.example.com"}' | parse_json
assign object = object | hash_merge: response: response, success: true, valid: true
@@ -16,8 +14,7 @@
endif
assign response = '{"success": false}' | parse_json
- assign messages = ["modules/captchas/errors.verification_failed"]
- assign errors = c | hash_merge: captcha: messages
+ function errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.verification_failed'
assign object = object | hash_merge: response: response, success: false, valid: false, errors: errors
return object
%}
diff --git a/pos-module-captchas/app/views/pages/captcha-verify.liquid b/pos-module-captchas/app/views/pages/captcha-verify.liquid
index 28c73ea..7d4afaf 100644
--- a/pos-module-captchas/app/views/pages/captcha-verify.liquid
+++ b/pos-module-captchas/app/views/pages/captcha-verify.liquid
@@ -27,10 +27,15 @@ method: post
Submitted message: {{ context.params.message }}
{% else %}
❌ Captcha failed ({{ result.provider }})
+ {% comment %}
+ result.errors holds already-translated messages (the commands translate at set time, like
+ modules/core/helpers/register_error), so they render as-is — no `| t` here. This is also why
+ a caller can pass result.errors[field] straight to modules/common-styling/forms/error_list.
+ {% endcomment %}
{% for error in result.errors %}
- {% for key in error[1] %}
- - {{ key | t }}
+ {% for message in error[1] %}
+ - {{ message }}
{% endfor %}
{% endfor %}
diff --git a/pos-module-captchas/modules/captchas/public/lib/commands/captcha/verify/check.liquid b/pos-module-captchas/modules/captchas/public/lib/commands/captcha/verify/check.liquid
index a18775e..1e9f0ec 100644
--- a/pos-module-captchas/modules/captchas/public/lib/commands/captcha/verify/check.liquid
+++ b/pos-module-captchas/modules/captchas/public/lib/commands/captcha/verify/check.liquid
@@ -2,25 +2,23 @@
@description Check phase for captcha verify: require a token + secret and an available
provider (installed provider module, permitted by the optional allow-list).
@param {object} object - normalized object from the build phase.
- @returns {object} object with errors populated and valid set.
+ @returns {object} object with errors populated (translated messages, via helpers/add_error)
+ and valid set.
{% enddoc %}
{% liquid
function c = 'modules/captchas/helpers/errors_hash', errors: object.errors
function provider_supported = 'modules/captchas/helpers/provider_available', provider: object.provider
if object.token == blank
- assign messages = ["modules/captchas/errors.token_missing"]
- assign c = c | hash_merge: token: messages
+ function c = 'modules/captchas/helpers/add_error', errors: c, field_name: 'token', key: 'modules/captchas/errors.token_missing'
endif
if object.secret == blank
- assign messages = ["modules/captchas/errors.secret_missing"]
- assign c = c | hash_merge: secret: messages
+ function c = 'modules/captchas/helpers/add_error', errors: c, field_name: 'secret', key: 'modules/captchas/errors.secret_missing'
endif
unless provider_supported
- assign messages = ["modules/captchas/errors.unsupported_provider"]
- assign c = c | hash_merge: provider: messages
+ function c = 'modules/captchas/helpers/add_error', errors: c, field_name: 'provider', key: 'modules/captchas/errors.unsupported_provider'
endunless
assign object.errors = c
diff --git a/pos-module-captchas/modules/captchas/public/lib/commands/captcha/verify/execute.liquid b/pos-module-captchas/modules/captchas/public/lib/commands/captcha/verify/execute.liquid
index 4ab6b1a..e1e1373 100644
--- a/pos-module-captchas/modules/captchas/public/lib/commands/captcha/verify/execute.liquid
+++ b/pos-module-captchas/modules/captchas/public/lib/commands/captcha/verify/execute.liquid
@@ -5,7 +5,7 @@
@param {object} object - validated object: { provider, secret, token, remote_ip, expected_sitekey,
expected_hostname, min_score, expected_action, errors }.
@returns {object} object with success (boolean), response (hash), valid, and errors
- (plus score/action for score-based providers).
+ (translated messages; plus score/action for score-based providers).
{% enddoc %}
{% liquid
# Re-check availability even though the check phase already did (defense in depth: this
@@ -14,9 +14,8 @@
# siteverify endpoint — never leave object.valid true here.
function provider_is_available = 'modules/captchas/helpers/provider_available', provider: object.provider
unless provider_is_available
- function c = 'modules/captchas/helpers/errors_hash', errors: object.errors
- assign messages = ["modules/captchas/errors.unsupported_provider"]
- assign object.errors = c | hash_merge: provider: messages
+ function c = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'provider', key: 'modules/captchas/errors.unsupported_provider'
+ assign object.errors = c
assign object.success = false
assign object.valid = false
return object
@@ -28,9 +27,8 @@
if object.valid
function hostname_allowed = 'modules/captchas/helpers/hostname_allowed', hostname: object.response.hostname, expected_hostname: object.expected_hostname
unless hostname_allowed
- function c = 'modules/captchas/helpers/errors_hash', errors: object.errors
- assign messages = ["modules/captchas/errors.hostname_mismatch"]
- assign object.errors = c | hash_merge: captcha: messages
+ function c = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.hostname_mismatch'
+ assign object.errors = c
assign object.valid = false
endunless
endif
diff --git a/pos-module-captchas/modules/captchas/public/lib/helpers/add_error.liquid b/pos-module-captchas/modules/captchas/public/lib/helpers/add_error.liquid
new file mode 100644
index 0000000..5d877a2
--- /dev/null
+++ b/pos-module-captchas/modules/captchas/public/lib/helpers/add_error.liquid
@@ -0,0 +1,24 @@
+{% doc %}
+ @description Append a translated error message for a field to an errors hash. Mirrors
+ modules/core/helpers/register_error: the message is translated at set time, so object.errors
+ holds ready-to-display strings — callers can pass them straight to
+ modules/common-styling/forms/error_list, which renders each entry raw. Appends rather than
+ replaces, so several errors can accumulate on the same field.
+ @param {object} [errors] - existing errors container; may be nil, empty, or a populated hash.
+ @param {string} field_name - the field the error belongs to (token, secret, provider, captcha).
+ @param {string} key - translation key, e.g. 'modules/captchas/errors.token_missing'. Module
+ translation keys are auto-prefixed, so the modules/captchas/ prefix is required — including
+ from provider modules, which store this module's error messages.
+ @returns {object} the errors hash with the translated message appended under field_name.
+{% enddoc %}
+{% liquid
+ function result = 'modules/captchas/helpers/errors_hash', errors: errors
+ assign message = key | t
+
+ assign empty_list = []
+ assign field_errors = result[field_name] | default: empty_list
+ assign field_errors << message
+ assign result[field_name] = field_errors
+
+ return result
+%}
diff --git a/pos-module-captchas/pos-module.json b/pos-module-captchas/pos-module.json
index df6ed71..3ecb527 100644
--- a/pos-module-captchas/pos-module.json
+++ b/pos-module-captchas/pos-module.json
@@ -1,7 +1,7 @@
{
"name": "Pos Module Captchas",
"machine_name": "captchas",
- "version": "1.0.0",
+ "version": "1.1.0",
"dependencies": {},
"devDependencies": {
"tests": "^1.3.4"
From de26c35ff34c113dd72126097dd8283195bd387f Mon Sep 17 00:00:00 2001
From: lk316 <51822928+lk316@users.noreply.github.com>
Date: Wed, 29 Jul 2026 18:21:16 +0300
Subject: [PATCH 4/4] changelog updated
---
pos-module-captchas-hcaptcha/CHANGELOG.md | 1 +
pos-module-captchas-recaptcha/CHANGELOG.md | 1 +
pos-module-captchas-recaptcha3/CHANGELOG.md | 1 +
pos-module-captchas-turnstile/CHANGELOG.md | 1 +
pos-module-captchas/CHANGELOG.md | 1 +
5 files changed, 5 insertions(+)
diff --git a/pos-module-captchas-hcaptcha/CHANGELOG.md b/pos-module-captchas-hcaptcha/CHANGELOG.md
index fc45747..a4d677c 100644
--- a/pos-module-captchas-hcaptcha/CHANGELOG.md
+++ b/pos-module-captchas-hcaptcha/CHANGELOG.md
@@ -5,6 +5,7 @@
- Errors are now recorded with `modules/captchas/helpers/add_error`, so `result.errors` holds
translated messages instead of `modules/captchas/errors.*` keys — drop any `| t` you apply
to them. Requires `captchas ^1.1.0`.
+- Other code improvements: internal cleanups with no change to behavior or the public API.
## 1.0.0
diff --git a/pos-module-captchas-recaptcha/CHANGELOG.md b/pos-module-captchas-recaptcha/CHANGELOG.md
index d5be26b..0d28ff2 100644
--- a/pos-module-captchas-recaptcha/CHANGELOG.md
+++ b/pos-module-captchas-recaptcha/CHANGELOG.md
@@ -5,6 +5,7 @@
- Errors are now recorded with `modules/captchas/helpers/add_error`, so `result.errors` holds
translated messages instead of `modules/captchas/errors.*` keys — drop any `| t` you apply
to them. Requires `captchas ^1.1.0`.
+- Other code improvements: internal cleanups with no change to behavior or the public API.
## 1.0.0
diff --git a/pos-module-captchas-recaptcha3/CHANGELOG.md b/pos-module-captchas-recaptcha3/CHANGELOG.md
index 92842e5..f645152 100644
--- a/pos-module-captchas-recaptcha3/CHANGELOG.md
+++ b/pos-module-captchas-recaptcha3/CHANGELOG.md
@@ -6,6 +6,7 @@
and `helpers/score_check`), so `result.errors` holds translated messages instead of
`modules/captchas/errors.*` keys — drop any `| t` you apply to them. Requires
`captchas ^1.1.0`.
+- Other code improvements: internal cleanups with no change to behavior or the public API.
## 1.0.0
diff --git a/pos-module-captchas-turnstile/CHANGELOG.md b/pos-module-captchas-turnstile/CHANGELOG.md
index 19e6385..72199b3 100644
--- a/pos-module-captchas-turnstile/CHANGELOG.md
+++ b/pos-module-captchas-turnstile/CHANGELOG.md
@@ -5,6 +5,7 @@
- Errors are now recorded with `modules/captchas/helpers/add_error`, so `result.errors` holds
translated messages instead of `modules/captchas/errors.*` keys — drop any `| t` you apply
to them. Requires `captchas ^1.1.0`.
+- Other code improvements: internal cleanups with no change to behavior or the public API.
## 1.0.0
diff --git a/pos-module-captchas/CHANGELOG.md b/pos-module-captchas/CHANGELOG.md
index 4dea297..b3a4745 100644
--- a/pos-module-captchas/CHANGELOG.md
+++ b/pos-module-captchas/CHANGELOG.md
@@ -13,6 +13,7 @@
modules to record errors; see the provider contract in the README.
- **Migration:** drop any `| t` you apply to values from `result.errors` — the strings are
already translated. Provider modules must depend on `captchas ^1.1.0`.
+- Other code improvements: internal cleanups with no change to behavior or the public API.
## 1.0.0