Skip to content

chore: provide more scalable interface for dynamic checkout#250

Open
jakubjasinsky wants to merge 2 commits intomasterfrom
init-dynamic-checkout
Open

chore: provide more scalable interface for dynamic checkout#250
jakubjasinsky wants to merge 2 commits intomasterfrom
init-dynamic-checkout

Conversation

@jakubjasinsky
Copy link
Copy Markdown
Collaborator

Summary

Redesign initDynamicCheckout public config interface to eliminate _global nesting, improve naming, and support per-payment-method overrides via a flat-then-override pattern. Deprecate setupDynamicCheckout.

Changes

  • New DynamicCheckoutInitConfigType with flat top-level options, theme, text and a paymentMethodOverrides dictionary for per-method config
  • DynamicCheckoutPaymentMethodKey union type for autocomplete on known method keys (card, applepay, googlepay) while allowing arbitrary gateway names
  • New normalizeDynamicCheckoutConfig() maps the flat public shape into the internal _global-based DynamicCheckoutMethodScopedConfig structure — zero changes to downstream payment method code
  • setupDynamicCheckout marked @deprecated with JSDoc
  • Old types (DynamicCheckoutPublicConfigType, DynamicCheckoutInitPublicConfigType) and their normalizers marked @deprecated
  • Existing example switched to setupDynamicCheckout, new dynamic-checkout-init example showcases initDynamicCheckout
  • Both examples display a live "Code Example" preview reflecting current form config
  • Restructured payment-config.ts with clear section headers (internal helpers → shared types → current API → deprecated API → normalization → runtime class)

Additional Context

  • Inspired by Stripe's flat appearance API and Adyen's paymentMethodsConfiguration pattern — simple cases stay flat, per-method overrides are opt-in
  • Scope limited to public API + normalization layer; DynamicCheckoutPaymentConfig class internals unchanged
  • All code compiles to ES5 via TypeScript (target: "es5")

@jakubjasinsky jakubjasinsky requested review from a team and andrzej-zmudzinski-cko and removed request for a team March 25, 2026 15:27
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR redesigns the public Dynamic Checkout initialization API by introducing a flatter initDynamicCheckout config shape with per-payment-method overrides, while keeping downstream checkout behavior stable via a normalization layer.

Changes:

  • Added initDynamicCheckout (new config types + normalizer) and deprecated setupDynamicCheckout.
  • Updated Dynamic Checkout internals to consume a normalized method-scoped config (options/theme/text/additionalData per method).
  • Updated examples and docs UI to showcase both APIs and display a live “Code Example” preview.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/processout/processout.ts Deprecates setupDynamicCheckout, adds initDynamicCheckout, and routes both through normalization.
