Add Dynamic Payment Templates authoring kit #trivial - #7
Conversation
🛡️ SDD Check — action requiredI couldn't detect an SDD in this PR. Please check one option below (requires write access to the repo):
|
Introduce the partner-facing contract, preview config, and a complete reference example with HTML, CSS, i18n files, and raster assets.
Wire @vtex/payment-templates-validator with shared lib helpers and npm scripts to validate the reference bundle before submission.
Wrap template fragments into sandboxed HTML with CSP and i18n runtime, and serve them through connect middleware during local development.
Replace the inline Pink partial with a sandboxed iframe host, locale switcher, and trimmed checkout styles scoped to the current mock.
Drop the Angular partial, Less/Sass styles, shell i18n files, and unused checkout images that belonged to the old inline template flow.
Simplify Grunt to connect + livereload with preview middleware, refresh the README for the new workflow, and trim obsolete gitignore entries.
966d814 to
22c25b7
Compare
Run template validation on grunt startup and watch, allow data: images in iframe CSP, restore payment-tab icons, and use full locale tags in the language switcher.
fdaciuk
left a comment
There was a problem hiding this comment.
Bloqueando por: CSP com script-src 'unsafe-inline' sobre HTML de parceiro sem gate de validação no preview, escape de symlink no middleware, medição de altura que corta bundles com mais de uma raiz, filtro de nomes que impede o validador local de reprovar, e lockfile sem integrity em 194 de 200 pacotes. Detalhes nos comentários.
| "default-src 'none'", | ||
| "style-src 'self'", | ||
| "img-src 'self' data:", | ||
| "script-src 'unsafe-inline'", |
There was a problem hiding this comment.
script-src 'unsafe-inline' libera exatamente o que o CONTRACT.md:60-61 proíbe (<script> e handlers inline), e o HTML do parceiro é colado cru na linha 60. Como o preview não passa pelo validador (lib/preview-middleware.js:59-60 chama loadBundle + wrapTemplate direto), um bundle reprovado na validação ainda executa script. Use nonce ou hash apenas para o runtime injetado.
|
|
||
| return [ | ||
| '<!doctype html>', | ||
| '<html lang="' + defaultLocale + '">', |
There was a problem hiding this comment.
defaultLocale entra no atributo sem escape, e lib/preview-config.js:12 só checa se está preenchido. Um valor contendo "> fecha o atributo e, com o CSP inline liberado, executa. Valide contra ^[a-z]{2}-[A-Z]{2}$ — o regex que o CONTRACT.md:28 já define. Importa mais porque o comentário das linhas 8-13 prevê reuso deste módulo no handler de upload, onde o valor vem do parceiro.
| return; | ||
| } | ||
|
|
||
| const relativePath = decodeURIComponent(req.url.slice(BUNDLE_PREFIX.length).split('?')[0]); |
There was a problem hiding this comment.
decodeURIComponent sem proteção: GET /template-bundle/% lança URIError e devolve 500 com stack trace, em vez do 404 limpo que a linha 79 faz questão de retornar. Envolva em try/catch e responda 400.
|
|
||
| const filePath = path.join(config.bundlePath, relativePath); | ||
| const normalizedRoot = path.resolve(config.bundlePath) + path.sep; | ||
| if (!path.resolve(filePath).startsWith(normalizedRoot)) { |
There was a problem hiding this comment.
A contenção compara só o caminho textual; existsSync, statSync e createReadStream seguem symlink. Um bundle de parceiro (entregue como pasta, CONTRACT.md:128) com asset-logo.png -> ../../../../.ssh/id_rsa passa pelo 403 e é servido. Resolva com fs.realpathSync antes de comparar.
| const assets = []; | ||
|
|
||
| for (const entry of fs.readdirSync(bundleDir)) { | ||
| const i18nMatch = /^i18n-([a-z]{2}-[A-Z]{2})\.json$/.exec(entry); |
There was a problem hiding this comment.
O filtro por nome roda antes de entregar ao validador, então as regras de nomenclatura nunca falham localmente: i18n-es.json é descartado em silêncio e npm run validate:reference imprime ok: true, apesar de o CONTRACT.md:16 dizer que esse nome é rejeitado. Mesmo problema na linha 29 com assets fora do padrão asset-*. Entregue todos os arquivos ao validador e deixe ele reprovar.
| { | ||
| "bundleDir": "reference", | ||
| "defaultLocale": "pt-BR", | ||
| "icon": "asset-logo.png", |
There was a problem hiding this comment.
asset-logo.png tem 300×300, mas o CONTRACT.md:18 diz que o ícone cabe numa caixa de 160×160. Como o ícone não é validado, nada acusa — e esta pasta é apresentada como exemplo completo e válido (CONTRACT.md:5). Ajuste a imagem ou a regra.
|
|
||
| The payment step renders your bundle in an iframe with `sandbox="allow-scripts"`, applies translations from the wrapped document, resizes on content changes, and accepts locale switches via `postMessage` — matching the checkout host contract. | ||
|
|
||
| Images inside the iframe may use bundle-local files or `data:` URLs on `<img src>` (including `data:image/svg+xml`). Inline SVG in CSS `url(data:…)` is not supported. |
There was a problem hiding this comment.
Contradiz a linha 63, que lista data:... entre os itens proibidos em todos os casos. Defina qual das duas vale — hoje o parceiro não tem como saber o que o validador aplica.
| - `style.css` with bundle-local asset references | ||
| - `asset-logo.png` and `asset-badge.png` | ||
| - `i18n-pt-BR.json` and `i18n-en-US.json` with matching keys | ||
| - `defaultLocale = pt-BR` (set in the validation script and documented above) |
There was a problem hiding this comment.
defaultLocale não é definido no script de validação, e sim em template/preview.config.json.
| "pay": { | ||
| "title": "Example Pay", | ||
| "lead": "Ao finalizar a compra, você será redirecionado(a) para concluir o pagamento.", | ||
| "benefitGuaranteedTitle": "Guarantido", |
There was a problem hiding this comment.
"Guarantido" → "Garantido". Esta é a pasta que o CONTRACT.md:146 manda copiar, então o erro se propaga para os templates dos parceiros.
| @@ -1,5 +1,2 @@ | |||
| .DS_Store | |||
| .sass-cache | |||
| .grunt | |||
There was a problem hiding this comment.
.grunt saiu do ignore, mas o projeto continua rodando grunt. .sass-cache e build saíram com razão; este parece ter ido junto sem querer.
Summary
allow-scriptsonly) inside the checkout shell mock, with{ locale }/{ height }postMessage, locale switching, and dynamic iframe height.@vtex/payment-templates-validatorongruntstartup and on saves totemplate/orlib/— validation replaces manual smoke checks in the dev loop.img-src 'self' data:for<img src>data URLs).fr-FR,es-ES), and trimmed checkout CSS scoped to the current mock.Test plan
npm iandgrunt— validation must pass (ok: true) before the server startspt-BR,en-US,fr-FR,es-ES) and verify iframe content updates; unavailable locales should fall back todefaultLocaletemplate/reference/and confirm livereload refreshes the preview; saving an invalid bundle should fail validation on watchnpm run validate:referencemanually and expectok: true