Skip to content

Commit

Permalink
CIWEMB-320: User can optionally record payment for new membership
Browse files Browse the repository at this point in the history
  • Loading branch information
olayiwola-compucorp committed Jul 28, 2023
1 parent 8e337af commit 3a7ce22
Show file tree
Hide file tree
Showing 5 changed files with 308 additions and 0 deletions.
70 changes: 70 additions & 0 deletions Civi/Financeextras/Hook/BuildForm/MembershipCreate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

namespace Civi\Financeextras\Hook\BuildForm;

use CRM_Financeextras_ExtensionUtil as E;

class MembershipCreate {

/**
* @param \CRM_Member_Form_Membership $form
*/
public function __construct(private \CRM_Member_Form_Membership $form) {
}

public function handle() {
$this->configureRecordContributionField();
}

/**
* CiviCRM by default always shows the payment fields for new contribution,
* This method configures the display of these payment related fields by
* grouping them together as a block and adds a 'Record Payment' checkbox
* that determines visibility of this block.
*
* Also, adds a payment amount field, that allows users to specify the amount
* they would like to record for the contribution.
*/
private function configureRecordContributionField() {
if (!$this->isEdit()) {
$this->form->addElement('radio', 'fe_member_type', NULL, ts('Paid Membership'), 'paid_member');
$this->form->addElement('radio', 'fe_member_type', NULL, ts('Free Membership'), 'free_member');
$this->form->add('checkbox', 'fe_record_payment_check', ts('Record Payment'), NULL);
$this->form->add('text', 'fe_record_payment_amount', ts('Amount'), ['readonly' => TRUE]);
\Civi::resources()->add([
'scriptFile' => [E::LONG_NAME, 'js/modifyMemberForm.js'],
'region' => 'page-header',
]);
\Civi::resources()->add([
'template' => 'CRM/Financeextras/Form/Member/AddPayment.tpl',
'region' => 'page-body',
]);
\Civi::resources()->addVars('financeextras', ['currencySymbol' => $this->getCurrencySymbol()]);
}
}

/**
* @return string
*/
private function getCurrencySymbol() {
$config = \CRM_Core_Config::singleton();
return \CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_Currency', $config->defaultCurrency, 'symbol', 'name');
}

private function isEdit() {
return !empty($this->form->_id);
}

/**
* Checks if the hook should run.
*
* @param \CRM_Core_Form $form
* @param string $formName
*
* @return bool
*/
public static function shouldHandle($form, $formName) {
return $formName === "CRM_Member_Form_Membership";
}

}
56 changes: 56 additions & 0 deletions Civi/Financeextras/Hook/ValidateForm/MembershipCreate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace Civi\Financeextras\Hook\ValidateForm;

use Civi\Financeextras\Utils\OptionValueUtils;