src/dynamic-checkout/dynamic-checkout.ts Updates DynamicCheckout constructor to accept normalized config; removes legacy theme plumbing.
src/dynamic-checkout/config/payment-config.ts Introduces new public init config/types, normalization functions, and method-scoped runtime accessors.
src/dynamic-checkout/views/payment-methods.ts Removes theme propagation when constructing payment method UI elements.
src/dynamic-checkout/payment-methods/card.ts Switches card method to method-scoped options/theme/text overrides.
src/dynamic-checkout/payment-methods/apm.ts Switches APM method to method-scoped options/theme/text overrides and additional data lookup.
src/dynamic-checkout/payment-methods/native-apm.ts Switches native APM to method-scoped theme/text/options lookups.
src/dynamic-checkout/payment-methods/saved-card.ts Switches saved card to method-scoped options (capture/fallback/status-message).
src/dynamic-checkout/payment-methods/saved-apm.ts Switches saved APM to method-scoped options and method-scoped additional data lookup.
src/dynamic-checkout/clients/google-pay.ts Applies method-scoped options for Google Pay payment flow.
src/dynamic-checkout/clients/apple-pay.ts Applies method-scoped options for Apple Pay payment flow.
examples/dynamic-checkout/styles.css Adds styling for the new code preview block.
examples/dynamic-checkout/index.html Adds “Code Example” preview and updates example wiring for deprecated setup API.
examples/dynamic-checkout-init/index.html Adds a new init-based example demonstrating the flat + override config pattern.
index.html Adds a link to the new initDynamicCheckout example.
package.json Bumps package version to 1.8.6.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +168 to +191
if (config.paymentMethodOverrides) {
for (const key in config.paymentMethodOverrides) {
const override = config.paymentMethodOverrides[key]

if (!override) {
continue
}

if (override.options) {
optionsByMethod[key] = { ...override.options }
}

if (override.theme) {
themeByMethod[key] = { ...override.theme }
}

if (override.text) {
textOverridesByMethod[key] = { ...override.text }
}

if (override.additionalData) {
additionalPaymentDataByMethod[key] = { ...override.additionalData }
}
}
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

normalizeDynamicCheckoutConfig copies paymentMethodOverrides keys into method-scoped maps using for...in without an own-property guard and without filtering reserved keys. Since override keys are arbitrary strings, this can again enable inherited key enumeration and prototype-pollution via keys like __proto__. Use Object.keys()/hasOwnProperty and reject __proto__/constructor/prototype (or build the maps with Object.create(null)).

Copilot uses AI. Check for mistakes.
Comment on lines +138 to +139
allow_fallback_to_sale: methodOptions.allowFallbackToSale,
save_source: canSavePaymentMethod && methodOptions.enforceSavePaymentMethod,
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

methodOptions comes from a merged config where option properties are optional; if a consumer passes an option key with an undefined value, spreading can overwrite the default boolean and make allowFallbackToSale/enforceSavePaymentMethod undefined. Here those values are forwarded directly into the payment request (allow_fallback_to_sale, save_source), which should be booleans. Either coerce to booleans at usage (consistent with other payment methods) or ensure the normalizer/merger drops undefined values so getOptionsForMethod always returns booleans.

Suggested change
allow_fallback_to_sale: methodOptions.allowFallbackToSale,
save_source: canSavePaymentMethod && methodOptions.enforceSavePaymentMethod,
allow_fallback_to_sale: !!methodOptions.allowFallbackToSale,
save_source: !!(canSavePaymentMethod && methodOptions.enforceSavePaymentMethod),

Copilot uses AI. Check for mistakes.
'const client = new ProcessOut.ProcessOut("' + formValues.projectId + '")\n\n' +
"const checkout = client.setupDynamicCheckout(\n" +
" " + JSON.stringify(config, null, 2).replace(/\n/g, "\n ") + ",\n" +
" " + JSON.stringify(theme, null, 2).replace(/\n/g, "\n ") + ",\n" +
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generated code preview includes an extra trailing comma after the theme argument (...,\n)), which makes the displayed snippet invalid JavaScript for some environments. Drop the comma after the second argument so the preview matches a runnable setupDynamicCheckout(config, theme) call.

Suggested change
" " + JSON.stringify(theme, null, 2).replace(/\n/g, "\n ") + ",\n" +
" " + JSON.stringify(theme, null, 2).replace(/\n/g, "\n ") + "\n" +

Copilot uses AI. Check for mistakes.
name="enforceSavePaymentMethod"
type="checkbox"
/>
<span>Enforce Safe Payment Method</span>
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in the form label: it says "Enforce Safe Payment Method" but the config field is enforceSavePaymentMethod. Consider updating the label text to "Enforce Save Payment Method" (and, if you rename the input id, update the corresponding DOM lookup in this same file).

Suggested change
<span>Enforce Safe Payment Method</span>
<span>Enforce Save Payment Method</span>

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +27
const clonedConfig: DynamicCheckoutMethodScopedConfig<T> = {}

if (!config) {
return clonedConfig
}

for (const key in config) {
if (config[key]) {
clonedConfig[key] = {
...config[key],
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cloneMethodScopedConfig iterates with for...in and copies keys directly into a plain object. Because this config originates from user input, this pattern can enumerate inherited properties and allows special keys like __proto__/constructor to mutate the target object's prototype (prototype pollution). Prefer iterating over Object.keys(config) (or hasOwnProperty checks) and/or using Object.create(null) for the cloned container while explicitly rejecting dangerous keys.

Suggested change
const clonedConfig: DynamicCheckoutMethodScopedConfig<T> = {}
if (!config) {
return clonedConfig
}
for (const key in config) {
if (config[key]) {
clonedConfig[key] = {
...config[key],
const clonedConfig = Object.create(null) as DynamicCheckoutMethodScopedConfig<T>
if (!config) {
return clonedConfig
}
for (const key of Object.keys(config)) {
// Explicitly skip dangerous keys to prevent prototype pollution
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue
}
const value = config[key]
if (value) {
clonedConfig[key] = {
...value,

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants