wikimedia/mediawiki-extensions-DonationInterface

View on GitHub
special/RecurUpgrade.php

Summary

Maintainability
B
5 hrs
Test Coverage
<?php

use MediaWiki\Extension\DonationInterface\RecurUpgrade\Validator;
use Psr\Log\LoggerInterface;
use SmashPig\Core\DataStores\QueueWrapper;

class RecurUpgrade extends UnlistedSpecialPage {

    const FALLBACK_COUNTRY = 'US';
    const FALLBACK_LANGUAGE = 'en_US';
    const FALLBACK_CURRENCY = 'USD';

    const DONOR_DATA = 'Donor';

    // Note: Coordinate with Getpreferences.php in Civiproxy API, in wmf-civicrm extension.
    const CIVI_NO_RESULTS_ERROR = 'No result found';

    protected Validator $validator;

    public function __construct() {
        parent::__construct( 'RecurUpgrade' );
        $this->validator = new Validator( $this->getRequest()->getSession(), $this->getConfig() );
    }

    public function execute( $subpage ) {
        $this->setHeaders();
        $this->addStylesScriptsAndViewport();

        $params = $this->getRequest()->getValues();
        $posted = $this->getRequest()->wasPosted();
        if ( !$this->validator->validate( $params, $posted ) ) {
            $this->renderError( $params );
            return;
        }
        if ( $posted ) {
            $this->executeRecurUpgrade( $params );
        } else {
            $formParams = $this->paramsForRecurUpgradeForm(
                $params[ 'checksum' ],
                $params[ 'contact_id' ],
                $params[ 'country' ] ?? null
            );
            if ( $formParams === null ) {
                $this->renderError( $params );
                return;
            } elseif ( $formParams['is_error'] && $formParams[ 'error_message' ] === self::CIVI_NO_RESULTS_ERROR ) {
                $this->renderEmpty( $params );
                return;
            }
            $this->addDataToSession( $formParams );
            $params += $formParams;
            if ( $this->wasCanceled( $params ) ) {
                $this->sendCancelRecurringUpgradeQueue( $formParams['contribution_recur_id'], $params[ 'contact_id' ] );
                $this->redirectToCancel( $formParams['country'] );
            }
            $this->renderForm( 'recurUpgrade', $params );
        }
    }

    protected function sendCancelRecurringUpgradeQueue( $contributionID, $contactID ) {
        $logger = self::getLogger();
        $message = [
            'txn_type' => 'recurring_upgrade_decline',
            'contribution_recur_id' => $contributionID,
            'contact_id' => $contactID,
        ] + $this->getTrackingParametersWithoutPrefix();
        try {
            $logger->info( "Pushing recurring_upgrade_decline to recurring-modify queue with contribution_recur_id: {$message['contribution_recur_id']}" );
            QueueWrapper::push( 'recurring-modify', $message );
        } catch ( Exception $e ) {
            $logger->error( "Error pushing recurring_upgrade_decline message: {$e->getMessage()}" );
        }
    }

    protected function paramsForRecurUpgradeForm( $checksum, $contactID, $country ): ?array {
        $recurData = CiviproxyConnect::getRecurDetails( $checksum, $contactID );
        if ( $recurData[ 'is_error' ] ) {
            $logger = self::getLogger();

            if ( $recurData[ 'error_message' ] == self::CIVI_NO_RESULTS_ERROR ) {
                $logger->warning(
                    "No results for contact_id $contactID with checksum $checksum" );
                return $recurData;
            } else {
                $logger->error( 'Error from civiproxy: ' . $recurData[ 'error_message' ] );
            }
            return null;
        }
        $nextDateFormatted = EmailForm::dateFormatter( $recurData['next_sched_contribution_date'] );

        $uiLang = $this->getLanguage()->getCode();
        $locale = self::FALLBACK_LANGUAGE;
        // If no country param on query string, use the country from the donor address.
        $country ??= $recurData['country'];
        if ( $country && $uiLang ) {
            $locale = $uiLang . '_' . $country;
        }

        $allRecurringOptions = $this->getConfig()->get( 'DonationInterfaceRecurringUpgradeOptions' );
        $currency = $recurData['currency'];
        $optionsForSelectedCurrency = $allRecurringOptions[$currency] ?? $allRecurringOptions[self::FALLBACK_CURRENCY];
        $optionsForTemplate = [];
        foreach ( $optionsForSelectedCurrency as $option ) {
            $optionsForTemplate[] = [
                'value' => $option,
                'value_formatted' => EmailForm::amountFormatter( $option, $locale, $currency )
            ];
        }

        return [
            'full_name' => $recurData['donor_name'],
            'recur_amount' => $recurData['amount'],
            'recur_amount_formatted' => EmailForm::amountFormatter( $recurData['amount'], $locale, $currency ),
            'contribution_recur_id' => $recurData['id'],
            'next_sched_contribution_date' => $recurData['next_sched_contribution_date'],
            'next_sched_contribution_date_formatted' => $nextDateFormatted,
            'country' => $country ?? self::FALLBACK_COUNTRY,
            'currency' => $currency,
            'maximum' => $this->validator->getMaxInSelectedCurrency( $recurData ),
            'locale' => $locale,
            'recurringOptions' => $optionsForTemplate,
        ] + $this->getTrackingParametersForForm();
    }

