Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC - Centralized payment method definitions #10217

Open
wants to merge 69 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
3849078
Sorry for the big commit - first pass at getting the php side working…
brettshumaker Jan 15, 2025
888fcc6
We need to access this method outside of the definition.
brettshumaker Jan 15, 2025
66ab9a1
Update afterpay class to use new definitions.
brettshumaker Jan 15, 2025
ab7c4fb
Fix dark icon file checking.
brettshumaker Jan 15, 2025
845ecd6
Push generated types to client mapping
brettshumaker Jan 15, 2025
ae13734
Fix tests.
brettshumaker Jan 16, 2025
9d0cc13
Fixing the tests revealed that the stripe_id here should just be the …
brettshumaker Jan 16, 2025
c3c093c
Add note to Afterpay
brettshumaker Jan 16, 2025
b0aece2
Make get_stripe_id a consistent trait for the definitions.
brettshumaker Jan 16, 2025
b0d8884
Use existing constant for definition ID.
brettshumaker Jan 16, 2025
70ec9bd
Address notes from Marcin
brettshumaker Feb 3, 2025
2baec4b
Revert "Address notes from Marcin"
brettshumaker Feb 11, 2025
87e8f88
Rename classes to psr-4 and update references
brettshumaker Feb 11, 2025
ffe2a5d
Remove BNPL trait and definition.
brettshumaker Feb 11, 2025
136a012
Remove Payment_Method_Icons trait and add direct icon URL methods to …
brettshumaker Feb 11, 2025
45bc090
Remove Base_Payment_Method trait and move some of its functionality t…
brettshumaker Feb 11, 2025
9ae210b
Moved to static methods and updated all references to reflect that.
brettshumaker Feb 12, 2025
df36af2
white space clean up and use the "correct" id for the Affirm payment …
brettshumaker Feb 13, 2025
5258803
Update to definition registry, the definition registration process, a…
brettshumaker Feb 13, 2025
8fd22ab
Make a new CLI Commands class for implementing our CLI command and an…
brettshumaker Feb 13, 2025
d436df5
Fix PSR-4 naming inconsistencies in includes/payment-methods/Configs
brettshumaker Feb 13, 2025
22e3e4f
init payment definitions at run time
brettshumaker Feb 13, 2025
58e1feb
Make PaymentMethodDefinitionRegistry consistently have instance methods
brettshumaker Feb 14, 2025
bf2c18c
Rearrange method order
brettshumaker Feb 14, 2025
db37a11
Make is_reusable/bnpl/accept_only_domestic_payment part of the defini…
brettshumaker Feb 14, 2025
be7c41f
Merge remote-tracking branch 'origin/develop' into poc/centralized-pa…
brettshumaker Feb 14, 2025
76a18d2
Remove unused `use` statements
brettshumaker Feb 14, 2025
f3d88c0
Rename payment method definition method
brettshumaker Feb 14, 2025
7795dff
Add support for manual capture to definition
brettshumaker Feb 14, 2025
ff219f6
Add manual capture to the definition interface - missed this in the l…
brettshumaker Feb 14, 2025
a035b82
Remove unneeded npm command and associated file.
brettshumaker Feb 14, 2025
521b941
Simplify mapping class
brettshumaker Feb 17, 2025
fad6bde
Auto-generate icon components when generating types for payment methods.
brettshumaker Feb 17, 2025
174689d
Fix psalm linting
brettshumaker Feb 18, 2025
b4e30c1
Merge branch 'develop' into poc/centralized-payment-method-definitions
brettshumaker Feb 18, 2025
c7b9b8e
Convert Klarna to new payment method definition
brettshumaker Feb 18, 2025
81f31e0
Add utility method for checking if a given currency is "domestic" for…
brettshumaker Feb 18, 2025
ceb5657
Remove unused method in definition.
brettshumaker Feb 18, 2025
dccdc2d
Fix js tests
brettshumaker Feb 18, 2025
8af7634
Merge branch 'develop' into poc/centralized-payment-method-definitions
brettshumaker Feb 18, 2025
3069f05
Fix Klarna tests
brettshumaker Feb 18, 2025
ad0bc2e
Create poc-centralized-payment-method-definitions
brettshumaker Feb 18, 2025
ee07ebc
Merge branch 'develop' into poc/centralized-payment-method-definitions
frosso Feb 19, 2025
5e06321
Revert "Fix js tests"
brettshumaker Feb 19, 2025
b3cd1dc
Actually fix js tests
brettshumaker Feb 19, 2025
a16c8f3
Resolve (compiling) circular dependency.
brettshumaker Feb 19, 2025
7be446b
Merge branch 'develop' into poc/centralized-payment-method-definitions
brettshumaker Feb 19, 2025
a52710f
Pass payment method config to inline script instead of generating
brettshumaker Feb 25, 2025
7ecea8a
Remove all code related to generating JS types for payment method def…
brettshumaker Feb 25, 2025
7e9f5e8
Merge branch 'develop' into poc/centralized-payment-method-definitions
brettshumaker Feb 25, 2025
3b39e27
Fix FalsableReturnStatement error
brettshumaker Feb 26, 2025
2a9d6d1
Properly handle wp_json_encode
brettshumaker Feb 26, 2025
693f5ae
Merge branch 'develop' into poc/centralized-payment-method-definitions
brettshumaker Feb 26, 2025
b1795e2
Really fix the psalm error - wasn't failing with npm run psalm
brettshumaker Feb 26, 2025
8aa160b
Merge branch 'poc/centralized-payment-method-definitions' of https://…
brettshumaker Feb 26, 2025
b55bca5
Fix jest config error - missed this when removing the js type generat…
brettshumaker Feb 26, 2025
6bcc283
Remove payment method definitions - will be re-added in individual PR…
brettshumaker Feb 26, 2025
10006d3
Merge branch 'develop' into poc/centralized-payment-method-definitions
brettshumaker Feb 26, 2025
2a8ca73
Remove a few straggling references to the definitions I deleted.
brettshumaker Feb 26, 2025
78d4aa9
Merge branch 'develop' into poc/centralized-payment-method-definitions
brettshumaker Feb 26, 2025
71d9650
Create tests for PaymentMethodDefinitionRegistry
brettshumaker Feb 26, 2025
df2d1fa
Create tests for PaymentMethodUtils
brettshumaker Feb 26, 2025
c9c83d9
Whoops!
brettshumaker Feb 27, 2025
13fec88
Remove accidental double encoding of JSON data.
brettshumaker Feb 27, 2025
43161c0
Use window instead of const just in case this ever gets added to the …
brettshumaker Feb 28, 2025
7850669
Restore Klarna payment method in WC_Payment_Gateway_WCPay_Test
brettshumaker Feb 28, 2025
186a0ac
Merge branch 'poc/centralized-payment-method-definitions' of https://…
brettshumaker Feb 28, 2025
5b86484
Use type parameter instead of casting
brettshumaker Feb 28, 2025
4f65ac7
Merge branch 'develop' into poc/centralized-payment-method-definitions
brettshumaker Feb 28, 2025
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
228 changes: 228 additions & 0 deletions build/payment-methods-plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
const { spawn } = require( 'child_process' );
const path = require( 'path' );
const fs = require( 'fs' );

