Jump to content

IPCommerceFan

Clients
  • Posts

    493
  • Joined

  • Last visited

 Content Type 

Downloads

Release Notes

IPS4 Guides

IPS4 Developer Documentation

Invision Community Blog

Development Blog

Deprecation Tracker

Providers Directory

Forums

Events

Store

Gallery

Posts posted by IPCommerceFan

  1. 4.6.10
    all customizations disabled
    default theme

    I'm not sure how long this has existed, however I've encountered it while working on an application that adds custom validation to the package form.

    Steps to reproduce:

    1. Create a custom text field, set it to Required, and assign it to a package
      image.thumb.png.65261ba575fc74c79d7bb0bdc07239b0.png=
       
    2. On the front end, view packages in List View.   This adds a "Choose Options" button
      image.png.d495e552a5e23ace307c84a8bd826b54.png
    3. Click Choose Options, do not fill out any fields, and try to add to cart
      image.png.fbd69d321003c7f7682c586e2c9c278a.png
       
    4. A new element will be added below the form each time "Add to cart" is clicked, instead of showing that the required field was not filled out
      image.png.a01dffa7da229c37d8eb0d540b241791.png
    5. If the field is filled out and add to cart is clicked, all the extra elements disappear and you can proceed to checkout:image.png.0004ec2b769117bb3ef137e9b37fee83.png
    6. Without reloading the page - If you click Continue Shopping, and try to Add to Cart without entering anything in the field, you get this:
      image.png.45bfc5828cd66f5802324fbb76e90faa.png

     

    If we add some instrumentation to listen in on \IPS\Helpers\Form::values() , we see that the "form_required" error is being added to the element, but is just not displaying correctly via the template:

    image.png.bba41ec57b8c412cc428cc12fe32de09.png

    Validation works great when accessing the package directly (via Grid view), or when updating the purchase form in the client area.  It is only when the form is generated by the 'Choose Options' button that it behaves this way.

    Frankly I don't use List view - I've considered just disabling it as an option - but I imagine some customers might prefer it, so please let me know if I can do or add anything to help get this resolved.

  2. Yeah we currently use a template hack where we duplicate a bunch of elements (category sidebar, product groups on the index, possibly others) and specify which member groups can see what.  Its messy, and would be amazing if product groups could have permissions of their own.

    For our use case, we would use the group permissions to show resellers/vendors special packages only they can access, while simultaneously hiding the public-facing packages from them.   The opposite would be true for guests and regular customers.

  3. I wrote this to sort downloads by filename.  You should be able to use it to accomplish the reverse sort by changing the database query accordingly:

    <div class='ipsPad'>
    	<h1 class='ipsType_pageTitle'>{lang="download_your_files"}</h1>
    	<p class='ipsType_reset ipsType_normal ipsType_light'>{lang="download_file_count" pluralize="\count( $files )"}</p>
    	<hr class='ipsHr'>
    	<ul class='ipsDataList ipsDataList_reducedSpacing'>
          
          {{$fileRecords = \IPS\Db::i()->select('*', 'downloads_files_records', array(array('record_file_id=?', $fileObject->id),'record_backup=0'),'record_realname ASC');}}
    		{{foreach $fileRecords as $file}}
    		{{$data = $file;}}
    		{{$k = $data['record_id'];}}
    			
    			<li class='ipsDataItem'>
    				<div class='ipsDataItem_main'>
    					<h4 class='ipsDataItem_title ipsContained_container'><span class='ipsType_break ipsContained'>{{if $data['record_realname']}}{$data['record_realname']}{{else}}{{$pathBits = explode( '/', \IPS\Http\Url::external( $data['record_location'] )->data[ \IPS\Http\Url::COMPONENT_PATH ] );}}{expression="\count( $pathBits ) ? array_pop( $pathBits ) : $data['record_location']"}{{endif}}</span></h4>
    					{{if $data['record_size']}}<p class='ipsType_reset ipsDataItem_meta'>{filesize="$data['record_size']"}</p>{{endif}}
    				</div>
    				{{if $waitingOn == $k}}
    					<div class='ipsDataItem_generic ipsDataItem_size6 ipsType_warning'>
    						<noscript>{lang="wait_x_seconds_noscript" pluralize="$waitingFor"}</noscript>
    					</div>
    				{{endif}}
    				<div class='ipsDataItem_generic ipsDataItem_size4 ipsType_right'>
    					<span class="ipsHide" data-role="downloadCounterContainer">{lang="download_begins_in"} <span data-role="downloadCounter"></span> {lang="seconds"}</span>
    					<a href='{$fileObject->url()->setQueryString( array( 'do' => 'download', 'r' => $k, 'confirm' => 1, 't' => 1, 'version' => isset( \IPS\Request::i()->version ) ? \IPS\Request::i()->version : NULL ) )->csrf()}' class='ipsButton ipsButton_primary ipsButton_small' data-action="download" {{if member.group['idm_wait_period']}}data-wait='true'{{endif}}>{lang="download"}</a>
    				</div>
    			</li>
    		{{endforeach}}
    	</ul>
    </div>

     

  4. It would be interesting if the suite could count daily outgoing emails, and queue into batches anything in excess of a user-defined number.  200,300,5000, etc.  An interface for managing/prioritizing emails would eventually be needed for cases where certain emails need to go out ahead of others in the queue.

  5. Hi,

    It looks like the Braintree -> PayPal integration may need some attention.

    We recently changed our site from the "old" PayPal integration, to Braintree -> PayPal, and have had numerous customers report they can't purchase due to an error.

    Looking at the invoice in the AdminCP, we see that the error states:
     

    Quote

    Refused
    US state codes must be two characters to meet PayPal Seller Protection requirements.

     

    Since IPS stores customer states as full text - e.g Texas vs TX - I assume this must be the reason for this error message.

    The funny thing is I tried purchasing something myself, and could not reproduce the error.

    We've reverted back to the "old" PayPal method for now, but I think this is worth looking into.

    Our site is running 4.6.10, FYI.

  6. It looks like Samsung Pay is now (or perhaps was already) an option via Braintree.  Since it seems great effort was put forth to flesh out the Braintree integration, I figure I should point out this new method is available, as I'm sure the intent was probably to support all available methods - also we'd like to use it. 🙂

    Thanks!

  7. Assuming you are accessing the database directly via MySQL, you could use the JSON_EXTRACT function (MySQL 5.7 and above).

    SELECT 
    address->>'$."addressLines"[0]' AS 'addressLine1',
    address->>'$."addressLines"[1]' AS 'addressLine2',
    address->>'$."city"' AS 'City', 
    address->>'$."region"' AS 'Region', 
    address->>'$."country"' AS 'Country', 
    address->>'$."postalCode"' AS 'postalCode' 
    
    FROM nexus_customer_addresses

    The above example utilizes the shorthand for JSON_UNQUOTE(JSON_EXTRACT()):

    https://dev.mysql.com/doc/refman/8.0/en/json-search-functions.html#operator_json-inline-path

  8. 4.6.7
    All 3rd party apps/plugins disabled

    Issue:

    "Too many redirects" blank page when attempting to manage a purchase with a 'YesNo' field type that has the 'Used to identify purchases?' option set. (you're not supposed to be able to set this option for a 'YesNo' type)

    Repro:

    1. Create a nexus custom field with Field Type of Text, and specify 'Used to identify purchases?'
    2. Assign the field to a package, and generate a purchase with said package in it.
    3. Go to Manage Purchases, confirm the purchase can be managed.
    4. Change the field type to 'YesNo' (or some other type that doesn't use the 'Used to identify Purchases' toggle)
    5. Attempt to manage the purchase.  This should result in a URL that apparently contains the csrf key appended to it, and a "ERR_TOO_MANY_REDIRECTS" browser error.

    "No code" fix:

    1. Change the custom field back to 'Text'
    2. De-select 'Used to identify Purchases'
    3. Save
    4. Change the type back to 'YesNo'
    5. Save

    I confirmed changing the setting for 'Used to identify Purchases' fixes this, but I figure if I ran into this organically, someone else might as well too and would come to support for help with it.  Frankly I had no idea why I was suddenly unable to manage some purchases for a good while.  lol

    Here's what some poking around the $values revealed when saving the form:

    Text -> Utip enabled:

    • "cf_sticky":true

    Text -> Utip disabled:

    • "cf_sticky":false

    YesNo -> Utip disabled:

    • "cf_sticky":false

    YesNo -> Utip enabled:

    • "cf_sticky":1

    It would seem the cf_sticky state is still being saved, but it is formatted incorrectly because the toggle isn't actually displayed on the form.

    We don't actually want any YesNo field to be sticky though, since its not really useful info, so I'd suggest doing something like this in \IPS\nexus\Package\CustomField::formatFormValues()

            if ($values['cf_sticky'] === 1){
                $values['cf_sticky'] = FALSE;
            }

    Tested via plugin, it fixes the problem.

    GIF by Giphy QA

    [/Fringe case] lol

  9. Just browsing through nexus\interface\gateways\paypal, it looks like you need to set the status when you save the transaction.

    if ($status == 'paid') {
        // payment is complete
        if ( $transaction->status !== \IPS\nexus\Transaction::STATUS_PAID ) {
            $transaction->gw_id = $transactionId;
            $transaction->auth = NULL;
            $transaction->approve();
            $transaction->status = \IPS\nexus\Transaction::STATUS_PAID;
            $transaction->save();
            $transaction->sendNotification();
        }
    }

    It seems $transaction->approve() SHOULD do this for you, but if its not, I'd try doing it explicitly like its done in the paypal gateway.  Maybe approve isn't doing it due to declaring "->approve(NULL)" as opposed to "->approve()"?

    Incidentally (and feel free to ignore this section), I'm curious what some kind of debugging would capture at each step.  One method I use is adding a function that looks something like this to the class I want to debug:

        /**
         * Debugging Tool
         *
         * @param	__LINE__		$magicLine		Line in code
         * @param	__FUNCTION__	$magicFunction	Function in code
         * @param   string          $desc           Unique descriptive text
         * @param   mixed          $markerData     Data we want to capture
         * @return  void
         */
    
        static public function codeMarker( $magicLine, $magicFunction, $desc=NULL, $markerData=NULL )
        {
            $hookID = '';
            $filename = 'phpFilename';
            \IPS\Db::i()->insert('dbg_table', array(
                'debug_hook_id' => $hookID,
                'debug_hook_filename' => $filename,
                //'debug_hook_plugin' => 	\IPS\Application::load(\IPS\Plugin\Hook::load( $hookID )->app)->directory,
                'debug_hook_plugin' => 'pluginName',
                'debug_line_number' => $magicLine,
                'debug_function_name' => $magicFunction,
                'debug_description' => $desc,
                'debug_markerdata' => \is_array($markerData) ? substr(json_encode($markerData),0,2048) : $markerData,
                'debug_timestamp' => str_pad(microtime(time()),15,"0",STR_PAD_RIGHT)
            ));
        }

    Then add "code markers" to the code, run it, and see what turns up.

    In this case I would hook into \IPS\nexus\Transaction::approve() and add:

    static::codeMarker(__LINE__,__FUNCTION__,'_check_status_before_', $this->status);
    
    parent::approve();
    
    static::codeMarker(__LINE__,__FUNCTION__,'_check_status_after_', $this->status);

    Or even redeclare the function verbatim and insert markers as desired.

    Then query the debug table to see what we caught.  I'm not sure that this is an accepted or even a remotely efficient way of debugging, but its what works for me (better than error_log, debug_backtrace, etc since we are inserting markers directly into the code at the spot we want to inspect)

  10. It appears STATUS_PAID is the closest thing to STATUS_APPROVED short of adding a new status yourself.

    e.g.

    	const STATUS_PAID			= 'okay'; // Transaction has been paid successfully
    	const STATUS_PENDING		= 'pend'; // Payment not yet submitted (for example, has been redirected to external site)
    	const STATUS_WAITING		= 'wait'; // Waiting for user (for example, a check is in the mail). Manual approval will be required
    	const STATUS_HELD			= 'hold'; // Transaction is being held for approval
    	const STATUS_REVIEW			= 'revw'; // Transaction, after being held for approval, has been flagged for review by staff
    	const STATUS_REFUSED		= 'fail'; // Transaction was refused
    	const STATUS_REFUNDED		= 'rfnd'; // Transaction has been refunded in full
    	const STATUS_PART_REFUNDED	= 'prfd'; // Transaction has been partially refunded
    	const STATUS_GATEWAY_PENDING= 'gwpd'; // The gateway is processing the transaction
    	const STATUS_DISPUTED		= 'dspd'; // The customer disputed the transaction with their bank (filed a chargeback)
    
    	const STATUS_APPROVED		= 'appr'; // Added for additional status functionality

    and then of course you'd have to decide how/when the status would be set.

    As it stands though, the suite seems to treat PAID as being synonymous with APPROVED.

    For instance, the sendNotification() funciton in \IPS\nexus\Transaction lists the notification key for STATUS_PAID as 'transactionApproved'.

  11. Try using a unix timestamp, like so (this one is for Oct 1st 2021):

    /api/core/search?sortby=newest&key=################################&type=forums_topic&start_after=1633064400

    The results that are returned are given in DateTime:

    {
        "page": 1,
        "perPage": 25,
        "totalResults": 1,
        "totalPages": 1,
        "results": [
            {
                "title": "Widget",
                "content": "",
                "class": "IPS\\nexus\\Package\\Item",
                "objectId": 9999,
                "itemClass": "IPS\\nexus\\Package\\Item",
                "itemId": 9999,
                "started": "2021-11-02T08:35:52Z",
                "updated": "2021-11-02T08:38:33Z",
                "itemUrl": "https:\/\/www.yourdomain.com\/store\/product\/9999-widget\/",
                "objectUrl": "https:\/\/www.yourdomain.com\/store\/product\/9999-widget\/?do=findComment&comment=1358",
                "reputation": 0,
                "comments": null,
                "reviews": 0,
                "container": "Widgets",
                "containerUrl": "https:\/\/www.yourdomain.com\/store\/category\/1-widgets\/",
                "author": "Guest",
                "authorUrl": null,
                "authorPhoto": "https:\/\/www.yourdomain.com\/applications\/core\/dev\/resources\/global\/default_photo.png",
                "authorPhotoThumbnail": "https:\/\/www.yourdomain.com\/applications\/core\/dev\/resources\/global\/default_photo.png",
                "tags": []
            }
        ]
    }

    But as far as the GET request goes, its in unixtime.


  12. When viewing a nexus invoice on the front end for a physical product, Pending shipping items show the nexus pfield ID instead of the name:

    image.png.c9bf042cc39f95b156be0766cf8cf112.png

    If we change this in nexus -> front -> clients -> invoice (line 220):

    										<span class="ipsType_light">
    											{{foreach $item['details'] as $k => $v}}
    												{lang="$k"}: {$v}<br>
    											{{endforeach}}
    										</span>

    To this:

    										<span class="ipsType_light">
    											{{foreach $item['details'] as $k => $v}}
    												{lang="nexus_pfield_$k"}: {$v}<br><br>
    											{{endforeach}}
    										</span>

    We get this:

    image.png.33f3a0515162dacf78e5bbef127ffe05.png
    Thanks!

     

  13. Unfortunately I don't know when this originated, as I hadn't tried to use the 2FA feature until today.  I'm only theorizing it had to do with my install having been upgraded with long version gaps in-between and wanted to share my solution in case its of any benefit to others.

    It is indeed resolved. The plugin I wrote was disabled and discarded, and I'm successfully using Google Authenticator.   👍

     

  14. IC 4.6.7 / PHP 8 / MySQL 8

    All 3rd party apps/plugins disabled

    Not a fresh install - upgraded from 4.4.10 recently, 3.4.9 before that.

    It seems, perhaps due to something missed in an upgrade routine, the Enabled/Disabled status for Two Factor Authentication handlers is missing. (it had never been used)

    In IPS\core\modules\admin\settings\mfa.php, line 108

    isEnabled() is run on the handler, which checks for the settings:

    \IPS\Settings::i()->authy_enabled
    \IPS\Settings::i()->questions_enabled
    \IPS\Settings::i()->google_enabled

    The settings have never been populated with a 0 or 1, and are instead null.

    image.png.fda6712b957bcfa5b56583322d786057.png

    Since they are null, the "Enable/Disable" button doesn't know to appear here:

    image.png.5067f09f30d1f7dd0b25660f66c1a09c.png

    My solution to this was to write a plugin which modifies this, at line 108:
     

    $handler->isEnabled()

    To:

    $handler->isEnabled() ?? 0

    and the buttons appeared, with "Disabled" selected.  

    I proceeded to enable each item, then disable, and disabled the plugin.   This wrote 0's to the setting for each handler:

    image.png.0d805e6609c73e96181170caf263b101.png

    Now I can freely enable/disable the MFA handlers without the plugin:

    image.png.5f03bc482be02901206a2e122c700fc4.png

    I'm not sure how this would be worked into a future update, whether by the method I used, or doing something in the upgrader to populate the null settings with 0's, but the issue existed for me so I figured I'd report it!  👍

  15. Something like a "Service Fee"?

    The only additional fees which work that way would be Tax rates, and those are always a percentage of each line item (product price and shipping rate have tax applied individually).

    In order to add a static amount to each order, you'd need a developer to write custom application or plugin for it which would add a new item to the invoice.

  16. Hey @Maxxius, yeah, using a Pages block is just my way of quickly testing something out.

    If we're talkin' page templates, then one thing to remember is anything encapsulated by double curly braces is parsed as PHP, and anything in single curly braces echoes the variable.

    So, in a template, this would look like:

    {{$apiOutput = \IPS\Member::loggedIn()->apiOutput();}}
    {$apiOutput['joined']}
    {$apiOutput['rank']['name']}

    You could not, however, do:

    {\IPS\Member::loggedIn()->apiOutput()['joined']}

    since there is not a variable in the single curly braces.

    Where in globalTemplate do you want this to appear, exactly?

  17. This works in a custom Pages PHP block:

            $output = '';
            $apiOutput = \IPS\Member::loggedIn()->apiOutput();
            $output .= json_encode($apiOutput);
    		echo $output;

    ^ this will return all of the possible values.

    If you wanted just rank title and registration date, you'd do:

    		$output .= $apiOutput['joined'];
    		$output .= $apiOutput['rank']['name'];

    To discover what your options are for working with the member object directly, you'd do:

    $output .= json_encode(\IPS\Member::loggedIn()->_data);

    At which point you'll discover the apiOutput method is a lot more straightforward, since you'd then need to learn all the functions necessary to call up the data that isn't in the member object itself.

    e.g.:

    $output .= \IPS\Member::loggedIn()->joined;
    $output .= \IPS\Member::loggedIn()->rank()->title;

    Hope that helps!

×
×
  • Create New...