    protected function executeRecurUpgrade( $params ) {
        $logger = self::getLogger();
        $donorData = WmfFramework::getSessionValue( self::DONOR_DATA );
        if ( !isset( $donorData['contribution_recur_id'] ) ) {
            $this->renderError( $params );
            return;
        }
        if ( $this->wasCanceled( $params ) ) {
            $this->sendCancelRecurringUpgradeQueue( $donorData['contribution_recur_id'], $params['contact_id'] );
            $this->redirectToCancel( $donorData['country'] );
            return;
        }
        $upgradeAmount = ( $params['upgrade_amount'] === 'other' )
            ? $params['upgrade_amount_other']
            : $params['upgrade_amount'];

        $amount = $donorData['amount'] + round( (double)$upgradeAmount, 2 );
        $message = [
            'txn_type' => 'recurring_upgrade',
            'contribution_recur_id' => $donorData['contribution_recur_id'],
            'amount' => $amount,
            'currency' => $donorData['currency'],
        ] + $this->getTrackingParametersWithoutPrefix();

        try {
            $logger->info( "Pushing upgraded amount to recurring-modify queue with contribution_recur_id: {$message['contribution_recur_id']}" );
            QueueWrapper::push( 'recurring-modify', $message );
            $this->redirectToSuccess( $donorData, $amount );
        } catch ( Exception $e ) {
            $logger->error( "Error pushing upgraded amount to recurring-modify queue: {$e->getMessage()}" );
            $this->renderError( $params );
        }
    }

    protected function redirectToCancel( ?string $country ): void {
        $this->redirectToThankYouPage( [
            'country' => $country,
            'recurUpgrade' => 0,
        ] );
    }

    protected function renderError( array $params ) {
        $this->renderForm( 'recurUpgradeError', $params );
    }

    protected function renderEmpty( array $params ) {
        $this->renderForm( 'recurUpgradeEmpty', $params );
    }

    protected function redirectToSuccess( array $donorData, float $amount ): void {
        $redirectParams = [
            'country' => $donorData['country'],
            'recurAmount' => $amount,
            'recurCurrency' => $donorData['currency'],
            'recurDate' => substr( $donorData['next_sched_contribution_date'], 0, 10 ),
            'recurUpgrade' => 1,
        ];
        $this->redirectToThankYouPage( $redirectParams );
    }

    protected function redirectToThankYouPage( array $params ): void {
        $page = $this->getConfig()->get( 'DonationInterfaceThankYouPage' );
        $page = ResultPages::appendLanguageAndMakeURL(
            $page,
            $this->getLanguage()->getCode()
        );

        $this->getOutput()->redirect(
            wfAppendQuery( $page, $params + $this->getTrackingParametersWithPrefix() )
        );
    }

    protected function renderForm( string $templateName, array $params ) {
        $formObj = new EmailForm( $templateName, $params );
        $this->getOutput()->addHTML( $formObj->getForm() );
        $this->getConfig();
    }

    protected function wasCanceled( $params ) {
        return ( isset( $params['submit'] ) && ( $params['submit'] === 'cancel' ) );
    }

    protected function addDataToSession( array $formParams ) {
        WmfFramework::setSessionValue( self::DONOR_DATA, [
            'contribution_recur_id' => $formParams['contribution_recur_id'],
            'amount' => $formParams['recur_amount'],
            'currency' => $formParams['currency'],
            'country' => $formParams['country'],
            'next_sched_contribution_date' => $formParams['next_sched_contribution_date'],
        ] );
    }

    protected function getTrackingParametersWithPrefix() {
        return $this->getRequest()->getValues( 'wmf_campaign', 'wmf_medium', 'wmf_source' );
    }

    protected function getTrackingParametersWithoutPrefix() {
        $paramsWithPrefix = $this->getTrackingParametersWithPrefix();
        $paramsWithoutPrefix = [];
        foreach ( $paramsWithPrefix as $name => $value ) {
            $paramsWithoutPrefix[ substr( $name, 4 ) ] = $value;
        }
        return $paramsWithoutPrefix;
    }

    protected function getTrackingParametersForForm() {
        return $this->getTrackingParametersWithoutPrefix() + [
            'campaign' => '',
            'medium' => '',
            'source' => '',
        ];
    }

    /**
     * @return void
     */
    public function addStylesScriptsAndViewport(): void {
        $out = $this->getOutput();

        // Adding styles-only modules this way causes them to arrive ahead of page rendering
        $out->addModuleStyles( [
            'donationInterface.skinOverrideStyles',
            'ext.donationInterface.emailPreferencesStyles',
        ] );

        $out->addModules( [
            'ext.donationInterface.emailPreferences',
            'ext.donationInterface.recurUpgrade',
            'ext.donationInterface.errorLog',
        ] );

        // Tell the errorLog module which action to call
        $out->addJsConfigVars( [
            'ClientErrorLogAction' => 'logRecurUpgradeFormError',
        ] );

        $out->addHeadItem(
            'viewport',
            Html::element(
                'meta', [
                    'name' => 'viewport',
                    'content' => 'width=device-width, initial-scale=1',
                ]
            )
        );
    }

    public static function getLogger(): LoggerInterface {
        return DonationLoggerFactory::getLoggerFromParams(
            'RecurUpgrade', true, false, '', null );
    }
}