// Helper function to recursively get all files in a directory
function getAllFiles( dirPath, arrayOfFiles = [] ) {
const files = fs.readdirSync( dirPath );

files.forEach( ( file ) => {
const fullPath = path.join( dirPath, file );
if ( fs.statSync( fullPath ).isDirectory() ) {
arrayOfFiles = getAllFiles( fullPath, arrayOfFiles );
} else {
arrayOfFiles.push( fullPath );
}
} );

return arrayOfFiles;
}

// Helper function to recursively remove a directory
function removeDirectory( dirPath ) {
if ( fs.existsSync( dirPath ) ) {
fs.readdirSync( dirPath ).forEach( ( file ) => {
const curPath = path.join( dirPath, file );
if ( fs.lstatSync( curPath ).isDirectory() ) {
removeDirectory( curPath );
} else {
fs.unlinkSync( curPath );
}
} );
fs.rmdirSync( dirPath );
}
}

class PaymentMethodsPlugin {
apply( compiler ) {
// Track if we've built the payment methods in this session
let hasBuilt = false;
// Track if we're currently building
let isBuilding = false;

// Create build directory if it doesn't exist
const buildDir = path.resolve( __dirname, './payment-methods' );
if ( ! fs.existsSync( buildDir ) ) {
fs.mkdirSync( buildDir, { recursive: true } );
}

// Add payment method files to webpack's watch list
compiler.hooks.afterCompile.tap(
'PaymentMethodsPlugin',
( compilation ) => {
const paymentMethodsDir = path.resolve(
__dirname,
'../includes/payment-methods/Configs'
);

// Add all files and directories recursively
if ( fs.existsSync( paymentMethodsDir ) ) {
// Add the root directory
compilation.fileDependencies.add( paymentMethodsDir );

// Add all files recursively
const allFiles = getAllFiles( paymentMethodsDir );
allFiles.forEach( ( file ) => {
compilation.fileDependencies.add( file );
} );

// Add all directories recursively
const addDirectory = ( dir ) => {
compilation.fileDependencies.add( dir );
fs.readdirSync( dir ).forEach( ( file ) => {
const fullPath = path.join( dir, file );
if ( fs.statSync( fullPath ).isDirectory() ) {
addDirectory( fullPath );
}
} );
};
addDirectory( paymentMethodsDir );
}
}
);

const buildPaymentMethods = ( callback ) => {
if ( isBuilding ) {
callback();
return;
}

isBuilding = true;
console.log( '\nBuilding payment method definitions...' );

Check warning on line 91 in build/payment-methods-plugin.js

View workflow job for this annotation

GitHub Actions / JS linting

Unexpected console statement

// Split the long path into multiple lines
const phpScriptPath = path.join(
'/var/www/html/wp-content/plugins',
'woocommerce-payments/includes/payment-methods',
'Configs/scripts/generate-payment-method-configs.php'
);

// Run the PHP script inside Docker
const phpScript = spawn( 'docker', [
'compose',
'exec',
'-T',
'wordpress',
'php',
phpScriptPath,
] );

phpScript.stdout.on( 'data', ( data ) => {
console.log( `PHP: ${ data }` );

Check warning on line 111 in build/payment-methods-plugin.js

View workflow job for this annotation

GitHub Actions / JS linting

Unexpected console statement
} );

phpScript.stderr.on( 'data', ( data ) => {
console.error( `PHP Error: ${ data }` );

Check warning on line 115 in build/payment-methods-plugin.js

View workflow job for this annotation

GitHub Actions / JS linting

Unexpected console statement
} );

phpScript.on( 'close', ( code ) => {
if ( code !== 0 ) {
console.error( 'PHP script failed' );

Check warning on line 120 in build/payment-methods-plugin.js

View workflow job for this annotation

GitHub Actions / JS linting

Unexpected console statement
isBuilding = false;
callback( new Error( 'PHP script failed' ) );
return;
}

console.log( 'PHP script completed successfully' );

Check warning on line 126 in build/payment-methods-plugin.js

View workflow job for this annotation

GitHub Actions / JS linting

Unexpected console statement

// Then run the JavaScript script
const jsScript = spawn(
'node',
[
path.resolve(
__dirname,
'../includes/payment-methods/Configs/scripts/generate-payment-method-types.js'
),
],
{
stdio: 'inherit',
}
);

jsScript.on( 'close', ( closeCode ) => {
if ( closeCode !== 0 ) {
console.error( 'JavaScript script failed' );

Check warning on line 144 in build/payment-methods-plugin.js

View workflow job for this annotation

GitHub Actions / JS linting

Unexpected console statement
isBuilding = false;
callback( new Error( 'JavaScript script failed' ) );
return;
}

console.log( 'Running eslint...' );

Check warning on line 150 in build/payment-methods-plugin.js

View workflow job for this annotation

GitHub Actions / JS linting

Unexpected console statement

// Run eslint --fix on the generated file
const eslintScript = spawn(
'npx',
[
'eslint',
'--fix',
'client/payment-methods/types.ts',
],
{
stdio: 'inherit',
}
);

eslintScript.on( 'close', ( eslintCode ) => {
isBuilding = false;
if ( eslintCode !== 0 ) {
console.error( 'Eslint failed' );

Check warning on line 168 in build/payment-methods-plugin.js

View workflow job for this annotation

GitHub Actions / JS linting

Unexpected console statement
callback( new Error( 'Eslint failed' ) );
return;
}

// Clean up build artifacts
removeDirectory( buildDir );

console.log(

Check warning on line 176 in build/payment-methods-plugin.js

View workflow job for this annotation

GitHub Actions / JS linting

Unexpected console statement
'Payment method definitions built successfully\n'
);
hasBuilt = true;
callback();
} );
} );
} );
};

// Run build when files change during watch
compiler.hooks.watchRun.tapAsync(
'PaymentMethodsPlugin',
( compilation, callback ) => {
// Build on initial run or when files have changed
if ( ! hasBuilt || compilation.modifiedFiles ) {
// Only check for payment method changes if we have modified files
if ( compilation.modifiedFiles ) {
const modifiedFiles = Array.from(
compilation.modifiedFiles || []
);

// Ignore changes to generated files
const hasPaymentMethodChanges = modifiedFiles.some(
( file ) =>
file.includes( '/payment-methods/Configs/' ) &&
! file.includes( 'build/' ) &&
! file.includes(
'client/payment-methods/types.ts'
)
);

if ( ! hasPaymentMethodChanges ) {
callback();
return;
}

console.log(

Check warning on line 213 in build/payment-methods-plugin.js

View workflow job for this annotation

GitHub Actions / JS linting

Unexpected console statement
'\nPayment method files changed, rebuilding...'
);
}

hasBuilt = false; // Reset build flag
buildPaymentMethods( callback );
} else {
callback();
}
}
);
}
}

