adyen_gateway/forms/adyen.js
/* global AdyenCheckout, Promise */
( function ( $, mw ) {
// promise objects are for Apple Pay - see comments below
var checkout, onSubmit, authPromise, submitPromise,
configFromServer = mw.config.get( 'adyenConfiguration' ),
payment_method = $( '#payment_method' ).val(),
onBrandSubmethod = '',
country = $( '#country' ).val(),
language = $( '#language' ).val(),
// This is the old-style Google Pay integration type currently active on
// our account. Older versions of the Adyen JS SDK treated the 'googlepay'
// component type as the old GPay integration, but for newer versions of
// the GPay SDK we need to explicitly specify 'paywithgoogle' to get tokens
// that work with the old-style integration. At some point we should upgrade
// to the new interaction, but that will require coordinating an update to
// this constant with an update to our account.
GOOGLEPAY_COMPONENT_TYPE = 'paywithgoogle',
ACH_GET_DONOR_ADDRESS = true; // set false to remove address from form
/**
* Get extra configuration values for specific payment types
*
* @param {string} type Adyen-side name of component type
* @param {Object} checkoutConfig The config object used to instantiate the Adyen Checkout object
* @return Object
*/
function getComponentConfig( type, checkoutConfig ) {
var config = {};
switch ( type ) {
case 'card':
// Note: Debug messages are only sent and logged server-side if
// $wgDonationInterfaceLogDebug (or $wgAdyenCheckoutGatewayLogDebug) is true
config.onBrand = function ( brandInfo ) {
var message = brandInfo.brand ?
'onBrand returned brand: ' + brandInfo.brand :
'onBrand returned: ' + JSON.stringify( brandInfo );
if ( brandInfo.brand ) {
onBrandSubmethod = brandInfo.brand;
}
mw.donationInterface.forms.addDebugMessage( message );
};
config.onBinLookup = function ( binLookupInfo ) {
var message = binLookupInfo.detectedBrands && binLookupInfo.detectedBrands.length > 0 ?
'onBinLookup returned detected brands: ' + JSON.stringify( binLookupInfo.detectedBrands ) :
'onBinLookup returned: ' + JSON.stringify( binLookupInfo );
mw.donationInterface.forms.addDebugMessage( message );
};
config.showBrandsUnderCardNumber = false;
return config;
case 'ach':
config.billingAddressRequired = ACH_GET_DONOR_ADDRESS;
return config;
case 'ideal':
case 'onlineBanking_CZ':
case 'sepadirectdebit':
// for ach, ideal, CZ bank transfers, and sepa additional config is optional
return config;
case 'applepay':
// for applepay, additional config is required
var amount = {},
currency = $( '#currency' ).val(),
amount_value = $( '#amount' ).val();
amount.currency = currency;
amount.value = amountInMinorUnits( amount_value, currency );
config.amount = amount;
config.countryCode = country;
config.requiredBillingContactFields = [
'name',
'postalAddress'
];
config.requiredShippingContactFields = [
'name',
'email'
];
authPromise = new Promise( function ( authResolve, authReject ) {
config.onAuthorized = function ( resolve, reject, event ) {
var bContact = event.payment.billingContact,
sContact = event.payment.shippingContact,
extraData = {};
extraData = getBestApplePayContactName( extraData, bContact, sContact );
extraData.postal_code = bContact.postalCode;
extraData.state_province = bContact.administrativeArea;
extraData.city = bContact.locality;
if ( bContact.addressLines.length > 0 ) {
extraData.street_address = bContact.addressLines[ 0 ];
}
extraData.email = sContact.emailAddress;
extraData.payment_submethod = mapAppleNetworkToSubmethod( event.payment.token.paymentMethod.network );
// We will combine this contact data with a token from the
// onSubmit event after both events have fired.
authResolve( extraData );
resolve();
};
} );
// For Apple Pay show the branded button with 'Donate with 🍎Pay'
// text as opposed to our standard blue Donate button
config.showPayButton = true;
config.buttonType = 'donate';
// When the donor clicks the donate button, this event is fired with
// a validationUrl provided by Apple. We have to make a server-side
// request to get a big blob of Apple Pay session data, then send it
// via the resolve function back to the component, which apparently
// sends it back to the native widget via completeMerchantValidation.
// https://developer.apple.com/documentation/apple_pay_on_the_web/apple_pay_js_api/providing_merchant_validation
config.onValidateMerchant = function ( resolve, reject, validationUrl ) {
var api = new mw.Api();
api.post( {
action: 'di_applesession_adyen',
validation_url: validationUrl,
wmf_token: $( '#wmf_token' ).val()
} ).then( function ( data ) {
if ( data.result && data.result.errors ) {
mw.donationInterface.validation.showErrors( data.result.errors );
reject();
} else {
resolve( data.session );
}
} );
};
return config;
case GOOGLEPAY_COMPONENT_TYPE:
// for googlepay, additional config is required
var g_amount = {},
g_currency = $( '#currency' ).val(),
g_amount_value = $( '#amount' ).val(),
languagesSupportedByGPayButton = [
'ar', 'bg', 'ca', 'cs', 'da', 'de', 'en', 'el', 'es', 'et', 'fi', 'fr', 'hr', 'id', 'it', 'ja',
'ko', 'ms', 'nl', 'no', 'pl', 'pt', 'ru', 'sk', 'sl', 'sr', 'sv', 'th', 'tr', 'uk', 'zh'
], baseLanguageCode = checkoutConfig.locale.slice( 0, 2 );
g_amount.currency = g_currency;
g_amount.value = amountInMinorUnits( g_amount_value, g_currency );
config.amount = g_amount;
config.countryCode = country;
config.environment = configFromServer.environment.toUpperCase();
config.showPayButton = true;
// When we are showing the form in a language for which Google Pay has no
// translations of their button text, use the plain 'GPay' button rather
// than the 'Donate with GPay' button.
if ( languagesSupportedByGPayButton.indexOf( baseLanguageCode ) === -1 ) {
config.buttonType = 'plain';
} else {
config.buttonType = 'donate';
}
config.emailRequired = true;
config.billingAddressRequired = true;
config.allowedCardNetworks = configFromServer.googleAllowedNetworks;
config.billingAddressParameters = {
format: 'FULL'
};
// called gatewayMerchantId but actually our account name with Adyen
config.gatewayMerchantId = configFromServer.merchantAccountName;
config.merchantId = configFromServer.googleMerchantId;
authPromise = new Promise( function ( authResolve ) {
config.onAuthorized = function ( response ) {
var bContact = response.paymentMethodData.info.billingAddress,
extraData = {};
extraData.postal_code = bContact.postalCode;
extraData.state_province = bContact.administrativeArea;
extraData.city = bContact.locality;
extraData.street_address = bContact.address1;
extraData.email = response.email;
extraData.full_name = bContact.name;
extraData.payment_submethod = mapAdyenSubmethod(
response.paymentMethodData.info.cardNetwork.toLowerCase()
);
// We will combine this contact data with a token from the
// onSubmit event after both events have fired.
authResolve( extraData );
};
} );
return config;
default:
throw new Error( 'Component type not found' );
}
}
/**
* Given an amount in major currency units, e.g. dollars, returns the
* amount in minor units for the currency, e.g. cents. For non-fractional
* currencies just rounds the amount to the nearest whole number.
*
* @param {number} amount
* @param {string} currency
* @return {number} amount in minor units for specified currency
*/
function amountInMinorUnits( amount, currency ) {
var threeDecimals = mw.config.get( 'DonationInterfaceThreeDecimalCurrencies' ),
noDecimals = mw.config.get( 'DonationInterfaceNoDecimalCurrencies' );
if ( noDecimals.indexOf( currency ) !== -1 ) {
return Math.round( amount );
}
if ( threeDecimals.indexOf( currency ) !== -1 ) {
return Math.round( amount * 1000 );
}
return Math.round( amount * 100 );
}
/**
* Set up Adyen Checkout object
*
* @param {Object} config requires clientKey, environment, locale,
* and paymentMethodsResponse
* @return {AdyenCheckout}
*/
function getCheckout( config ) {
config.onSubmit = onSubmit;
config.onAdditionalDetails = onAdditionalDetails;
config.onError = onError;
config.showPayButton = false;
var checkoutObject = new AdyenCheckout( config );
if ( checkoutObject instanceof Promise ) {
return checkoutObject;
}
return new Promise( function ( resolve, reject ) {
resolve( checkoutObject );
} );
}
/**
* Try to obtain the "best" name from the available contact info sent back by Apple pay
*
* @see https://developer.apple.com/documentation/apple_pay_on_the_web/applepaypaymentrequest/2216120-requiredbillingcontactfields
* @see https://developer.apple.com/documentation/apple_pay_on_the_web/applepaypaymentcontact
* @param extraData
* @param billingContact
* @param shippingContact
* @return {*}
*/
function getBestApplePayContactName( extraData, billingContact, shippingContact ) {
var first_name, last_name;
if ( billingContact && billingContact.givenName && billingContact.givenName.length > 1 ) {
first_name = billingContact.givenName;
if ( billingContact.familyName && billingContact.familyName.length > 1 ) {
last_name = billingContact.familyName;
}
}
if ( first_name && !last_name ) {
// suspected 'dad' scenario so use shipping contact
if ( shippingContact && shippingContact.givenName && shippingContact.givenName.length > 1 ) {
first_name = shippingContact.givenName;
if ( shippingContact.familyName && shippingContact.familyName.length > 1 ) {
last_name = shippingContact.familyName;
}
}
}
extraData.first_name = first_name;
extraData.last_name = last_name;
return extraData;
}
function mapAppleNetworkToSubmethod( network ) {
network = network.toLowerCase();
switch ( network ) {
case 'amex':
case 'discover':
case 'jcb':
case 'visa':
return network;
case 'cartesbancaires':
return 'cb';
case 'electron':
return 'visa-electron';
case 'mastercard':
return 'mc';
default:
return '';
}
}
/**
* Get the name of the Adyen Checkout component to instantiate
*
* @param {string} paymentMethod our top-level payment method code
* @return {string} name of Adyen Checkout component to instantiate
*/
function mapPaymentMethodToComponentType( paymentMethod ) {
switch ( paymentMethod ) {
case 'dd':
return 'ach';
case 'cc':
return 'card';
case 'rtbt':
if ( mw.config.get( 'payment_submethod' ) === 'sepadirectdebit' ) {
return 'sepadirectdebit';
} else {
return 'ideal';
}
case 'bt':
return 'onlineBanking_CZ';
case 'apple':
return 'applepay';
case 'google':
return GOOGLEPAY_COMPONENT_TYPE;
default:
throw new Error( 'paymentMethod not found' );
}
}
/**
* TODO: should we do this mapping server-side
* using SmashPig's ReferenceData?
*
* @param {string} adyenBrandCode Adyen-side identifier for the payment submethod
* @return {string} Our identifier for the payment submethod
*/
function mapAdyenSubmethod( adyenBrandCode ) {
switch ( adyenBrandCode ) {
case 'bijcard':
return 'bij';
case 'cartebancaire':
return 'cb';
case 'mc-debit':
return 'mc';
case 'visadankort':
return 'visa';
case 'visadebit':
case 'vpay':
return 'visa-debit';
case 'visabeneficial':
return 'visa-beneficial';
case 'visaelectron':
return 'visa-electron';
case 'mastercard':
return 'mc';
default:
return adyenBrandCode;
}
}
submitPromise = new Promise( function ( submitResolve, submitReject ) {
onSubmit = function ( state, component ) {
var extraData = {};
// Submit to our server, unless it's Apple Pay, which submits in
// the onAuthorized handler.
if ( mw.donationInterface.validation.validate() && state.isValid ) {
switch ( payment_method ) {
case 'dd':
extraData = {
encrypted_bank_account_number: state.data.paymentMethod.encryptedBankAccountNumber,
encrypted_bank_location_id: state.data.paymentMethod.encryptedBankLocationId,
full_name: state.data.paymentMethod.ownerName,
bank_account_type: $( '#bank_account_type' ).val()
};
if ( ACH_GET_DONOR_ADDRESS ) {
extraData.supplemental_address_1 = state.data.billingAddress.houseNumberOrName !== 'N/A' ? state.data.billingAddress.houseNumberOrName : '';
extraData.country = state.data.billingAddress.country;
extraData.street_address = state.data.billingAddress.street;
extraData.postal_code = state.data.billingAddress.postalCode;
extraData.city = state.data.billingAddress.city;
extraData.state_province = state.data.billingAddress.stateOrProvince;
}
break;
case 'rtbt':
switch ( state.data.paymentMethod.type ) {
case 'ideal':
extraData = {
// issuer is bank chosen from dropdown
issuer_id: state.data.paymentMethod.issuer,
payment_submethod: 'rtbt_ideal'
};
break;
case 'sepadirectdebit':
extraData = {
full_name: state.data.paymentMethod.ownerName,
// The International Bank Account Number
iban: state.data.paymentMethod.iban,
payment_submethod: 'sepadirectdebit'
};
break;
}
break;
case 'bt':
extraData = {
// issuer is bank chosen from dropdown
issuer_id: state.data.paymentMethod.issuer
};
break;
case 'cc':
extraData = {
encrypted_card_number: state.data.paymentMethod.encryptedCardNumber,
encrypted_expiry_month: state.data.paymentMethod.encryptedExpiryMonth,
encrypted_expiry_year: state.data.paymentMethod.encryptedExpiryYear,
encrypted_security_code: state.data.paymentMethod.encryptedSecurityCode,
// The code should be available in state.data.paymentMethod.brand, but
// sometimes it's not there. We can usually still find it via component.
payment_submethod: mapAdyenSubmethod(
state.data.paymentMethod.brand || component.state.brand || onBrandSubmethod
)
};
if ( state.data.browserInfo ) {
extraData.color_depth = state.data.browserInfo.colorDepth;
extraData.java_enabled = state.data.browserInfo.javaEnabled;
extraData.screen_height = state.data.browserInfo.screenHeight;
extraData.screen_width = state.data.browserInfo.screenWidth;
extraData.time_zone_offset = state.data.browserInfo.timeZoneOffset;
}
break;
case 'google':
submitResolve( state.data.paymentMethod.googlePayToken );
return;
case 'apple':
// Resolve the submit promise with the Apple Pay token and bail out - we
// also need to wait for the onAuthorized event with contact data.
submitResolve( state.data.paymentMethod.applePayToken );
return;
}
// Allow other scripts (e.g. variants) to provide more data to submit
if ( typeof mw.donationInterface.getExtraData === 'function' ) {
Object.assign( extraData, mw.donationInterface.getExtraData() );
}
mw.donationInterface.forms.callDonateApi(
handleApiResult, extraData, 'di_donate_adyen'
);
}
};
} );
function handleApiResult( result ) {
if ( result.isFailed ) {
location.assign( mw.config.get( 'DonationInterfaceFailUrl' ) );
return;
}
if ( result.formData && Object.keys( result.formData ).length > 0 ) {
// FIXME: reconstructing the raw result from the API
// which has been normalized down to just these two
// fields. Should we just pass the raw Adyen API result
// back to the front end? Seems like we would only want
// a rawResult property on the front-end donation API
// response when we are damn sure it's sanitized.
checkout.createFromAction( {
paymentMethodType: 'scheme',
url: result.redirect,
data: result.formData,
method: 'POST',
type: 'redirect'
} ).mount( '#action-container' );
// canShowModal() is just a sanity check to see if the required DOM elements
// are there.
} else if ( mw.monthlyConvert && mw.monthlyConvert.canShowModal() ) {
mw.monthlyConvert.init();
} else if ( result.redirect ) {
document.location.replace( result.redirect );
} else {
document.location.replace( mw.config.get( 'DonationInterfaceThankYouPage' ) );
}
}
function onAdditionalDetails( state, dropin ) {
// Handle 3D secure
}
// T292571 try catch the adyen error, see if any connection been blocked, e.g. iframe
function onError( error ) {
// Ignore blank string - that means a previous error was cleared up
if ( typeof error.error === 'string' && error.error === '' ) {
return;
}
if ( typeof error.error === 'string' && error.error.slice( 0, 8 ) === 'error.va' ) {
// T349600 Log validation error codes only if sf-cc-num.02 donate_interface-error-msg-card-number-do-not-match-card-brand)
// with date time and time zone for adyen to investigate their card validation js issue
if ( error.error === 'error.va.sf-cc-num.02' ) {
error.error = 'Adyen error: ' + error.error + ' on ' + new Date().toString();
} else {
return;
}
} else {
// handle component error
mw.donationInterface.validation.showErrors( {
general: mw.msg( 'donate_interface-error-msg-general' )
} );
}
throw error;
}
function setLocaleAndTranslations( config, localeFromServer ) {
// Adyen supports the locales listed below, according to
// https://docs.adyen.com/online-payments/classic-integrations/checkout-sdks/web-sdk/customization/localization/
var adyenSupportedLocale = [
'zh-CN', 'zh-TW', 'hr-HR', 'cs-CZ',
'da-DK', 'nl-NL', 'en-US', 'fi-FI',
'fr-FR', 'de-DE', 'el-GR', 'hu-HU',
'it-IT', 'ja-JP', 'ko-KR', 'no-NO',
'pl-PL', 'pt-BR', 'ro-RO', 'ru-RU',
'sk-SK', 'sl-SL', 'es-ES', 'sv-SE'
], baseLocaleFromServer = localeFromServer.slice( 0, 2 );
// We support Norwegian Bokmal (nb) but Adyen's components just support the generic 'no' Norwegian code
if ( baseLocaleFromServer === 'nb' ) {
config.locale = 'no-NO';
} else {
config.locale = localeFromServer;
}
config.translations = {};
// Check if donor's language is unsupported by Adyen and we need to provide our own customized translation
// Adyen supports ar as Arabic - International and doesn't check the country part
if ( baseLocaleFromServer !== 'ar' && adyenSupportedLocale.indexOf( config.locale ) === -1 ) {
config.translations[ config.locale ] = {
//title
'creditCard.numberField.title': mw.msg( 'donate_interface-credit-card-number' ),
'creditCard.expiryDateField.title': mw.msg( 'donate_interface-credit-card-expiration' ),
'creditCard.cvcField.title': mw.msg( 'donate_interface-cvv' ),
//placeholder
'creditCard.expiryDateField.placeholder': mw.msg( 'donate_interface-expiry-date-field-placeholder' ),
'creditCard.cvcField.placeholder.3digits': mw.msg( 'donate_interface-cvv-placeholder-3-digits' ),
'creditCard.cvcField.placeholder.4digits': mw.msg( 'donate_interface-cvv-placeholder-4-digits' ),
//error
'creditCard.numberField.invalid': mw.msg( 'donate_interface-error-msg-invalid-card-number' ),
'creditCard.expiryDateField.invalid': mw.msg( 'donate_interface-error-msg-expiry-date-field-invalid' ),
'error.va.gen.01': mw.msg( 'donate_interface-error-msg-incomplete-field' ),
'error.va.gen.02': mw.msg( 'donate_interface-error-msg-field-not-valid' ),
'error.va.sf-cc-num.01': mw.msg( 'donate_interface-error-msg-invalid-card-number' ),
'error.va.sf-cc-num.02': mw.msg( 'donate_interface-error-msg-card-number-do-not-match-card-brand' ),
'error.va.sf-cc-num.03': mw.msg( 'donate_interface-error-msg-unsupported-card-entered' ),
'error.va.sf-cc-dat.01': mw.msg( 'donate_interface-error-msg-card-too-old' ),
'error.va.sf-cc-dat.02': mw.msg( 'donate_interface-error-msg-date-too-far-in-the-future' )
};
} else if ( config.locale === 'nl-NL' ) {
config.translations[ config.locale ] = {
'idealIssuer.selectField.placeholder': mw.msg( 'donate_interface-rtbt-issuer_id' )
};
} else if ( language === 'ja' ) {
config.translations[ config.locale ] = {
'creditCard.expiryDateField.placeholder': mw.msg( 'donate_interface-expiry-date-field-placeholder' ),
'creditCard.cvcField.placeholder.3digits': '',
'creditCard.cvcField.placeholder.4digits': ''
};
} else {
config.translations[ config.locale ] = {};
}
if ( mw.config.get( 'payment_submethod' ) === 'ach' ) {
config.translations[ config.locale ] = {
'error.va.sf-ach-loc.02': mw.msg( 'donate_interface-ach-invalid-routing-number-count' ),
'error.va.sf-ach-num.02': mw.msg( 'donate_interface-ach-invalid-account-number-count' )
};
}
// if sepa, update holder name to account holder name
if ( mw.config.get( 'payment_submethod' ) === 'sepadirectdebit' ) {
config.translations[ config.locale ][ 'sepa.ownerName' ] = mw.msg( 'donate_interface-bt-account_holder' );
}
// Allow other scripts (e.g. variants) to provide more translations to the Adyen components
if ( mw.donationInterface.extraTranslations ) {
Object.assign( config.translations[ config.locale ], mw.donationInterface.extraTranslations );
}
}
/**
* Runs as soon as the external Adyen checkout script is loaded
*/
function setup() {
var component_type,
config,
containerName = 'component-container',
oldShowErrors,
checkoutPromise;
component_type = mapPaymentMethodToComponentType( payment_method );
// Drop in the adyen components placeholder container
$( '.submethods' ).before(
'<div id="' + containerName + '" />'
).before(
'<div id="action-container" />'
);
// add name placeholder for ja JP
if ( language === 'ja' ) {
$( '#last_name' ).attr( 'placeholder', '鈴木' );
$( '#first_name' ).attr( 'placeholder', '太郎' );
}
// Override validation's showErrors function to add error
// highlights to the outer div around the secure field iframe.
// FIXME: cleaner object-oriented JS with inheritance would
// make this prettier. See https://phabricator.wikimedia.org/T293287
oldShowErrors = mw.donationInterface.validation.showErrors;
mw.donationInterface.validation.showErrors = function ( errors ) {
var adyenFieldName;
$.each( errors, function ( field ) {
adyenFieldName = false;
if ( field === 'card_num' || field === 'encrypted_card_number' ) {
adyenFieldName = 'encryptedCardNumber';
} else if ( field === 'encrypted_expiry_month' || field === 'encrypted_expiry_year' ) {
adyenFieldName = 'encryptedExpiryDate';
} else if ( field === 'cvv' ) {
adyenFieldName = 'encryptedSecurityCode';
}
if ( adyenFieldName ) {
$( 'span[data-cse=' + adyenFieldName + ']' )
.closest( '.adyen-checkout__input-wrapper' )
.addClass( 'errorHighlight' );
}
} );
oldShowErrors( errors );
};
// Copy values to leave the mw.config setting untouched
config = {
clientKey: configFromServer.clientKey,
environment: configFromServer.environment,
paymentMethodsResponse: configFromServer.paymentMethodsResponse
};
setLocaleAndTranslations( config, configFromServer.locale );
checkoutPromise = getCheckout( config );
checkoutPromise.then( function ( checkoutObject ) {
checkout = checkoutObject;
createAndMountComponent( config, component_type, containerName );
} );
}
function createAndMountComponent( config, component_type, containerName ) {
var component_config = getComponentConfig( component_type, config ),
component = checkout.create( component_type, component_config );
if ( component_type === GOOGLEPAY_COMPONENT_TYPE ) {
component.isAvailable().then( function () {
component.mount( '#' + containerName );
} ).catch( function () {
mw.donationInterface.validation.showErrors( {
general: mw.message(
'donate_interface-error-msg-google_pay_unsupported',
mw.config.get( 'DonationInterfaceOtherWaysURL' )
).plain()
} );
} );
// For Google Pay, we need contact data from the onAuthorized event and token
// data from the onSubmit event before we can make our MediaWiki API call.
Promise.all( [ submitPromise, authPromise ] ).then( function ( values ) {
var extraData = values[ 1 ];
extraData.payment_token = values[ 0 ];
mw.donationInterface.forms.callDonateApi(
handleApiResult, extraData, 'di_donate_adyen'
);
} ).catch( function ( err ) {
mw.donationInterface.validation.showErrors( {
general: mw.msg( 'donate_interface-error-msg-general' )
} );
// Let error bubble up to window.onerror handler so the errorLog
// module sends it to our client-side logging endpoint.
throw err;
} );
} else if ( component_type === 'applepay' ) {
component.isAvailable().then( function () {
component.mount( '#' + containerName );
} ).catch( function () {
mw.donationInterface.validation.showErrors( {
general: mw.message(
'donate_interface-error-msg-apple_pay_unsupported',
mw.config.get( 'DonationInterfaceOtherWaysURL' )
).plain()
} );
} );
// For Apple Pay, we need contact data from the onAuthorized event and token
// data from the onSubmit event before we can make our MediaWiki API call.
Promise.all( [ submitPromise, authPromise ] ).then( function ( values ) {
var extraData = values[ 1 ];
extraData.payment_token = values[ 0 ];
mw.donationInterface.forms.callDonateApi(
handleApiResult, extraData, 'di_donate_adyen'
);
} ).catch( function ( err ) {
mw.donationInterface.validation.showErrors( {
general: mw.msg( 'donate_interface-error-msg-general' )
} );
// Let error bubble up to window.onerror handler so the errorLog
// module sends it to our client-side logging endpoint.
throw err;
} );
} else {
try {
component.mount( '#' + containerName );
} catch ( err ) {
mw.donationInterface.validation.showErrors( {
general: mw.msg( 'donate_interface-error-msg-general' )
} );
throw err;
}
// For everything except Apple and google
// Pay, show our standard 'Donate' button
$( '#paymentSubmit' ).show();
$( '#paymentSubmitBtn' ).click( mw.util.debounce( function ( evt ) {
component.submit( evt );
}, 100 ) );
}
}
/**
* On documentready we create a script tag and wire it up to run setup as soon as it
* is loaded, or to show an error message if the external script can't be loaded.
* The script should already be mostly or completely preloaded at this point, thanks
* to a <link rel=preload> we add in AdyenCheckoutGateway::addGatewaySpecificResources
*/
function loadScript( type ) {
var scriptNode = document.createElement( 'script' );
scriptNode.onerror = function () {
mw.donationInterface.validation.showErrors(
{ general: 'Could not load payment provider Javascript. Please reload or try again later.' }
);
};
if ( type === 'adyen' ) {
scriptNode.onload = setup;
scriptNode.crossOrigin = 'anonymous';
scriptNode.integrity = configFromServer.script.integrity;
scriptNode.src = configFromServer.script.src;
} else {
scriptNode.src = configFromServer.googleScript;
}
document.body.append( scriptNode );
}
$( function () {
if ( !configFromServer ) {
// If the configuration has not been passed from the server, we are likely on the
// ResultSwitcher page and have just been loaded incidentally to make a form for
// a backdrop of the monthly convert popup. As the rest of this function is only
// needed to set up payment widgets which will not be needed here, just quit.
// It might be better to stop loading adyen.js in that situation, but that's a
// bigger refactor than we want to do right now.
return;
}
if ( payment_method === 'google' ) {
loadScript( 'google' );
}
loadScript( 'adyen' );
} );
} )( jQuery, mediaWiki );