Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,15 @@ Management; the backend needs its own runtime switch for a system-wide shutdown.
## Donation checkout presentation

Donation links navigate to `/donate/`, which embeds the existing Inttegro checkout
using `@inttegro/js` 0.5.8. The purchase intent, payment processing, hosted fields,
using `@inttegro/js` 0.7.1. The purchase intent, payment processing, hosted fields,
and invoice/receipt features are unchanged. The old presentation flag is no longer
used; existing `modal` or `redirect` environment values do not change this flow.

The embedded payment form uses the SDK's semantic appearance controls to carry
DevCongress's cream, pink, yellow, black, and muted-gray palette into the hosted
surface. Button, input, selection, focus, typography, and radius values are set
explicitly while Inttegro continues to own the secure fields and payment states.

Prepared order IDs retain the original same-tab session-storage key. Reloads and
retries reopen that order. Completion and errors keep the reference; uncertain
payment outcomes never automatically start a new donation or redirect to a fresh
Expand Down
8 changes: 8 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 2026-10-21 — Match the donation form to DevCongress

- Update the embedded donation checkout to `@inttegro/js` 0.7.1 and apply the
DevCongress palette to its background, surfaces, text, controls, focus state,
typography and radius through the SDK appearance API.
- Remove the persistent prepared-order recovery statement beneath the form while
retaining the guarded restart controls and actual same-tab order recovery.

## 2026-10-19 — Consolidate dependency updates from #66

- Include Astro 7.4.0, Zod 4.6.7, Cloudflare Workers types 5.20261016.1 and Wrangler 4.151.0 in the donation follow-up, preserving Inttegro 0.5.8 and the donation recovery build checks. Raise the Node engine minimum to 22.22.0 to match Astro 7.4.0.
Expand Down
11 changes: 6 additions & 5 deletions docs/donation-checkout.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Donation checkout

The `/donate/` page replaces PR #61's homepage modal. It uses the same Inttegro
purchase intent, `@inttegro/js` 0.5.8, hosted fields, payment processing, locale,
purchase intent, `@inttegro/js` 0.7.1, hosted fields, payment processing, locale,
and document/payment-method features. `/support` remains the hosted redirect.
The old presentation environment flag is ignored.

Expand All @@ -28,10 +28,11 @@ contact support, rather than being sent into a potentially duplicate checkout.

The page reuses the website's Bricolage Grotesque/Source Sans 3 fonts, cream palette,
spacing, responsive layout, and contact information. Hosted fields are rendered in
an Inttegro-controlled cross-origin iframe. Version 0.5.8 supports theme and primary
color, not font, radius, spacing, or custom field styling. Matching those controls
exactly requires a provider-supported extension; the website must not proxy or
modify the hosted payment runtime. The accepted boundary is a site-styled surrounding page with provider-owned fields.
an Inttegro-controlled cross-origin iframe. Version 0.7.1 applies DevCongress's
semantic background, surface, foreground, muted, border, focus, button, input, and
selection colors, along with the SDK's sans-serif typography and shared 8px radius.
The website must not proxy or modify the hosted payment runtime; Inttegro continues
to own the secure controls, validation, and payment states.