module.exports = PaymentMethodsPlugin;
43 changes: 2 additions & 41 deletions client/payment-methods-map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ import { __ } from '@wordpress/i18n';
*/

import {
AffirmIcon,
AfterpayIcon,
ClearpayIcon,
BancontactIcon,
BankDebitIcon,
CreditCardIcon,
Expand All @@ -24,7 +21,7 @@ import {
SofortIcon,
} from 'wcpay/payment-methods-icons';

const accountCountry = window.wcpaySettings?.accountStatus?.country || 'US';
import GeneratedPaymentMethodInformationObject from './payment-methods/generated-map';

export interface PaymentMethodMapEntry {
id: string;
Expand Down Expand Up @@ -168,43 +165,6 @@ const PaymentMethodInformationObject: Record<
allows_pay_later: false,
accepts_only_domestic_payment: false,
},
affirm: {
id: 'affirm',
label: __( 'Affirm', 'woocommerce-payments' ),
description: __(
'Allow customers to pay over time with Affirm.',
'woocommerce-payments'
),
icon: AffirmIcon,
currencies: [ 'USD', 'CAD' ],
stripe_key: 'affirm_payments',
allows_manual_capture: false,
allows_pay_later: true,
accepts_only_domestic_payment: true,
},
afterpay_clearpay: {
id: 'afterpay_clearpay',
label:
'GB' === accountCountry
? __( 'Clearpay', 'woocommerce-payments' )
: __( 'Afterpay', 'woocommerce-payments' ),
description:
'GB' === accountCountry
? __(
'Allow customers to pay over time with Clearpay.',
'woocommerce-payments'
)
: __(
'Allow customers to pay over time with Afterpay.',
'woocommerce-payments'
),
icon: 'GB' === accountCountry ? ClearpayIcon : AfterpayIcon,
currencies: [ 'USD', 'AUD', 'CAD', 'NZD', 'GBP' ],
stripe_key: 'afterpay_clearpay_payments',
allows_manual_capture: false,
allows_pay_later: true,
accepts_only_domestic_payment: true,
},
jcb: {
id: 'jcb',
label: __( 'JCB', 'woocommerce-payments' ),
Expand Down Expand Up @@ -233,6 +193,7 @@ const PaymentMethodInformationObject: Record<
allows_pay_later: true,
accepts_only_domestic_payment: true,
},
...GeneratedPaymentMethodInformationObject,
};

