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
17 changes: 9 additions & 8 deletions paypal/controllers/FrmPayPalLiteActionsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

class FrmPayPalLiteActionsController extends FrmTransLiteActionsController {

private static $active_order_id;

Check failure on line 8 in paypal/controllers/FrmPayPalLiteActionsController.php

View workflow job for this annotation

GitHub Actions / PHPStan

Property FrmPayPalLiteActionsController::$active_order_id has no type specified.

private static $active_payment_source;

Check failure on line 10 in paypal/controllers/FrmPayPalLiteActionsController.php

View workflow job for this annotation

GitHub Actions / PHPStan

Property FrmPayPalLiteActionsController::$active_payment_source has no type specified.

/**
* @since x.x
Expand Down Expand Up @@ -455,7 +455,7 @@
$captures = $payments->captures;

foreach ( $captures as $capture ) {
return $capture->id;

Check failure on line 458 in paypal/controllers/FrmPayPalLiteActionsController.php

View workflow job for this annotation

GitHub Actions / PHPStan

Method FrmPayPalLiteActionsController::get_capture_id_from_response() should return string but returns mixed.
}
}

Expand Down Expand Up @@ -492,7 +492,7 @@
FrmEntryMeta::add_entry_meta( $entry->id, $field_id, '', $new_value );
}
} else {
$updates = self::get_payer_field_updates( $payer, $response, $action, $entry );

Check failure on line 495 in paypal/controllers/FrmPayPalLiteActionsController.php

View workflow job for this annotation

GitHub Actions / PHPStan

Parameter #3 $action of static method FrmPayPalLiteActionsController::get_payer_field_updates() expects WP_Post, mixed given.

foreach ( $updates as $field_id => $new_value ) {
if ( ! FrmEntryMeta::update_entry_meta( $entry->id, $field_id, '', $new_value ) ) {
Expand Down Expand Up @@ -569,7 +569,7 @@
FrmEntryMeta::add_entry_meta( $entry->id, $field_id, '', $new_value );
}
} else {
$updates = self::get_payer_field_updates( $payer, $response, $action, $entry );

Check failure on line 572 in paypal/controllers/FrmPayPalLiteActionsController.php

View workflow job for this annotation

GitHub Actions / PHPStan

Parameter #3 $action of static method FrmPayPalLiteActionsController::get_payer_field_updates() expects WP_Post, mixed given.

foreach ( $updates as $field_id => $new_value ) {
if ( ! FrmEntryMeta::update_entry_meta( $entry->id, $field_id, '', $new_value ) ) {
Expand Down Expand Up @@ -664,7 +664,7 @@
$settings = $action->post_content;

// Email: setting is a shortcode like [25], extract the field ID.
if ( ! empty( $settings['email'] ) && preg_match( '/\[(\d+)\]/', $settings['email'], $matches ) ) {

Check failure on line 667 in paypal/controllers/FrmPayPalLiteActionsController.php

View workflow job for this annotation

GitHub Actions / PHPStan

Offset 'email' on string in empty() does not exist.
$email_field_id = (int) $matches[1];

if ( ! empty( $payer->email_address ) ) {
Expand All @@ -678,8 +678,8 @@

// Name fields.
if ( isset( $payer->name ) ) {
$first_name_field_id = ! empty( $settings['billing_first_name'] ) ? (int) $settings['billing_first_name'] : 0;

Check failure on line 681 in paypal/controllers/FrmPayPalLiteActionsController.php

View workflow job for this annotation

GitHub Actions / PHPStan

Offset 'billing_first_name' on string in empty() does not exist.
$last_name_field_id = ! empty( $settings['billing_last_name'] ) ? (int) $settings['billing_last_name'] : 0;

Check failure on line 682 in paypal/controllers/FrmPayPalLiteActionsController.php

View workflow job for this annotation

GitHub Actions / PHPStan

Offset 'billing_last_name' on string in empty() does not exist.

if ( $first_name_field_id && $first_name_field_id === $last_name_field_id ) {
// Both settings point to the same Name field. Store as a serialized array.
Expand Down Expand Up @@ -713,7 +713,7 @@
}//end if

// Address: pull from the first purchase unit's shipping address.
if ( empty( $settings['billing_address'] ) ) {

Check failure on line 716 in paypal/controllers/FrmPayPalLiteActionsController.php

View workflow job for this annotation

GitHub Actions / PHPStan

Offset 'billing_address' on string in empty() does not exist.
return $updates;
}

Expand Down Expand Up @@ -1009,7 +1009,7 @@
'intent' => $intent,
'currency' => strtoupper( $action->post_content['currency'] ?? 'USD' ),
'merchant-id' => FrmPayPalLiteConnectHelper::get_merchant_id(),
'enable-funding' => 'venmo',
'enable-funding' => 'venmo,applepay',
);

if ( 'subscription' === $intent ) {
Expand Down Expand Up @@ -1051,6 +1051,7 @@
if ( $include_buttons ) {
$components[] = 'buttons';
$components[] = 'googlepay';
$components[] = 'applepay';
}

if ( $include_card_fields ) {
Expand Down Expand Up @@ -1084,6 +1085,7 @@
$sdk_url = add_query_arg( $query_args, 'https://www.paypal.com/sdk/js' );

wp_register_script( 'paypal-sdk', $sdk_url, array(), null, false );
wp_register_script( 'apple-pay-sdk', 'https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js', array(), null, false );

$has_break = FrmAppHelper::pro_is_installed() && (bool) FrmField::get_all_types_in_form( $form_id, 'break' );

Expand All @@ -1097,15 +1099,14 @@
*/
function ( $tag, $handle ) use ( $has_break ) {
if ( 'paypal-sdk' === $handle ) {
$attributes = ' data-partner-attribution-id="' . esc_attr( FrmPayPalLiteConnectHelper::get_bn_code() ) . '"';

if ( $has_break ) {
$attributes .= ' async';
}

$attributes = ' async data-partner-attribution-id="' . esc_attr( FrmPayPalLiteConnectHelper::get_bn_code() ) . '"';
return str_replace( ' src=', $attributes . ' src=', $tag );
}

if ( in_array( $handle, array( 'apple-pay-sdk', 'google-pay' ), true ) ) {
return str_replace( ' src=', ' async src=', $tag );
}

if ( $has_break && 'formidable-paypal' === $handle ) {
return str_replace( ' src=', ' async src=', $tag );
}
Expand All @@ -1123,7 +1124,7 @@
FrmAppHelper::plugin_version()
);

$dependencies = array( 'paypal-sdk', 'formidable' );
$dependencies = array( 'paypal-sdk', 'apple-pay-sdk', 'formidable' );
$script_url = FrmPayPalLiteAppHelper::plugin_url() . 'js/frontend.js';

wp_enqueue_script(
Expand Down
12 changes: 7 additions & 5 deletions paypal/controllers/FrmPayPalLiteAppController.php
Original file line number Diff line number Diff line change
Expand Up @@ -272,17 +272,19 @@ private static function get_valid_payment_sources() {
$sources = array(
'card',
'paypal',
'mybank',
'apple_pay',
'bancontact',
'blik',
'eps',
'giropay',
'ideal',
'mybank',
'p24',
'trustly',
'satispay',
'sepa',
'ideal',
'paylater',
'sofort',
'trustly',
'venmo',
'paylater',
'google_pay',
);

Expand Down
9 changes: 9 additions & 0 deletions paypal/css/frontend.css
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,15 @@
width: auto;
}

.frm-payment-method-apple-pay-icon {
height: 24px;
}

.frm-payment-method-apple-pay-icon svg {
height: 24px;
width: auto;
}

.frm-payment-method-paylater-wrap {
border-bottom: 1px solid #e0e0e0;
background: #fff;
Expand Down
16 changes: 16 additions & 0 deletions paypal/images/apple-pay.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
198 changes: 197 additions & 1 deletion paypal/js/frontend.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
/** Cached Google Pay config from paypal.Googlepay().config(). */
let googlePayConfig = null;

/** Cached Apple Pay config from paypal.Applepay().config(). */
let applePayConfig = null;

// ---- Constants ----

/**
Expand All @@ -48,6 +51,7 @@
venmo: 'Venmo',
paylater: 'Pay Later',
google_pay: 'Google Pay',
apple_pay: 'Apple Pay',
bancontact: 'Bancontact',
blik: 'BLIK',
eps: 'EPS',
Expand Down Expand Up @@ -255,6 +259,19 @@
} );
}
}

// --- Apple Pay ---
if ( buttonsAreEnabled && ! isRecurring ) {
const applePayEligibilityResult = await checkApplePayEligibility();
if ( applePayEligibilityResult === '' ) {
registerMethod( 'apple_pay', {
eligible: true,
render: renderApplePayButton
} );
} else {
console.log( 'Apple Pay not available:', applePayEligibilityResult );
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Avoid using console in code that runs on the browser


It is considered a best practice to avoid the use of any console methods in JavaScript code that will run on the browser.

NOTE: If your repository contains a server side project, you can add "nodejs" to the environment property of analyzer meta in .deepsource.toml.
This will prevent this issue from getting raised.
Documentation for the analyzer meta can be found here.
Alternatively, you can silence this issue for your repository as shown here.

If a specific console call is meant to stay for other reasons, you can add a skipcq comment to that line.
This will inform other developers about the reason behind the log's presence, and prevent DeepSource from flagging it.

}
}
}

/**
Expand Down Expand Up @@ -342,11 +359,18 @@
} else if ( key === 'google_pay' ) {
markWrap.classList.add( 'frm-payment-method-google-pay-icon' );
const img = document.createElement( 'img' );
const baseUrl = frmPayPalVars.imagesUrl || '';
img.src = `${ baseUrl }gpay.svg`;
img.alt = 'Google Pay';
img.height = 24;
markWrap.append( img );
} else if ( key === 'apple_pay' ) {
markWrap.classList.add( 'frm-payment-method-apple-pay-icon' );
const img = document.createElement( 'img' );
img.src = `${ baseUrl }apple-pay.svg`;
img.alt = 'Apple Pay';
img.height = 24;
img.style.width = 'auto';
markWrap.append( img );
}

label.append( radio );
Expand Down Expand Up @@ -854,6 +878,145 @@
}
}

// ---- Apple Pay ----

/**
* Check if Apple Pay is eligible (without rendering).
*
* @return {Promise<string>} An empty string if Apple Pay is supported and ready to accept payments in the current environment, or a string with the reason for ineligibility.
*/
async function checkApplePayEligibility() {
if ( 'function' !== typeof paypal.Applepay ) {
return 'PayPal Apple Pay SDK not loaded';
}

if ( ! window.ApplePaySession ) {
return 'Not on Apple device';
}

if ( ! ApplePaySession.canMakePayments() ) {
return 'Apple Pay not configured on device';
}

// Use paypal.Applepay().config() as the definitive eligibility check (per PayPal multiparty docs).
try {
applePayConfig = await paypal.Applepay().config();

if ( ! applePayConfig || ! applePayConfig.isEligible ) {
return 'PayPal reports Apple Pay is not eligible for this merchant/domain';
}
} catch ( err ) {
return 'Apple Pay config check failed: ' + err.message;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unexpected string concatenation


In ES2015 (ES6), we can use template literals instead of string concatenation.

}

return '';
}

/**
* Render the Apple Pay button into its method container.
*/
async function renderApplePayButton() {
const method = paymentMethods.get( 'apple_pay' );
if ( ! method ) {
return;
}

const container = method.containerEl;
container.innerHTML = '';

const btn = document.createElement( 'apple-pay-button' );
btn.setAttribute( 'buttonstyle', 'black' );
btn.setAttribute( 'type', 'buy' );
btn.setAttribute( 'locale', 'en' );
btn.style.width = '100%';
btn.style.height = '40px';

btn.addEventListener( 'click', onApplePayButtonClick );
container.appendChild( btn );
}
Comment on lines +918 to +936
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Found `async` function without any `await` expressions


A function that does not contain any await expressions should not be async (except for some edge cases in TypeScript which are discussed below). Asynchronous functions in JavaScript behave differently than other functions in two important ways:


/**
* Handle click on the Apple Pay button.
* Creates an ApplePaySession synchronously (required by Apple) and processes the payment via PayPal.
*/
function onApplePayButtonClick() {
if ( ! applePayConfig ) {
console.error( 'Apple Pay config not available' );
return;
}

const paymentRequest = {
countryCode: applePayConfig.countryCode,
merchantCapabilities: applePayConfig.merchantCapabilities,
supportedNetworks: applePayConfig.supportedNetworks,
currencyCode: applePayConfig.currencyCode || 'USD',
total: {
label: document.title || 'Payment',
type: 'final',
amount: getFormTotal(),
},
};

// ApplePaySession MUST be created synchronously inside the click handler.
const session = new ApplePaySession( 4, paymentRequest );
const applepay = paypal.Applepay();

session.onvalidatemerchant = ( event ) => {
applepay.validateMerchant( {
validationUrl: event.validationURL,
displayName: document.title || 'Payment'
} )
.then( ( validateResult ) => {
session.completeMerchantValidation( validateResult.merchantSession );
} )
.catch( ( validateError ) => {
console.error( 'Apple Pay merchant validation failed', validateError );
session.abort();
} );
};

session.onpaymentauthorized = ( event ) => {
createOrderForApplePay()
.then( ( orderId ) => {
return applepay.confirmOrder( {
orderId: orderId,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Expected property shorthand


ECMAScript 6 provides a concise form for defining object literal methods and properties. This syntax can make defining complex object literals much cleaner.

token: event.payment.token,
billingContact: event.payment.billingContact
} )
.then( () => {
session.completePayment( ApplePaySession.STATUS_SUCCESS );
onApprove( {
orderID: orderId,
paymentSource: 'apple_pay'
} );
} );
} )
.catch( ( err ) => {
console.error( 'Apple Pay payment failed', err );
session.completePayment( ApplePaySession.STATUS_FAILURE );
} );
};

session.oncancel = () => {
onCancel();
};

session.begin();
}

/**
* Get the form total amount as a string.
*
* @return {string} The total amount.
*/
function getFormTotal() {
const totalField = thisForm.querySelector( '[data-frmtotal]' );
if ( totalField && totalField.value ) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Prefer using an optional chain expression instead, as it's more concise and easier to read


The optional chaining operator can be used to perform null checks before accessing a property, or calling a function.

return parseFloat( totalField.value ).toFixed( 2 );
}
return '0.00';
}

// ---- AJAX / Order Creation ----

/**
Expand Down Expand Up @@ -964,6 +1127,39 @@
return orderData.data.orderID;
}

/**
* Create a PayPal order specifically for Apple Pay.
*
* @return {Promise<string>} The PayPal order ID.
*/
async function createOrderForApplePay() {
const formData = new FormData( thisForm );
formData.append( 'action', 'frm_paypal_create_order' );
formData.append( 'nonce', frmPayPalVars.nonce );
formData.append( 'payment_source', 'apple_pay' );

formData.delete( 'frm_action' );
formData.delete( 'form_key' );
formData.delete( 'item_key' );

const response = await fetch( frmPayPalVars.ajax, {
method: 'POST',
body: formData
} );

if ( ! response.ok ) {
throw new Error( 'Failed to create PayPal order for Apple Pay' );
}

const orderData = await response.json();

if ( ! orderData.success || ! orderData.data.orderID ) {
throw new Error( orderData.data || 'Failed to create PayPal order for Apple Pay' );
}

return orderData.data.orderID;
}

async function createVaultSetupToken() {
const formData = new FormData( thisForm );
formData.append( 'action', 'frm_paypal_create_vault_setup_token' );
Expand Down
Loading