VirtueMart Forum

VirtueMart 2 + 3 + 4 => Plugins: Payment, Shipment and others => Topic started by: vmfyelloq19 on August 13, 2019, 11:46:57 AM

Title: Payment method plugin with user input field
Post by: vmfyelloq19 on August 13, 2019, 11:46:57 AM
Hi folks,

I need to have a user input field for a payment method, baisally a text input filed that's presented when the payment method is selected.
( ) paypal
() other 1
(x) my payment method
     Please enter code [___________]

Any hints?

TIA!
Title: Re: Payment method plugin with user input field
Post by: GJC Web Design on August 13, 2019, 16:17:25 PM
you can over ride the protected function getPluginHtml () and provide a field but I assume there is a lot more to it than that.. what is the field supposed to do?
Title: Re: Payment method plugin with user input field
Post by: vmfyelloq19 on August 13, 2019, 18:53:51 PM
Hi GJC Web Design,

thank your for your reply.
This particular "payment method" just needs to pass a string with the customer's account number along with the order.
A shopper field would be fit the case better, as it would be stored into the customer's profile, but shopper fields are not be conditional for the specify payment method.

Cheers!
Title: Re: Payment method plugin with user input field
Post by: GJC Web Design on August 13, 2019, 23:18:17 PM
in that case u could use some JS to show/hide and enforce the shopper field if that radio is chosen
Title: Re: Payment method plugin with user input field
Post by: vmfyelloq19 on August 14, 2019, 08:49:59 AM
I never use Javascript for required functionality, as it is possibly disabled.

Cheers!
Title: Re: Payment method plugin with user input field
Post by: jenkinhill on August 14, 2019, 11:29:17 AM
Quote from: vmfyelloq19 on August 14, 2019, 08:49:59 AM
I never use Javascript for required functionality, as it is possibly disabled.

As VirtueMart makes heavy use of JavaScript then AFAIK someone with js "disabled" would be unable to use the store, anyway.
Title: Re: Payment method plugin with user input field
Post by: vmfyelloq19 on January 31, 2020, 08:08:21 AM
Hi jenkinhill,

You are right, and I adjusted my opinion on requiring JS with VM.

I've added some custom user text fields, added some JS to hide them via CSS if the payment mezhod does not match.
But that's causing several issues:
- I cannot set the fields to required
- there is no way to add a custom validation

Any hints anyone?

TIA!
Title: Re: Payment method plugin with user input field
Post by: GJC Web Design on January 31, 2020, 10:08:19 AM
just do it with JS

example

      if(jQuery('#companyreg_field').prop('checked')) {
         jQuery('#first_name_field').removeClass('required');
         jQuery('#first_name_field').removeAttr('required aria-required');
      }else{
         jQuery('#first_name_field').addClass('required')
         jQuery('#first_name_field').attr('required', 'required');
         jQuery('#first_name_field').attr('aria-required', 'true');
      }


validation can be handled the same way  with e.g.

jQuery('input#your_input ').bind("focusout keyup change", function() {
   // do stuff
});

or on submit etc
Title: Re: Payment method plugin with user input field
Post by: vmfyelloq19 on February 04, 2020, 23:18:48 PM
Thank you GJC Web Design,
that helped a lot!

After some consideration I'm giving the paymnet plugin another go.

I managed to display the required user input fields using plgVmDisplayListFEPayment().
I can check user input just fine in plgVmOnSelectCheckPayment() by checking the values I get from vRequest::getString([...]).

Still I'm stuck with re-displaying user input in the layout called from plgVmDisplayListFEPayment().
It seems user input is empty here.
I also tried to settting a class variable ($this->myformdata) in plgVmOnSelectCheckPayment() but it this var is empty in plgVmDisplayListFEPayment(). Might be a method-call ordering thing.

So what's the proper way to fill in the former user input in the form fields?
<input type... value="<insert former user input here>" ... />


What I have so far in plugins/vmpayment/mypayment/mypayment.php is:

class plgVmpaymentMypayment extends vmPSPlugin {
    [...]
    public function plgVmDisplayListFEPayment(VirtueMartCart $cart, $selected=0, &$htmlIn) {
        if ($this->getPluginMethods($cart->vendorId) === 0) {
            if (empty($this->_name)) {
                $app=JFactory::getApplication();
                $app->enqueueMessage(vmText::_('COM_VIRTUEMART_CART_NO_' . strtoupper($this->_psType)));
                return false;
            } else {
                return false;
            }
        }
        $htmlarray=[];
        foreach ($this->methods as $this->_currentMethod) {
            if($this->checkConditions($cart, $this->_currentMethod, $cart->cartPrices)) {
            $cartPrices=$cart->cartPrices;
                $methodSalesPrice=$this->calculateSalesPrice($cart, $this->_currentMethod, $cartPrices);
                $payment_cost='';
                if ($methodSalesPrice) {
                    $payment_cost=$currency->priceDisplay($methodSalesPrice);
                }
                if ($selected == $this->_currentMethod->virtuemart_paymentmethod_id) {
                    $checked='checked="checked"';
                } else {
                    $checked='';
                }
                /*
                 *
                 *  Need to pass former user input into layout here
                 *  If possible: also result of validation to highlight bad input fields
                 *
                 */

                $html=$this->renderByLayout('display_payment', [
                    'plugin' => $this->_currentMethod
                   ,'checked' => $checked
                   ,'payment_cost' => $payment_cost
                ]);
                $htmlarray[]=$html;
                if (!empty($htmlarray)) {
                    $htmlIn[]=$htmlarray;
                }
            }
        }
        return true;
    } // plgVmDisplayListFEPayment()

    [...]

    public function plgVmOnSelectCheckPayment(VirtueMartCart $cart,  &$msg) {
        if(! $this->selectedThisByMethodId($cart->virtuemart_paymentmethod_id)) {
            return NULL; // Another method was selected, do nothing
        }
        [...] // [my checks here]
        if($errors) {
            vmWarn($errormessage);
       return false;
        } else {
            // Just return true?
            return $this->OnSelectCheck($cart);
        }
    } // plgVmOnSelectCheckPayment()
Title: Re: Payment method plugin with user input field
Post by: vmfyelloq19 on February 24, 2020, 19:30:35 PM
I could not find a "proper" way to save/recall/pass user input to my form.
Please find what I managed so far after fidling around, trying to make sense of vm source etc.

I'm storing user input data in the user's session to be able to recall it later.
Here's the relevant plugin code, plus a stub to render the html form fields required.
User input get's saved to database automatically as announced in constructor.

Hope it helps someone looking for something similar.

[Update: fixed copy/paste error / missing methods]

* htdocs/plugins/vmpayment/mypayment.php:

<?php
defined ('_JEXEC') or die();
use Joomla\CMS\Factory;
if(! class_exists('vmPSPlugin')) require(VMPATH_PLUGINLIBS . DS . 'vmpsplugin.php');

class plgVmpaymentMypayment extends vmPSPlugin {

    private $_formdata=[];

    function __construct(& $subject, $config) {
        parent::__construct($subject, $config);
        $this->_loggable = true;
        $this->tableFields = array_keys($this->getTableSQLFields());
        $this->_tablepkey = 'id'; //virtuemart_directdebit_id';
        $this->_tableId = 'id'; //'virtuemart_directdebit_id';
        $varsToPush=[
             'myvar1' => array('', 'char')
            ,'myvar2' => array('', 'char')
            ,'myvar3' => array('', 'char')
        ];
        $this->addVarsToPushCore($varsToPush, 1);
        $this->setConfigParameterable($this->_configTableFieldName, $varsToPush);
        this->setConvertable(array('min_amount','max_amount','cost_per_transaction','cost_min_transaction'));
        $this->setConvertDecimal(array('min_amount','max_amount','cost_per_transaction','cost_min_transaction','cost_percent_total'));
    } // __construct();

    function getTableSQLFields() {
        return [
            'id'                                 => 'int(11) UNSIGNED NOT null AUTO_INCREMENT'
            ,'virtuemart_order_id'               => 'int(1) UNSIGNED'
            ,'order_number'                      => 'char(64)'
            ,'virtuemart_paymentmethod_id'       => 'mediumint(1) UNSIGNED'
            ,'payment_name'                      => 'varchar(5000)' // might include html
            ,'payment_order_total'               => 'decimal(15,5) NOT null DEFAULT \'0.00000\''
            ,'payment_currency'                  => 'char(3) '
            ,'email_currency'                    => 'smallint(1)'
            ,'cost_per_transaction'              => 'decimal(10,2)'
            ,'cost_percent_total'                => 'decimal(10,2)'
            ,'tax_id'                            => 'smallint(1)'
            ,'myvar1'                            => 'varchar(255)'
            ,'myvar3'                           => 'varchar(255)'
            ,'myvar3'                           => 'varchar(255)'
        ];
    } // getTableSQLFields()