class MembershipCreate {

/**
* @param \CRM_Member_Form_Membership $form
* @param array $fields
* @param array $errors
*/
public function __construct(private \CRM_Member_Form_Membership $form, private array &$fields, private array &$errors) {
}

public function handle() {
$this->validatePaymentForm();
}

public function validatePaymentForm() {
if (empty($this->fields['record_contribution'])) {
// Don't do anything if user will not be recording contribution
return;
}

if ($this->fields['contribution_type_toggle'] == 'payment_plan') {
// Don't do anything if membership is paid for using the payment plan option
return;
}

if (empty($this->fields['fe_record_payment_check'])) {
$data = &$this->form->controller->container();
// Before the form is submitted, we ensure that if the "Record Payment" box is unchecked,
// payment will not be recorded against the contribution.
$data['values']['Membership']['contribution_status_id'] = OptionValueUtils::getValueForOptionValue('contribution_status', 'Pending');
}

if (!empty($this->fields['fe_record_payment_check']) && empty($this->fields['fe_record_payment_amount'])) {
$this->errors['fe_record_payment_amount'] = ts('Payment amount is required');
}
}

/**
* Checks if the hook should run.
*
* @param CRM_Core_Form $form
* @param string $formName
*
* @return bool
*/
public static function shouldHandle($form, $formName) {
return $formName === "CRM_Member_Form_Membership" && ($form->_action & \CRM_Core_Action::ADD);
}

}
2 changes: 2 additions & 0 deletions financeextras.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ function financeextras_civicrm_post($op, $objectName, $objectId, &$objectRef) {
*/
function financeextras_civicrm_validateForm($formName, &$fields, &$files, &$form, &$errors) {
$hooks = [
\Civi\Financeextras\Hook\ValidateForm\MembershipCreate::class,
\Civi\Financeextras\Hook\ValidateForm\ContributionCreate::class,
];

Expand Down Expand Up @@ -176,6 +177,7 @@ function financeextras_civicrm_postProcess($formName, $form) {
*/
function financeextras_civicrm_buildForm($formName, &$form) {
$hooks = [
\Civi\Financeextras\Hook\BuildForm\MembershipCreate::class,
\Civi\Financeextras\Hook\BuildForm\ContributionCreate::class,
];

Expand Down
146 changes: 146 additions & 0 deletions js/modifyMemberForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
CRM.$(function ($) {

(function() {
setTotalAmount();
togglePaymentBlock();
toggleMembershipType();
toggleContributionBlock();
placePaymentFieldsTogether();
hidePaymentFieldsOnPaymentToggler();
})();

function setTotalAmount() {
const recordPaymentAmount = document.querySelector("input[name=fe_record_payment_amount]");
observeElement('input[name=total_amount]', "value", function () {
toggleMembershipType();
recordPaymentAmount.value = $('#total_amount').val();
});
}

function togglePaymentBlock() {
$('input[name=fe_record_payment_check]').prop("checked", true).trigger('change')

$('input[name=fe_record_payment_check]').on('change', () => {
const recordPayment = $('input[name=fe_record_payment_check]').is(':checked')
$('tr.record_payment-block_row').toggle(recordPayment)
});
}

function toggleMembershipType() {
if ($('input[name=record_contribution]').is(':checked')) {
$('input[name=fe_member_type][value=paid_member]').prop("checked", true)
} else {
$('input[name=fe_member_type][value=free_member]').prop("checked", true)
}
}

function toggleContributionBlock() {
toggleMembershipType();

$('tr#contri').after(
$('<tr>').addClass('fe-membership_type-row').append($('<td>').attr('colspan', 2).append(
$('.fe-membership_type')
))
);

$('input:radio[name=fe_member_type]').on('change', () => {
const isPaid = $('input:radio[name=fe_member_type][value=paid_member]').is(':checked');
$('input#record_contribution').prop("checked", !isPaid).trigger('click')
});

$('tr#contri').hide();
}

function placePaymentFieldsTogether() {
$('tr.crm-membership-form-block-receive_date').after(
$('<tr>').addClass('record_payment-block_row').append($('<td>').attr('colspan', 2).append(
$('.record_payment-block')
))
)

waitForElement($, 'input[name=contribution_type_toggle]',
() => {
$('tr.crm-membership-form-block-receive_date').before($('tr.crm-membership-form-block-financial_type_id'));
$('tr.crm-contribution-form-block-financeextras_record_payment_amount').after(
$('tr.crm-membership-form-block-payment_instrument_id')
);
$('tr.crm-membership-form-block-payment_instrument_id').after($('tr.crm-membership-form-block-trxn_id'))
$('tr.crm-membership-form-block-trxn_id').after($('tr.crm-membership-form-block-billing'))

$('tr.crm-membership-form-block-contribution_status_id').hide()
}
);

$('tr.record_payment-block_row').before(
$('<tr>').append($('<td>').attr('colspan', 2).append(
$('.record_payment-block_check')
))
)

const symbol = CRM.vars.financeextras.currencySymbol;
$('.crm-membership-form-block-total_amount label').text('Contribution Total Amount')
$('.crm-membership-form-block-financial_type_id label').text('Contribution Financial Type')
$('#total_amount').before($('<span>').text(`${symbol} `))
$('.record_payment-block #currency-symbol').text(symbol)
}

function hidePaymentFieldsOnPaymentToggler() {
$('li[data-selector="payment_plan"]').click( () => {
$('tr.record_payment-block_row').hide()
$('div.record_payment-block_check').hide();
});

$('li[data-selector="contribution"]').click( () => {
$('div.record_payment-block_check').show()
togglePaymentBlock();
});
}

/**
* Triggers callback when element attribute changes.
*
* @param {object} $
* @param {string} elementPath
* @param {object} callBack
*/
function waitForElement($, elementPath, callBack) {
(new MutationObserver(function() {
callBack($(elementPath));
})).observe(document.querySelector(elementPath), {
attributes: true,
});
}

/**
* Observes change in property value for an element.
*
* This method is used to listen for change in input fields
* that doesn't emit a change event when their value changes.
*
* @param {string} elementPath
* @param {string} property
* @param {function} callback
* @param {number} delay
*/
function observeElement(elementPath, property, callback, delay = 0) {
const element = document.querySelector(elementPath)
const elementPrototype = Object.getPrototypeOf(element);
if (Object.hasOwn(elementPrototype, property)) {
const descriptor = Object.getOwnPropertyDescriptor(elementPrototype, property);
Object.defineProperty(element, property, {
get: function() {
return descriptor.get.apply(this, arguments);
},
set: function () {
const oldValue = this[property];
descriptor.set.apply(this, arguments);
const newValue = this[property];
if (typeof callback == "function") {
setTimeout(callback.bind(this, oldValue, newValue), delay);
}
}
});
}
}

});
34 changes: 34 additions & 0 deletions templates/CRM/Financeextras/Form/Member/AddPayment.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<div class="fe-membership_type">
<table>
<tbody>
<tr>
<td class="label">{$form.fe_member_type.paid_member.label}</td>
<td>{$form.fe_member_type.paid_member.html}</td>
</tr>
<tr>
<td class="label">{$form.fe_member_type.free_member.label}</td>
<td>{$form.fe_member_type.free_member.html}</td>
</tr>
</tbody>
</table>
</div>
<div class="record_payment-block_check">
<table>
<tbody>
<tr class="crm-contribution-form-block-financeextras_record_payment_check">
<td class="label">{$form.fe_record_payment_check.html}</td>
<td>{$form.fe_record_payment_check.label}</td>
</tr>
</tbody>
</table>
</div>
<div class="record_payment-block">
<table>
<tbody>
<tr class="crm-contribution-form-block-financeextras_record_payment_amount">
<td class="label" id="amount-label">{$form.fe_record_payment_amount.label} <span class="crm-marker" title="This field is required."> *</span></td>
<td><span id="currency-symbol"></span> {$form.fe_record_payment_amount.html}</td>
</tr>
</tbody>
</table>
</div>

0 comments on commit 3a7ce22

Please sign in to comment.