## Verification

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"check:donation": "node scripts/check-donation-checkout.mjs"
},
"dependencies": {
"@inttegro/js": "0.5.8",
"@inttegro/js": "0.7.1",
"astro": "^7.4.0",
"zod": "^4.6.7"
},
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 20 additions & 1 deletion scripts/check-donation-checkout.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,27 @@ function harness(storage = new Map(), storageBlocked = false) {
const h = harness();
await h.settle();
assert.equal(h.controllers[0].options.purchaseIntentId, intent);
assert.deepEqual(JSON.parse(JSON.stringify(h.controllers[0].options.appearance)), {
theme: 'light',
colors: {
background: '#FFFFFF',
surface: '#F5F2E8',
foreground: '#111111',
mutedForeground: '#555555',
border: '#E0DDD4',
focus: '#E8117F',
},
button: { background: '#E8117F', foreground: '#FFFFFF', border: '#111111' },
input: { background: '#FFFFFF', foreground: '#111111', border: '#111111' },
selection: { background: '#FEFCE8', foreground: '#111111', border: '#111111' },
typography: { fontFamily: 'sans-serif' },
shape: { borderRadius: 8 },
});
const order = { id: orderId, status: 'requires_payment' };
h.controllers[0].emit({ type: 'orderPrepared', order });
assert.equal(h.storage.get(key), orderId);
assert.deepEqual([...h.storage.keys()], [key]);
assert.doesNotMatch(h.element('[data-checkout-status]').textContent, /prepared order can/i);

const resumed = harness(h.storage);
await resumed.settle();
Expand Down Expand Up @@ -148,6 +165,8 @@ await blocked.click('[data-checkout-retry]');
assert.equal(blocked.controllers.at(-1).options.orderId, orderId, 'memory recovery survives storage failure');

const worker = readFileSync('src/worker.ts', 'utf8');
const donationPage = readFileSync('src/pages/donate.astro', 'utf8');
assert.match(worker, /SUPPORT_PATHS = new Set\(\['\/support', '\/support\/'\]\)/);
assert.doesNotMatch(readFileSync('src/pages/index.astro', 'utf8'), /useDonationModal|InttegroDonationCheckout/);
console.log('PASS donation recovery: legacy resume, ID-only storage, completion/error retention, same-order retries, stale events, pending-payment guard, acknowledged restart, blocked storage, and page routing.');
assert.doesNotMatch(donationPage, /A prepared order can/i);
console.log('PASS donation recovery: branded checkout appearance, legacy resume, ID-only storage, completion/error retention, same-order retries, stale events, pending-payment guard, acknowledged restart, blocked storage, and page routing.');
43 changes: 40 additions & 3 deletions src/lib/donation-checkout.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,44 @@
import { loadInttegro, type CheckoutController, type CheckoutOrder } from '@inttegro/js';
import {
loadInttegro,
type CheckoutAppearance,
type CheckoutController,
type CheckoutOrder,
} from '@inttegro/js';

const orderPattern = /^or_[A-Za-z0-9]{20,128}$/;
const intentPattern = /^sale_[A-Za-z0-9]{20,128}$/;
const checkoutAppearance = {
theme: 'light',
colors: {
background: '#FFFFFF',
surface: '#F5F2E8',
foreground: '#111111',
mutedForeground: '#555555',
border: '#E0DDD4',
focus: '#E8117F',
},
button: {
background: '#E8117F',
foreground: '#FFFFFF',
border: '#111111',
},
input: {
background: '#FFFFFF',
foreground: '#111111',
border: '#111111',
},
selection: {
background: '#FEFCE8',
foreground: '#111111',
border: '#111111',
},
typography: {
fontFamily: 'sans-serif',
},
shape: {
borderRadius: 8,
},
} satisfies CheckoutAppearance;

export function initializeDonationCheckout(root: ParentNode = document): void {
const page = root.querySelector<HTMLElement>('[data-donation-page]');
Expand Down Expand Up @@ -81,7 +118,7 @@ export function initializeDonationCheckout(root: ParentNode = document): void {
remember(order);

const messages: Record<CheckoutOrder['status'], string> = {
requires_payment: 'Your donation is ready. Complete payment below. This prepared order can resume in this tab.',
requires_payment: 'Your donation is ready. Complete payment below.',
requires_action: 'Your payment needs confirmation. Follow the instructions below; do not start another donation.',
processing: 'Your payment is processing. Keep this reference and check this donation again before paying again.',
paid: 'Thank you for supporting DevCongress. Your payment is confirmed. Your receipt is available in checkout below.',
Expand Down Expand Up @@ -111,7 +148,7 @@ export function initializeDonationCheckout(root: ParentNode = document): void {

const checkout = inttegro.createCheckout({
...(orderId ? { orderId } : { purchaseIntentId }),
appearance: { primaryColor: '#E8117F', theme: 'light' },
appearance: checkoutAppearance,
features: {
showLineItems: true,
showInvoiceDownload: true,
Expand Down
19 changes: 6 additions & 13 deletions src/pages/donate.astro
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,12 @@ const hostedCheckout = `https://pages.inttegro.com/buy/${site.inttegro_purchase_
<a data-checkout-fallback href={hostedCheckout} hidden>Open hosted checkout</a>
</div>
<div id="donation-checkout" data-checkout-frame></div>
<div class="recovery-note">
<p>A prepared order can resume when you reload this tab. Details entered before that point are not saved. Closing the tab may end recovery.</p>
<details data-checkout-restart hidden>
<summary>Start a new donation</summary>
<p>A payment may still be processing. Starting again could cause a duplicate charge. Check your payment prompt or receipt first, and keep your current reference.</p>
<label><input type="checkbox" data-restart-acknowledge /> I have checked my payment status and want to start a new donation.</label>
<button class="retry-button" data-start-new type="button" disabled>Start new donation</button>
</details>
</div>
<details class="recovery-note" data-checkout-restart hidden>
<summary>Start a new donation</summary>
<p>A payment may still be processing. Starting again could cause a duplicate charge. Check your payment prompt or receipt first, and keep your current reference.</p>
<label><input type="checkbox" data-restart-acknowledge /> I have checked my payment status and want to start a new donation.</label>
<button class="retry-button" data-start-new type="button" disabled>Start new donation</button>
</details>
<noscript><p class="recovery-note">Enable JavaScript to recover or continue checkout. If you already started a payment, do not start another one. <a href="mailto:hello@devcongress.org">Contact us with your payment reference</a> for help.</p></noscript>
</section>
<aside class="support-info" aria-label="About your support">
Expand Down Expand Up @@ -284,10 +281,6 @@ const hostedCheckout = `https://pages.inttegro.com/buy/${site.inttegro_purchase_
border-top: 1px solid var(--border-light);
}

.recovery-note details {
margin-top: 16px;
}

summary, .retry-button {
min-height: 44px;
cursor: pointer;
Expand Down
Loading