export default PaymentMethodInformationObject;
21 changes: 21 additions & 0 deletions client/payment-methods/generated-map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Internal dependencies
*/
import type { PaymentMethodMapEntry } from '../payment-methods-map';
import { PaymentMethodDefinitions } from './types';
import { mapDefinitionToEntry } from './mapping';

/**
* Generated payment method information using the backend-defined types
*/
const GeneratedPaymentMethodInformationObject: Record<
string,
PaymentMethodMapEntry
> = Object.fromEntries(
Object.entries( PaymentMethodDefinitions ).map( ( [ key, def ] ) => [
key,
mapDefinitionToEntry( def ),
] )
);

export default GeneratedPaymentMethodInformationObject;
54 changes: 54 additions & 0 deletions client/payment-methods/mapping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* External dependencies
*/
import type { ImgHTMLAttributes, FunctionComponent } from 'react';

/**
* Internal dependencies
*/
import type { PaymentMethodDefinition } from './types';
import type { PaymentMethodMapEntry } from '../payment-methods-map';
import {
AffirmIcon,
AfterpayIcon,
ClearpayIcon,
} from '../payment-methods-icons';

type ReactImgFuncComponent = FunctionComponent<
ImgHTMLAttributes< HTMLImageElement >
>;

const accountCountry = window.wcpaySettings?.accountStatus?.country || 'US';

/**
* Maps payment method IDs to their corresponding icon components
*/
function getIconComponent( id: string ): ReactImgFuncComponent {
const iconMap: Record< string, ReactImgFuncComponent > = {
affirm: AffirmIcon,
afterpay_clearpay:
accountCountry === 'GB' ? ClearpayIcon : AfterpayIcon,
};
return iconMap[ id ];
}

/**
* Maps a PaymentMethodDefinition to a PaymentMethodMapEntry
*/
export function mapDefinitionToEntry(
def: PaymentMethodDefinition
): PaymentMethodMapEntry {
return {
id: def.id,
label: def.title,
description: def.description,
icon: getIconComponent( def.id ),
currencies: def.currencies,
stripe_key: def.stripeId,
allows_manual_capture: def.capabilities.includes( 'capture_later' ),
allows_pay_later: def.capabilities.includes( 'buy_now_pay_later' ),
accepts_only_domestic_payment: def.capabilities.includes(
'domestic_transactions_only'
),
};
}
Loading
Loading