Skip to content

Commit

Permalink
Feature: add option to disable one time donations for recurring enabl…
Browse files Browse the repository at this point in the history
…ed forms (#6886)

* feature: add recurringDisableOneTimeDonations option

* fix: typo

* refactor: only show option when donor choice is selected

* fix: recurringDonationChoice should be donor by default

* refactor: add one time to billing period options

* Revert "refactor: add one time to billing period options"

This reverts commit 56c3949.

* refactor: rename to recurringEnableOneTimeDonations

* refactor: update logic to determine when recurring details are fixed instead of donation choice

* refactor: update recurring options logic and types

* refactor: clean up converter with donation amount block model decorator

* fix: default level

* tests: update backwards compatiblity test

---------

Co-authored-by: Jon Waldstein <[email protected]>
  • Loading branch information
jonwaldstein and Jon Waldstein authored Aug 28, 2023
1 parent 48da0aa commit 4bbd975
Show file tree
Hide file tree
Showing 13 changed files with 427 additions and 249 deletions.
83 changes: 39 additions & 44 deletions src/DonationForms/Actions/ConvertDonationAmountBlockToFieldsApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
use Give\DonationForms\Rules\SubscriptionInstallmentsRule;
use Give\DonationForms\Rules\SubscriptionPeriodRule;
use Give\Donations\ValueObjects\DonationType;
use Give\Framework\Blocks\BlockModel;
use Give\FormBuilder\BlockModels\DonationAmountBlockModel;
use Give\Framework\FieldsAPI\Amount;
use Give\Framework\FieldsAPI\DonationAmount;
use Give\Framework\FieldsAPI\Exceptions\EmptyNameException;
Expand All @@ -32,19 +32,19 @@ class ConvertDonationAmountBlockToFieldsApi
* @throws EmptyNameException
* @throws NameCollisionException
*/
public function __invoke(BlockModel $block, string $currency): DonationAmount
public function __invoke(DonationAmountBlockModel $block, string $currency): DonationAmount
{
$amountField = DonationAmount::make('donationAmount')->tap(function (Group $group) use ($block, $currency) {
$amountRules = ['required', 'numeric'];

if (!$block->getAttribute('customAmount') &&
$block->getAttribute('priceOption') === 'set') {
$size = $block->getAttribute('setPrice');
if (!$block->isCustomAmountEnabled() &&
$block->getPriceOption() === 'set') {
$size = $block->getSetPrice();

$amountRules[] = new Size($size);
}

if ($block->getAttribute('customAmount')) {
if ($block->isCustomAmountEnabled()) {
if ($block->hasAttribute('customAmountMin')) {
$amountRules[] = new Min($block->getAttribute('customAmountMin'));
}
Expand All @@ -56,16 +56,16 @@ public function __invoke(BlockModel $block, string $currency): DonationAmount

/** @var Amount $amountNode */
$amountNode = $group->getNodeByName('amount');
$defaultLevel = (float)$block->getAttribute('defaultLevel') > 0 ? (float)$block->getAttribute('defaultLevel') : 10;
$defaultLevel = (float)$block->getDefaultLevel() > 0 ? (float)$block->getDefaultLevel() : 10;
$amountNode
->label($block->getAttribute('label'))
->levels(...array_map('absint', $block->getAttribute('levels')))
->allowLevels($block->getAttribute('priceOption') === 'multi')
->allowCustomAmount($block->getAttribute('customAmount'))
->fixedAmountValue($block->getAttribute('setPrice'))
->label($block->getLabel())
->levels(...$block->getLevels())
->allowLevels($block->getPriceOption() === 'multi')
->allowCustomAmount($block->isCustomAmountEnabled())
->fixedAmountValue($block->getSetPrice())
->defaultValue(
$block->getAttribute('priceOption') === 'set' ?
$block->getAttribute('setPrice') : $defaultLevel
$block->getPriceOption() === 'set' ?
$block->getSetPrice() : $defaultLevel
)
->rules(...$amountRules);

Expand All @@ -76,7 +76,7 @@ public function __invoke(BlockModel $block, string $currency): DonationAmount
->rules('required', 'currency');
});

if (!$block->getAttribute('recurringEnabled')) {
if (!$block->isRecurringEnabled()) {
$donationType = Hidden::make('donationType')
->defaultValue(DonationType::SINGLE()->getValue())
->rules(new DonationTypeRule());
Expand All @@ -92,20 +92,17 @@ public function __invoke(BlockModel $block, string $currency): DonationAmount
->defaultValue($donationTypeDefault)
->rules(new DonationTypeRule());

$billingInterval = (int)$block->getAttribute('recurringBillingInterval');
$lengthOfTime = (int)$block->getAttribute('recurringLengthOfTime');

$subscriptionFrequency = Hidden::make('subscriptionFrequency')
->defaultValue($billingInterval)
->defaultValue($block->getRecurringBillingInterval())
->rules(new SubscriptionFrequencyRule());

$subscriptionInstallments = Hidden::make('subscriptionInstallments')
->defaultValue($lengthOfTime)
->defaultValue($block->getRecurringLengthOfTime())
->rules(new SubscriptionInstallmentsRule());

$amountField
->enableSubscriptions()
->subscriptionDetailsAreFixed($block->getAttribute('recurringDonationChoice') === 'admin')
->subscriptionDetailsAreFixed($block->isRecurringFixed())
->donationType($donationType)
->subscriptionPeriod($subscriptionPeriod)
->subscriptionFrequency($subscriptionFrequency)
Expand All @@ -120,32 +117,40 @@ public function __invoke(BlockModel $block, string $currency): DonationAmount
*
* @throws EmptyNameException
*/
protected function getRecurringAmountPeriodField(BlockModel $block): Field
protected function getRecurringAmountPeriodField(DonationAmountBlockModel $block): Field
{
$donationChoice = $block->getAttribute('recurringDonationChoice');
$recurringBillingPeriodOptions = $block->getRecurringBillingPeriodOptions();

// if recurring is fixed - fields are all hidden
if ($block->isRecurringFixed()) {
$fixedBillingPeriod = $recurringBillingPeriodOptions[0];

// if admin - fields are all hidden
if ($donationChoice === 'admin') {
$recurringBillingPeriod = new SubscriptionPeriod($block->getAttribute('recurringBillingPeriod'));
$subscriptionPeriodDefaultValue = SubscriptionPeriod::isValid(
$fixedBillingPeriod
) ? (new SubscriptionPeriod($fixedBillingPeriod))->getValue() : SubscriptionPeriod::MONTH()->getValue();

return Hidden::make('subscriptionPeriod')
->defaultValue($recurringBillingPeriod->getValue())
->defaultValue($subscriptionPeriodDefaultValue)
->rules(new SubscriptionPeriodRule());
}

$recurringBillingPeriodOptions = $block->getAttribute('recurringBillingPeriodOptions');
if ($block->isRecurringEnableOneTimeDonations()) {
$recurringBillingPeriodOptions = array_merge(['one-time'], $recurringBillingPeriodOptions);
}

$options = $this->mergePeriodOptionsWithOneTime(
array_map(static function ($option) {
$options = array_map(static function ($option) {
if (SubscriptionPeriod::isValid($option)) {
$subscriptionPeriod = new SubscriptionPeriod($option);

return new Option($subscriptionPeriod->getValue(), $subscriptionPeriod->label(0));
}, $recurringBillingPeriodOptions)
);
}

return new Option($option, $option === 'one-time' ? __('One Time', 'give') : ucfirst($option));
}, $recurringBillingPeriodOptions);

$recurringOptInDefault = $block->getAttribute('recurringOptInDefaultBillingPeriod');
$recurringOptInDefault = $block->getRecurringOptInDefaultBillingPeriod();

if (!empty($recurringOptInDefault) && $recurringOptInDefault !== 'one-time') {
if (SubscriptionPeriod::isValid($recurringOptInDefault)) {
$subscriptionPeriod = new SubscriptionPeriod($recurringOptInDefault);

$defaultValue = $subscriptionPeriod->getValue();
Expand All @@ -159,14 +164,4 @@ protected function getRecurringAmountPeriodField(BlockModel $block): Field
->options(...$options)
->rules(new SubscriptionPeriodRule());
}

/**
* @since 3.0.0
*/
protected function mergePeriodOptionsWithOneTime(array $options): array
{
return array_merge([
new Option('one-time', __('One Time', 'give'))
], $options);
}
}
18 changes: 10 additions & 8 deletions src/DonationForms/Actions/ConvertDonationFormBlocksToFieldsApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Give\DonationForms\Rules\BillingAddressStateRule;
use Give\DonationForms\Rules\BillingAddressZipRule;
use Give\DonationForms\Rules\GatewayRule;
use Give\FormBuilder\BlockModels\DonationAmountBlockModel;
use Give\Framework\Blocks\BlockCollection;
use Give\Framework\Blocks\BlockModel;
use Give\Framework\FieldsAPI\Authentication;
Expand Down Expand Up @@ -167,13 +168,13 @@ protected function createNodeFromBlockWithUniqueAttributes(BlockModel $block, in
});

case "givewp/payment-gateways":
$defaultGatewayId = give( DonationFormRepository::class )->getDefaultEnabledGatewayId( $this->formId );
$defaultGatewayId = give(DonationFormRepository::class)->getDefaultEnabledGatewayId($this->formId);

return PaymentGateways::make( 'gatewayId' )
->testMode( give_is_test_mode() )
->rules( new GatewayRule() )
->required()
->defaultValue( $defaultGatewayId );
return PaymentGateways::make('gatewayId')
->testMode(give_is_test_mode())
->rules(new GatewayRule())
->required()
->defaultValue($defaultGatewayId);

case "givewp/donation-summary":
return DonationSummary::make('donation-summary');
Expand Down Expand Up @@ -206,7 +207,7 @@ protected function createNodeFromBlockWithUniqueAttributes(BlockModel $block, in
->loginConfirmation($block->getAttribute('loginConfirmation'))
->tapNode('login', function ($field) use ($block) {
if ($block->getAttribute('required')) {
if ( ! is_user_logged_in()) {
if (!is_user_logged_in()) {
$field->required();
}

Expand Down Expand Up @@ -350,7 +351,8 @@ protected function createNodeFromBillingAddressBlock(BlockModel $block): Node
*/
protected function createNodeFromAmountBlock(BlockModel $block): Node
{
return (new ConvertDonationAmountBlockToFieldsApi())($block, $this->currency);
$donationAmountBlockModel = new DonationAmountBlockModel($block);
return (new ConvertDonationAmountBlockToFieldsApi())($donationAmountBlockModel, $this->currency);
}

/**
Expand Down
146 changes: 146 additions & 0 deletions src/FormBuilder/BlockModels/DonationAmountBlockModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php

namespace Give\FormBuilder\BlockModels;

use Give\Framework\Blocks\BlockModel;

/**
* This is a decorator for the Block Model block "givewp/donation-amount".
*
* @unreleased
*/
class DonationAmountBlockModel {
/**
* @var BlockModel
*/
public $block;

/**
* @unreleased
*/
public function __construct(BlockModel $block) {
$this->block = $block;
}

/**
* @unreleased
*/
public function getAttribute($name)
{
return $this->block->getAttribute($name);
}

/**
* @unreleased
*/
public function hasAttribute($name): bool
{
return $this->block->hasAttribute($name);
}

/**
* @unreleased
*/
public function getLabel(): string
{
return $this->block->getAttribute('label');
}

/**
* @unreleased
*/
public function getLevels(): array
{
return array_map('absint', $this->block->getAttribute('levels'));
}

/**
* @unreleased
*
* @return string|null
*/
public function getDefaultLevel()
{
return $this->block->getAttribute('defaultLevel');
}

/**
* @return bool
*/
public function isRecurringFixed(): bool
{
return count($this->block->getAttribute('recurringBillingPeriodOptions')) === 1 && $this->block->getAttribute('recurringEnableOneTimeDonations') === false;
}

/**
* @unreleased
*/
public function getRecurringBillingInterval(): int
{
return (int)$this->block->getAttribute('recurringBillingInterval');
}

/**
* @unreleased
*/
public function getRecurringLengthOfTime(): int
{
return (int)$this->block->getAttribute('recurringLengthOfTime');
}

/**
* @unreleased
*/
public function getRecurringOptInDefaultBillingPeriod(): string
{
return $this->block->getAttribute('recurringOptInDefaultBillingPeriod');
}

/**
* @unreleased
*/
public function getRecurringBillingPeriodOptions(): array
{
return $this->block->getAttribute('recurringBillingPeriodOptions');
}

/**
* @unreleased
*/
public function isRecurringEnableOneTimeDonations(): bool
{
return $this->block->getAttribute('recurringEnableOneTimeDonations') === true;
}

/**
* @unreleased
*/
public function isRecurringEnabled(): bool
{
return $this->block->getAttribute('recurringEnabled') === true;
}

/**
* @unreleased
*/
public function isCustomAmountEnabled(): bool
{
return $this->block->getAttribute('customAmount') === true;
}

/**
* @unreleased
*/
public function getPriceOption(): string
{
return $this->block->getAttribute('priceOption');
}

/**
* @unreleased
*/
public function getSetPrice(): int
{
return $this->block->getAttribute('setPrice');
}
}
5 changes: 2 additions & 3 deletions src/FormBuilder/resources/js/form-builder/src/blocks.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,16 @@
"defaultLevel": "10",
"priceOption": "multi",
"setPrice": 25,
"customAmount": "true",
"customAmount": true,
"customAmountMin": 1,
"recurringBillingPeriodOptions": [
"month"
],
"recurringBillingPeriod": "month",
"recurringBillingInterval": 1,
"recurringDonationChoice": "admin",
"recurringEnabled": false,
"recurringLengthOfTime": "0",
"recurringOptInDefaultBillingPeriod": "month",
"recurringEnableOneTimeDonations": true,
"lock": {
"remove": true
}
Expand Down
Loading

0 comments on commit 4bbd975

Please sign in to comment.