    public function plgVmDisplayListFEPayment(VirtueMartCart $cart, $selected = 0, &$htmlIn) {
            if ($this->getPluginMethods($cart->vendorId) === 0) {
                if (empty($this->_name)) {
                    $app = JFactory::getApplication();
                    $app->enqueueMessage(vmText::_('COM_VIRTUEMART_CART_NO_' . strtoupper($this->_psType)));
                    return false;
                } else {
                    return false;
                }
            }
            $method_name = $this->_psType . '_name';
            $currentMethod_id_key = 'virtuemart_' . $this->_psType . 'method_id';
            $session = JFactory::getSession();
            // get/store user input in session as there seems to be "official" way to keep it
            $session_node=$this->_type . '__' . $this->_name;
            $session_data = $session->get($session_node);
            foreach($this->methods as $this->_currentMethod) {
                if($this->checkConditions($cart, $this->_currentMethod, $cart->cartPrices)) {
                    $html = '';
                    $cartPrices=$cart->cartPrices;
                    if (isset($this->_currentMethod->cost_method)) {
                        $cost_method=$this->_currentMethod->cost_method;
                    } else {
                        $cost_method=true;
                    }
                    $methodSalesPrice = $this->setCartPrices($cart, $cartPrices, $this->_currentMethod, $cost_method);
                    $payment_name_html = $this->renderPluginName($this->_currentMethod);
                    $key=$method_name . '_html';
                    $this->_currentMethod->$key=$payment_name_html;
                    $this->_currentMethod->payment_currency = $this->getPaymentCurrency($this->_currentMethod);

                    $pluginmethod_id = $this->_idName;
                    $checked=($selected == $this->_currentMethod->$pluginmethod_id);
                    $html=$this->renderByLayout('display_payment', [
                         // passed to template as $viewData[]:
                         'plugin' => $this->_currentMethod
                        ,'checked' => $checked
                        ,'payment_cost' => $methodSalesPrice
                        ,'formdata' => $session_data
                    ]);
                    $htmlIn[$this->_psType][$this->_currentMethod->$currentMethod_id_key] =$html;
                } // if this->checkConditions
            } // foreach($this->methods)
            return true;
    } // plgVmDisplayListFEPayment()

    public function plgVmOnSelectCheckPayment(VirtueMartCart $cart, &$msg) {
       if(! $this->selectedThisByMethodId($cart->virtuemart_paymentmethod_id)) {
          return null; // Another method was selected, do nothing
       }

       if(! ($this->_currentMethod = $this->getVmPluginMethod($cart->virtuemart_paymentmethod_id))) {
          return false;
       }

      if(! $this->selectedThisByMethodId($cart->virtuemart_paymentmethod_id)) {
          return null; // Another method was selected, do nothing
       }
       $userinput_result=$this->_userinput_validate($cart); // result: true|array of string with errormessage. More data in $this->_formdata

       // Fire a user warning:
       if(is_array($userinput_result)) {
          array_map('vmWarn', $userinput_result); // see helpers/vmecho.php for vmWarn(), vmInfo(), ...
          return false;
       }
       return true;
    } // plgVmOnSelectCheckPayment()

