Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CIWEMB-320: User can optionally record payment for new membership #58

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
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>
Loading