    function plgVmConfirmedOrder($cart, $order) {
        if(! ($this->_currentMethod = $this->getVmPluginMethod($order['details']['BT']->virtuemart_paymentmethod_id))) {
            return null; // Another method was selected, do nothing
        }
        if(! $this->selectedThisElement($this->_currentMethod->payment_element)) {
            return false;
        }
        $session = JFactory::getSession();
        $session_node=$this->_type . '__' . $this->_name;
        $session_data = $session->get($session_node);
        $this->_currentMethod->payment_currency = $this->getPaymentCurrency($this->_currentMethod,$order['details']['BT']->payment_currency_id);

        $payment_name = $this->renderPluginName($this->_currentMethod, $order);
        $currency_code_3 = shopFunctions::getCurrencyByID($this->_currentMethod->payment_currency, 'currency_code_3');
        $email_currency = $this->getEmailCurrency($this->_currentMethod);
        $totalInPaymentCurrency = vmPSPlugin::getAmountInCurrency($order['details']['BT']->order_total,$this->_currentMethod->payment_currency);

        // Prepare data that should be stored in the database
        $dbValues['order_number'] = $order['details']['BT']->order_number;
        $dbValues['virtuemart_paymentmethod_id'] = $cart->virtuemart_paymentmethod_id;
        $dbValues['payment_name'] = $payment_name;
        $dbValues['payment_order_total'] = $totalInPaymentCurrency['value']; // from standard plugin
        $dbValues['payment_currency'] = $currency_code_3; // from standard plugin
        $dbValues['email_currency'] = $email_currency;
        $dbValues['cost_per_transaction'] = (float) @$this->_currentMethod->cost_per_transaction;
        $dbValues['cost_min_transaction'] = (float) @$this->_currentMethod->cost_min_transaction; // from standard plugin
        $dbValues['cost_percent_total'] = (float) @$this->_currentMethod->cost_percent_total; // from standard plugin
        $dbValues['tax_id'] = $this->_currentMethod->tax_id;
        $dbValues['myvar1'] = $session_data['myvar1']['value'];
        $dbValues['myvar2'] = $session_data['myvar2']['value'];
        $dbValues['myvar3'] = $session_data['myvar3']['value'];
        $this->storePSPluginInternalData($dbValues); // returns  full dataset array incl. new dbid
    } // plgVmConfirmedOrder()

    /**
    * _userinput_validate()
    * param $cart
    * return true|array of strings with error messages.
    *        Additonal data in $this->_formdata[
    *            (string) value
    *            (string) inputkey
    *            (bool) valid
    *            (string) errormessage, if any
    *        ]
    */
    private function _userinput_validate($cart) {
        $session = JFactory::getSession();
        $session_node=$this->_type . '__' . $this->_name;
        $session_data = $session->get($session_node);
        if(is_null($session_data)) $session_data = [];

        $input = Factory::getApplication()->input;
        $errors=[];

        $inputkey='virtuemart_paymentmethod_' . $cart->virtuemart_paymentmethod_id . '_' . 'myvar1';
        $value=trim($input->get($inputkey, null, 'string'));
        if(null===$value) {
            if(! empty($session_data[$key]['value'])) $value=$session_data[$key]['value'];
        }
        $formdata_dataset=[
             'value'=>$value
            ,'inputkey'=>$inputkey
            ,'valid'=>true
            ,'errormessage'=>''
        ];
        $langkey='MYPAYMENTPLUGIN_' . strtoupper($key);
        // Just check if not empty

        // Sample validation for empty value:
        if(empty($value)) {
            $formdata_dataset['valid']=false;
            $formdata_dataset['errormessage']=vmText::sprintf('COM_VIRTUEMART_MISSING_VALUE_FOR_FIELD', JText::_($langkey));
        }
        if(! empty($formdata_dataset['errormessage'])) $errors[]=$formdata_dataset['errormessage'];
        $this->_formdata[$key]=$formdata_dataset;
        $session_data[$key]=$formdata_dataset; // store in session - have no other way to push data to layout?

        // DO MORE VALIDATIONS HERE

        $session->set($session_node, $session_data);
        if(empty($errors)) {
            return true;
        } else {
            return $errors;
        }
    } // _userinput_validate()

    // [...]

} / class



* htdocs/plugins/vmpayment/myfancypayment/tmpl/display_payment.php:

<?php   
defined ('_JEXEC') or die();
// Render our fancy pamyner input fields here:
// Previous entered form data in $viewData['formdata'] etc.
// if($viewData ['checked'] ) ...
// echo '<label for=...
// echo '<input type=....
Title: Re: show data in my account / order detail and order email?
Post by: vmfyelloq19 on February 24, 2020, 20:06:43 PM
Hi folks,

anyone has a hint on how to get and show the plugin's data as stored during checkout?

For the order detail page I see my plugin's plgVmOnShowOrderFEPayment() get's fired, and I see I can set $payment_name.

a) What method does get called for the order email?

b) How would I retrieve the data? Do I need to do the DB selects for #__virtuemart_payment_plg_mypayment myself or is there a method for this?

TIA!