0 comments on “Locations Table : Convert Filter Interface To React Component”

Locations Table : Convert Filter Interface To React Component

Goal: Conversion the filter interface on the Locations | List page to a React component similar to the Bulk Actions and Search Box updates.


Research

Existing \SLP_Admin_Locations::createstring_FiltersBlock Method

\SLP_Admin_Locations::createstring_FiltersBlock creates the HTML string to render on the locationForm.
This fires the custom WordPress filter slp_locations_manage_filters to build an object array of properties for the drop down menu.
It builds up the $baExtras string if any object in the array has an ‘extras’ as a property.
It uses the $baExtras to build a dialog box modal attached to the #locationForm HTML Form on the page to add extra properties to form submissions.
This dialog is shown when specific drop down elements are selected.

\SLP_Admin_Locations::createstring_FiltersBlock calls \SLP_Admin_Helper::createstring_DropDownMenuWithButton to help build the dropdown HTML.
Creates the dropdown HTML via \SLP_Admin_Helper::createstring_DropDownMenu and adds a div wrapper and an <input…> HTML element masquerading as an icon button to trigger the drop down processing.


The bulk of the dropdown HTML is created with \SLP_Admin_Helper::createstring_DropDownMenu.
This builds the basic <select> and inner <option…> HTML elements into a string.

The slp_locations_manage_filters Filter

This filter is only used by the Power add on to extend the filter array of objects noted above.
The method that extends the array is \SLP_Power_Admin::filter_LocationsFilters.

Two filter drop down entries create extended modal dialog box interfaces:

\SLP_Power_Admin::createstring_FilterByPropertiesDiv

Add an iframe with a misnomer id ‘power_csv_download’ (this has nothing to do with csv downloads here).
Inside the HTML for the various location properties is created inside of \SLP_Power_Admin_Location_Filters::createstring_LocationFilterForm

\SLP_Power_Admin_Location_Filters::createstring_LocationFilterForm uses various methods to build up HTML input selectors including:

The modal dialog content when “With These Properties” (filter_by_property) is picked on the filter drop down.

Development

Follow the same design principles behind the Bulk Actions rewrite to make a React-based component for the location filters interface.

Create a new LocationsFilter component that is a sibling to the LocationsSearch and LocationsBulkActions (wp-content/plugins/store-locator-plus/src/components/locations/LocationsBulkActions.tsx) components.
Use a style similar to that for bulk actions.
Do not add an apply and apply to all button, instead use a filter icon button to submit the drop down selection.

Instead of a dialog box, use the same style slide out drawer used for Bulk Actions “categorize”.
Slide out from the right side, attached to the same parent div as the Drawer for the categoryDrawer.
Create the input elements in the same slide drawer as the categories filter following the order:

  • Name : input box
  • Zip: input box
  • State : make this an accordion that is collapsed by default
    • Inside the accordion use a checkbox list for all the states, built form the database of locations
  • Country : make this an accordion that is collapsed by default
    • Inside the accordion use a checkbox list for all the countries, built form the database of locations
  • Category : make this an accordion that is collapsed by default
    • Use a checklist of categories similar to that created for the BulkActions categorize interface

Retain the legacy jQuery driven form submission process to submit and process these filters.
Use styling similar to the Bulk Actions categorize drawer.
Create new REST endpoints only if necessary to fetch a list of states or countries from the list of locations.

Remove any legacy code that has been replaced by the new React interface after validating functionality.

0 comments on “Locations Table : Convert Search Box To React Component”

Locations Table : Convert Search Box To React Component

Goal: At the top of the Locations | List interface there is a search box for locations. This is currently rendered and managed with PHP+HTML+jQuery. Add this to the new React LocationsTableHeader component and deprecate the legacy code.


Research

Current UI/UX

This image shows the new Bulk Actions React component with the legacy filter and search PHP+HTML+jQuery UI below.

Existing LocationsTableHeader React Component

Typescript File: wp-content/plugins/store-locator-plus/src/components/locations/LocationsTableHeader.tsx

Legacy PHP+HTML Search Interface

HTML output string is generated in \SLP_Admin_Locations::createstring_SearchBlock.
This uses HTML based onkeypress and onClick attributes to trigger JavaScript actions.
Pressing enter in the search box or clicking on the search icon runs the jQuery-driven AdminUI.doAction(‘search’)


Development

Create A New LocationsSearch React Component

Create a new LocationsSearch React component that is a sibling of the LocationsBulkActions React component.
Create a search input box with a search icon button after that triggers the search.

UI/UX Updates

  • In LocationsTableHeader use MUI components to wrap all children that will allow for horizontal stacking of children.
    • LocationsTableHeader should take 100% of the width of parent .react-wrapper div.
    • If the children don’t fit
      • wrap the entire child component to the next line
      • do not use a horizontal scroll bar
  • Place the new LocationsSearch React component to the right of the LocationsBulkActions component.
  • All of for a future LocationsFilter component to be placed between the LocationsBulkActions and LocationsSearch components.
0 comments on “Locations Table (List Locations) : Bulk Actions Rewrite In React”

Locations Table (List Locations) : Bulk Actions Rewrite In React

Goal: Convert the Bulk Actions drop down menu and the associated apply and apply to all buttons into a React component.


Research

SLP_Admin_Locations PHP Class

This class manages the locations table interface.

\SLP_Admin_Locations::createstring_BulkActionsBlock

This method does most of the heavy lifting for the bulk actions drop down.

The bulk actions menu items are built from an array stored in $dropdownItems.

Extending The List Of Dropdown Options

The WordPress filter slp_locations_manage_bulkactions allows external modules to extend the $dropdownItems array.

The slp_locations_manage_bulkactions filter is called by these external modules:

Generating The Drop Down List HTML String

This is handled PHP generating an HTML string via \SLP_Admin_Helper::createstring_DropDownMenu.

Invoking The Bulk Actions

The selection on the bulk actions menu is executed via a jQuery on ‘click’ action that is attached to two divs posing as buttons.

The button interactions are driven via jQuery hooks that live in wp-content/plugins/store-locator-plus/js/admin-locations-tab.js
The invocation hooks via jQuery on ‘click’ and the methods that are invoked are in the SLP_Locations_table_header “class” in admin-locations-tab.js.

Apply
  • Intended to run the action against the locations that have been checked off using the locations table checkboxes which is rendered with PHP and HTML.
  • DIV ID #do_action_apply
Apply To All
  • Intended to run against ALL locations in the database.
  • DIV ID #do_action_apply_to_all
Render Additional Metadata User Interfaces

Some of the items on the drop down menu allow for extra meta data to be set by the user.
This meta data is sent along with the other form data for the locations table to the backend when the Apply or Apply To All buttons are processed via jQuery.

The only two use cases are for:

  • User category selection
    • attached to the ‘categorize’ dropdown item
    • shows the div with a checkbox list of categories available to locations when the users selects this dropdown item
    • the categories come from the WordPress taxonomy system using the SLPlus::locationTaxonomy (set to ‘stores’) property to determine the taxonomy label
  • User tag input
    • attached to the ‘add_tag’ dropdown item
    • shows a div with an input text box where the user can enter a string of comma separated values

\SLP_Admin_Locations::$settings

The SLP_Admin_Locations class leverages multiple methods from the SLP_Settings class via \SLP_Admin_Locations::$settings to manage the current PHP/HTML/JavaScript heavy implementation. The $settings property and thus SLP_Settings class manages much of the PHP-to-React interfaces.

A primary method of “feeding” variables from WordPress, PHP, and the underlying SQL data is managed via the \SLP_Settings::get_vars_for_react method


Development

Pre-Existing Issue

  • With the Export, Hosted CSV bulk action and checking the first 5 items, the export worked but the “Location Processing Info” box with the download link cannot be closed from the UI.
    • This should close when clicking outside the box.
    • Consider changing the header in the confirmation modal to the action name, in this case “Export, Hosted CSV”.

Second Turn Review

UI/UX Issues

  • LocationsTableHeader component needs some left margin/padding to align with the legacy PHP-derived table output below.
    It should not be flush against the left sidebar menu interface.
  • The Apply / Apply To All / Close buttons on the revised Category slide out drawer look awful and needs to follow modern design best practices.
  • Redesign the header of the slide out to follow a design like this:
  • A clear header box (white on white) with the text “Categorize Locations” instead of “Categories” in place of Settings in this example.
  • Use simple icons from MUI Icons with highlighted tool tips on hover (immediate, no wait)
    • CloseOutlinedIcon for close
    • ChecklistOutlinedIcon for Apply
    • FactCheckOutlinedIcon for Apply To All
  • In addition, I see the LocationsBulkActions component is using a deprecated property in the Drawer component.
    • PaperProps is deprecated for MUI <Drawer…>
  • On the Tag, Add modal add the Apply and Apply To All buttons
    • Change “done” to cancel.
    • Follow the same implementation as the category slide out, fire the underlying “apply” and “apply to all” functions from the main bulk actions form.
    • Apply , Apply To All, and Cancel should all be action buttons on the bottom of the modal.
    • When this modal exits, reset the Bulk Actions drop down back to the default no action “Bulk Actions” selection (first selection) same as when the categorize slide out closes.


Initial Turn Review

UI/UX Issues

  • Do not need “Bulk Actions” label around the drop down selector AND the word “Bulk Actions” as the first entry in the drop-down menu.
    • If the Bulk Actions in the border around the selector is considered best practices for a Material UI interface, leave that one remove the “Bulk Actions” from the first entry in the drop down menu, otherwise remove the border and “Bulk Actions” label entirely.
  • The box containing the <LocationsBulkActions/> component needs some padding above it to provide visual separation from the AdminHeader page title and tab bar (horizontal menu).
  • Sort the drop down list of bulk actions alphabetically.
  • Change the text from “Stop Featuring Location” to “Feature Location, Stop”
  • Change the text from “Feature Location” to “Feature Location, Start”
  • Change the text from “Tag” to “Tag, Add”
  • When choosing the Categorize bulk action, the side drawer does not render in the div#wpbody HTML element, causing the top portion to be obscured by the div#wpadminbar generated by WordPress.
  • When closing the Categorize slide-out the drop down menu should re-select the first entry
    • The issue is after closing categorize the user will need to select a different drop down entry to be able to show categorize again, this creates extra steps to re-draw the categorize slide out.
  • Add another pair of buttons to the top of the categorize slide-out for:
    • apply – does the same thing as the bulk action “apply” button
    • apply to all – does the same thing as the bulk action “apply to all” button
    • close the slide out after either slide out button or the slide out close icon is clicked
  • The extra meta input for the add_tags drop down entry has a label “comma separated tags” that is hard to read due to the border outline.
  • When going to other tabs on the Location page such as Add, Import, or Load, the new LocationsBulkActions component should be hidden, it only applies to the List tab.
    • Eventually the LocationsBulkActions will be within a TabPanel MUI React component driven by the tabs alongside the actual list of locations data table (currently rendered with PHP) and will be managed by the MUI tabs interface.
      • As such it may be prudent to wire this as a standard MUI TabPanel instead of inside a generic Box component and let the AdminHeader sections perform the standard tab-switching built into MUI.

Code Review

\SLP_Settings_manage_locations_table

In \SLP_Settings_manage_locations_table::get_bulk_actions_for_react the filter slp_locations_manage_bulkactions is applied.
One of the filters calls \SLP_Power_Admin_Locations::extend_bulk_actions.
Some of the entries in the returned array from \SLP_Power_Admin_Locations::extend_bulk_actions includes a lot of HTML stored in the ‘extra’ property of some of the array elements (see ‘add_tag’ and ‘categorize’ in \SLP_Power_Admin_Locations::extend_bulk_actions).
The values in the array returned by the filter is then passed through \SLP_Settings_manage_locations_table::normalize_bulk_action_for_react which replaces any ‘extra’ properties with a simple string of ‘tag’ or ‘categories’.
This makes all of the information stored in the ‘extra’ properties defined in \SLP_Power_Admin_Locations::extend_bulk_actions unnecessary.
I have removed the excess overhead from \SLP_Power_Admin_Locations::extend_bulk_actions.
This should have been caught in the code review process.
Creating solutions is great. Leaving behind a mess of unused legacy code that is not longer useful is not great.


Initial Turn

The HTML interface that presents the extra options to the user is part of the \SLP_Admin_Locations::createstring_BulkActionsBlock method.
The additional HTML element is stored in the $baExtras variable in the \SLP_Admin_Locations::createstring_BulkActionsBlock method.

$baExtras is built from The List Of Dropdown Options that was extended via the slp_locations_manage_bulkactions filter.

LocationsTableHeader React Component

TypeScript source: wp-content/plugins/store-locator-plus/src/components/locations/LocationsTableHeader.tsx
Part of the store-locator-plus plugin.
New as of Store Locator Plus v2606.30.01

This is where the Bulk Actions will end up being rendered when this task is finished.
Eventually we will add the location filters and search interfaces to the LocationsTableHeader component.

For this task I suggest creating a new component alongside (in the same directory as) the LocationsTableHeader React component named LocationsBulkActions. Render that in place of the existing “<p>Locations Table Header</p>” placeholder in the LocationsTableHeader component.

Setting Up The Dropdown List

Create a local get_vars_for_react method in SLP_Admin_Locations that extends the \SLP_Settings::get_vars_for_react method attached to the SLP_Admin_Locations\settings property.

It should store the bulk actions dropdown options in an array property that is added to the existing var being managed by the get_vars_for_react parent methods. When it reaches this new method in SLP_Admin_Locations\get_vars_for_react, which should call the $this->settings->get_vars_for_react() method first, the general properties available in the array should be:

SLPReact{… shown below}

  • SLP
    • apikey
  • mainButtons
  • MySLP
  • nonce
  • pageName
  • scriptHandle
  • sections
  • url
    • main_site
    • rest : the base URL for REST requests
    • slp_documentation

I suggest adding $vars[‘SLP’][‘bulkActions’] to store the drop down items, extracting that element from the existing architecture in \SLP_Admin_Locations::createstring_BulkActionsBlock.

Setting Up The Additional Metadata User Interfaces

For this element we are dealing with two fairly static components, a category checklist for the ‘categorize’ dropdown option and a text input for tags for the ‘add_tag’ dropdown option.

add_tag additional metadata interface

Since the underlying location tag data properties are always available, there is no need to only render this interface when the Power plugin is active. As such this can be directly added as a modal interface in LocationsBulkActions. The interface should only be shown when the ‘add_tag’ dropdown option is selected.

categorize additional metadata interface

This component should only be shown when the ‘categorize’ drop down is selected.

The list of category checkboxes may be better served being shown in a slide-out drawer attached to the right side of the page.

The context should be a checklist of the available categories from the WordPress taxonomy system for the \SLPlus::locationTaxonomy (‘stores’) taxonomy.
The checklist should honor the hierarchy system of the category list, rendering children indented one level directly underneath their parent entry.

I suggest Axios and a REST endpoint to fetch the category list the first time the ‘categorize’ drop down option is invoked.
Store the response in a state variable to prevent future REST queries during a single user interaction.
Show a loading indicator while fetching the list of categories.

0 comments on “SLP Base Plugin : Replace Datatables With MUI DataGridPro”

SLP Base Plugin : Replace Datatables With MUI DataGridPro

The Datatables JavaScript library is outdated based on a legacy jQuery oriented approach. The goal is to replace DataTables.js with the licensed MUI DataGridPro interface.

With the current version of Store Locator Plus® the DataTables jQuery interface was unused. It used to be part of the location table list interface to try to modernize the WordPress list tables.

This has been removed.

0 comments on “Directions 404 Error : marketing_at_am*(902.900)”

Directions 404 Error : marketing_at_am*(902.900)

Issue reported by customer: marketing_at_am*(902.900)

Add locations & generate embed.
In the resulting locations the Directions link is wrong.

Example: https://maps.googleapis.com/maps?saddr=Atlanta%20GA&daddr=1200%20Northside%20Forsyth%20Drive%2C%20Cumming%2C%20GA%2C%2030041%2C%20United%20States

0 comments on “New Manage Customers : Location Count Wrong”

New Manage Customers : Location Count Wrong

GitHub Project Issue: New Manage Customers : Location Count Wrong
Follow on to this task: Sysadmin : Manage Customers UX Improvement

Reproduction

The list of customers shows locations 0 for multiple customers with locations.

  • ID: 903.901 enterprise@st…
    Locations: 0
    Actual Locations: 25

Research

New Manage Customers Module (March 2026)

React manage_customers.tsx

File: WordPress/wp-content/plugins/myslp-dashboard/src/manage_customers/manage_customers.tsx

DataGridPro (from MUIx framework) properties…

— Data set

Most likely from

    React.useEffect( () => {
        fetchData();
    }, [ fetchData ] );

Calls REST endpoint from 
const restBase: string = slpReact.url.rest + 'myslp/v2/customers';

— Column definitions

const columns = React.useMemo( () => buildColumns( homeUrl, isMonthEnd ), [ homeUrl, isMonthEnd ] );
  • function buildColumns
    • const cols: GridColDef[]…
      • field: location_count

homeUrl most likely comes from the PHP class \MySLP_Manage_Customers::extendReactVars
set to WordPress get_home_url()

REST Backend

SaaS App backend via MySLP Dashboard plugin.

— Fetching Customers
PHP method \MySLP_REST_API::register_routes defines the registered routes for WordPress.
register_rest_route( $this->myslp_namespace, ‘/customers’,…)
Calls the PHP method \MySLP_REST_API::get_customers

Location count is coming from $this->myslp->User->location_count

Root Cause Theory

This appears to be using a meta_query to fetch the user location data.
This is NOT accurate.

In some cases the MySLP_User object does not have a location_count user_meta property set.
If that is the case, it should call \SLP_Location_Manager::get_location_count for that user and store the result with

User Location Count Architecture

\MySLP_User::__get

Fetched from user_meta with the location_count property.
This is likely where the AI decided to make this a source of truth for location counts.

				case 'location_count':
				case 'mapview_count':
					$this->__get( 'user_meta' );
					$this->$property = (int) ( $this->user_meta[ $property ][0] ?? 0 );
					break;

\MySLP_REST_API::get_location_count_for_user

Currently unused anywhere in the project.
This would ensure the app switched to the user’s blog and set_database_meta() then called:
\SLP_Location_Manager::get_location_count

\SLP_Location_Manager::get_location_count

This is the original method from the legacy app code to fetch location counts.
It queries the custom SLP database that is added for every user to get the count of records.
It comes from the linchpin Store Locator Plus base plugin.

				$the_count            = $this->slplus->database->get_Value( array(
					'selectall_count',
					'where_default'
				) );


Resolution

Update \MySLP_REST_API::get_customers must first call…

// Update count and user meta storing count.
$this->get_location_count_for_user( $user->ID );

This updates the myslp->User->location_count meta by querying the SLP custom table directly.

See myslp-dashboard git repo update SHA 4f54ff1154d0cf148c603000bdcd789a669ed8cc

0 comments on “Sysadmin : Manage Customers UX Improvement”

Sysadmin : Manage Customers UX Improvement

With the SaaS dashboard there is a Manage | Customers option that displays the customer list. It is using a default WordPress table style presentation that has been modified by one of the SaaS plugins, most likely MySLP Dashboard (myslp-dashboard). I would like to make improvements to this interface.

0 comments on “Twenty Twelve Theme Does Not Exist”

Twenty Twelve Theme Does Not Exist

The theme directory "twentytwelve" does not exist.

This issue comes up for older accounts where their wp_options table has their theme set to twentytwelve. If these accounts time out it does NOT flush the cookie (shit WordPress design) and when you re-visit the SaaS dashboard site (staging or production) you get the error message noted above.

0 comments on “Cancelling Subscription Creates New Subscription”

Cancelling Subscription Creates New Subscription

I had to update the stripe connection which meant rewriting how subscription processing is managed including cancellations.

So for the customer mcampbell_at_tnwebtech_dot_com (830.828) this is what I have as the status:
original subscription 24th
last renewed feb 24th
cancelled mar 6th
stripe charged them mar 6th AND marked it cancelled april 6th
it should have marked the 24th subscription to cancel on mar 24th

Clearly a bug in the new cancellation processor.

Task: https://github.com/Store-Locator-Plus/myslp_aws_ecs_kit/issues/94


Dev Notes

Customer: mc…tnwebtech… (830.828)

customer: mcampbell@tnwebtech.com

Current status according to Stripe: they have set their account to cancel on April 5th
Current subscription: *z8ku is deleted on Stripe now meaning it will not auto-renew

Please ensure that is what they want.

From the Stripe history:
The original subscription *9h4z
Started Oct 24 2023
Cancelled via SLP Dashboard on Mar 6th 2026 at 1:05AM (server time)
Was set to stop providing SLP maps on Mar 24th 2026
They then renewed (created the new subscription) Mar 6th at 1:08AM (server time)
This is set to expire on April 5th 2026

Yes, we have an issue with RENEW
If the prior subscription is still active (*9h4z in this case) it should set the new subscription (renewal) to start when that ends (Mar 24th 2026 06:21AM server time)
The bug is that is started immediately , thus the new 6th to 5th dates
That is OK for renewals that happen AFTER the maps are disabled (most users) but in this unique situation it needs to be addressed.

Stripe


Current Subscription *z8ku

Started and cancelled March 6th 2026

Mar 6, 2026, 1:08:53 AM EDT POST/v1/subscriptions

{
  "id": "sub_1T7rZBBvHKfBw2LGODU6z8ku",
  "object": "subscription",
...
  "cancel_at": null,
  "cancel_at_period_end": false,
  "canceled_at": null,
  "cancellation_details": {
    "comment": null,
    "feedback": null,
    "reason": null,
   },
  "collection_method": "charge_automatically",
  "created": 1772777333,
  "currency": "usd",
  "current_period_end": 1775455733, // April 5 2026 06:22:13
  "current_period_start": 1772777333,
  "customer": "cus_OsOnxeaYjgqPNu",
...
  "items": {
    "object": "list",
    "data": [
      {
        "id": "si_U63gr60piiTUmH",
        "object": "subscription_item",
        "billing_thresholds": null,
        "created": 1772777334,
        "current_period_end": 1775455733,
        "current_period_start": 1772777333,
        "discounts": [],
        "metadata": {},
        "plan": {
          "id": "Professional",
          "object": "plan",
          "active": true,
          "aggregate_usage": null,
          "amount": 3500,
          "amount_decimal": "3500",
          "billing_scheme": "per_unit",
          "created": 1500935159,
          "currency": "usd",
          "interval": "month",
          "interval_count": 1,
          "livemode": true,
          "metadata": {},
          "meter": null,
          "nickname": null,
          "product": "prod_BU6KhAFcwnXTef",
          "tiers_mode": null,
          "transform_usage": null,
          "trial_period_days": null,
          "usage_type": "licensed"
        },
        "price": {
          "id": "Professional",
          "object": "price",
          "active": true,
          "billing_scheme": "per_unit",
          "created": 1500935159,
          "currency": "usd",
          "custom_unit_amount": null,
          "livemode": true,
          "lookup_key": null,
          "metadata": {},
          "nickname": null,
          "product": "prod_BU6KhAFcwnXTef",
          "recurring": {
            "aggregate_usage": null,
            "interval": "month",
            "interval_count": 1,
            "meter": null,
            "trial_period_days": null,
            "usage_type": "licensed"
          },
          "tax_behavior": "unspecified",
          "tiers_mode": null,
          "transform_quantity": null,
          "type": "recurring",
          "unit_amount": 3500,
          "unit_amount_decimal": "3500"
        },
        "quantity": 1,
        "subscription": "sub_1T7rZBBvHKfBw2LGODU6z8ku",
        "tax_rates": []
      }
    ],
...
  },
...
  "trial_settings": {
    "end_behavior": {
      "missing_payment_method": "create_invoice"
    }
  },
  "trial_start": null
}

Mar 6, 2026, 1:09:11 AM EDT POST /v1/subscriptions/sub_1T7rZBBvHKfBw2LGODU6z8ku

{
  "id": "sub_1T7rZBBvHKfBw2LGODU6z8ku",
  "object": "subscription",
...
  "cancel_at": 1775455733,
  "cancel_at_period_end": true,
  "canceled_at": 1772777351,
  "cancellation_details": {
    "comment": null,
    "feedback": null,
    "reason": "cancellation_requested"
  },
  "collection_method": "charge_automatically",
  "created": 1772777333,
  "currency": "usd",
  "current_period_end": 1775455733,
  "current_period_start": 1772777333,
  "customer": "cus_OsOnxeaYjgqPNu",
...
}

Mar 6, 2026, 1:09:12 AM EDT DELETE/v1/subscriptions/sub_1T7rZBBvHKfBw2LGODU6z8ku


Original Subscription: *h4z

Started: 2023-10-24
Ended: 2026-03-06 01:05AM

Mar 6, 2026, 1:05:04 AM EDT POST/v1/subscriptions/sub_1O4dzSBvHKfBw2LGsKZp9h4z

{
  "id": "sub_1O4dzSBvHKfBw2LGsKZp9h4z",
  "object": "subscription",
  "application": null,
  "application_fee_percent": null,
  "automatic_tax": {
    "disabled_reason": null,
    "enabled": false,
    "liability": null
  },
  "billing_cycle_anchor": 1698128482,
  "billing_cycle_anchor_config": null,
  "billing_mode": {
    "flexible": null,
    "type": "classic"
  },
  "billing_thresholds": null,
  "cancel_at": 1774333282, // Mar 24 2026 06:21:22 AM (correct)
  "cancel_at_period_end": true,
  "canceled_at": 1772777104, // Mar 6 2026 01:05:04 AM
  "cancellation_details": {
    "comment": null,
    "feedback": null,
    "reason": "cancellation_requested"
  },
  "collection_method": "charge_automatically",
  "created": 1698128482,
  "currency": "usd",
  "current_period_end": 1774333282, // Mar 24 2026 06:21:22 AM (correct)
  "current_period_start": 1771914082, // February 23 2026 11:21:22 PM
  "customer": "cus_OsOnxeaYjgqPNu",
  "customer_account": null,
  "days_until_due": null,
  "default_payment_method": null,
  "default_source": null,
  "default_tax_rates": [],
  "description": null,
  "discount": null,
  "discounts": [],
  "ended_at": null,
  "invoice_settings": {
    "account_tax_ids": null,
    "issuer": {
      "type": "self"
    }
  },
  "items": {
    "object": "list",
    "data": [
      {
        "id": "si_OsOnhVkp9iBCCb",
        "object": "subscription_item",
        "billing_thresholds": null,
        "created": 1698128483,
        "current_period_end": 1774333282,
        "current_period_start": 1771914082,
        "discounts": [],
        "metadata": {},
        "plan": {
          "id": "Professional",
          "object": "plan",
          "active": true,
          "aggregate_usage": null,
          "amount": 3500,
          "amount_decimal": "3500",
          "billing_scheme": "per_unit",
          "created": 1500935159,
          "currency": "usd",
          "interval": "month",
          "interval_count": 1,
          "livemode": true,
          "metadata": {},
          "meter": null,
          "nickname": null,
          "product": "prod_BU6KhAFcwnXTef",
          "tiers_mode": null,
          "transform_usage": null,
          "trial_period_days": null,
          "usage_type": "licensed"
        },
        "price": {
          "id": "Professional",
          "object": "price",
          "active": true,
          "billing_scheme": "per_unit",
          "created": 1500935159,
          "currency": "usd",
          "custom_unit_amount": null,
          "livemode": true,
          "lookup_key": null,
          "metadata": {},
          "nickname": null,
          "product": "prod_BU6KhAFcwnXTef",
          "recurring": {
            "aggregate_usage": null,
            "interval": "month",
            "interval_count": 1,
            "meter": null,
            "trial_period_days": null,
            "usage_type": "licensed"
          },
          "tax_behavior": "unspecified",
          "tiers_mode": null,
          "transform_quantity": null,
          "type": "recurring",
          "unit_amount": 3500,
          "unit_amount_decimal": "3500"
        },
        "quantity": 1,
        "subscription": "sub_1O4dzSBvHKfBw2LGsKZp9h4z",
        "tax_rates": []
      }
    ],
    "has_more": false,
    "total_count": 1,
    "url": "/v1/subscription_items?subscription=sub_1O4dzSBvHKfBw2LGsKZp9h4z"
  },
  "latest_invoice": "in_1T4EzzBvHKfBw2LGXjuYItOu",
  "livemode": true,
  "metadata": {},
  "next_pending_invoice_item_invoice": null,
  "on_behalf_of": null,
  "pause_collection": null,
  "payment_settings": {
    "payment_method_options": null,
    "payment_method_types": null,
    "save_default_payment_method": null
  },
  "pending_invoice_item_interval": null,
  "pending_setup_intent": null,
  "pending_update": null,
  "plan": {
    "id": "Professional",
    "object": "plan",
    "active": true,
    "aggregate_usage": null,
    "amount": 3500,
    "amount_decimal": "3500",
    "billing_scheme": "per_unit",
    "created": 1500935159,
    "currency": "usd",
    "interval": "month",
    "interval_count": 1,
    "livemode": true,
    "metadata": {},
    "meter": null,
    "nickname": null,
    "product": "prod_BU6KhAFcwnXTef",
    "tiers_mode": null,
    "transform_usage": null,
    "trial_period_days": null,
    "usage_type": "licensed"
  },
  "quantity": 1,
  "schedule": null,
  "start_date": 1698128482,
  "status": "active",
  "test_clock": null,
  "transfer_data": null,
  "trial_end": null,
  "trial_settings": {
    "end_behavior": {
      "missing_payment_method": "create_invoice"
    }
  },
  "trial_start": null
}

Mar 6, 2026, 1:05:05 AM EDT DELETE/v1/subscriptions/sub_1O4dzSBvHKfBw2LGsKZp9h4z


SLP SaaS

Currently on Professional Plan
Expires 2026-05-21 06:08:53
Cancelled 2026-03-06 06:09:11

Current Stripe subscription: sub_1T7rZBBvHKfBw2LGODU6z8ku


Stripe Notes

Billing mode

Currently using Classic billing mode.

Flexible Recommended: Provides accurate and predictable billing behavior and new capabilities. To access these improvements, which are only available in flexible billing mode, you must create new subscriptions with flexible billing mode or migrate your existing subscriptions.

*Classic: Uses the existing Stripe subscription behavior. This setting is maintained for backward compatibility with older integrations.

AI Resolution Assistance

Prompt

@Amelia -
In the MySLP Payments module (WordPress/wp-content/plugins/myslp-payments) there is an issue related to renewing cancelled subscriptions.

__
Make a note of this as general knowledge about the Store Locator Plus Saas Application:
- local (https://local.storelocatorplus.com) and staging (https://staging.storelocatorplus.com) servers may be using outdated data sets
- local and staging servers employ the Stripe TEST environment and related keys 
- the production server uses live keys
- NEVER run tests against live Stripe customer data using live keys even on the staging or local servers

__

The following scenario is a real-world situation which played out on the production version of the SaaS application.   

We have an issue with RENEW subscription.
- If the prior subscription is still active (sub_1O4dzSBvHKfBw2LGsKZp9h4z in this case) it should set the new subscription (sub_1T7rZBBvHKfBw2LGODU6z8ku) to start when the still-active subscription ends (Mar 24th 2026 06:21AM server time).
- The bug is that the new subscription started immediately setting a March 6th start date when an April 5th end date.
- The new subscription should have started on March 24th 2026 at 6:21AM.
- If the  prior subscription is past the end (cancel_at) date, only then should the renewal start immediately.  That was not the case in this situation.


Meta data about the customer and their interaction with the application:

customer: mcampbell@tnwebtech.com

Current status according to Stripe: they have set their account to cancel on April 5th
Current subscription: *z8ku is deleted on Stripe now meaning it will not auto-renew

Please ensure that is what they want.

From the Stripe history:
The original subscription *9h4z
Started Oct 24 2023
Cancelled via SLP Dashboard on Mar 6th 2026 at 1:05AM (server time)
Was set to stop providing SLP maps on Mar 24th 2026
They then renewed (created the new subscription) Mar 6th at 1:08AM (server time)
This is set to expire on April 5th 2026

AI Fix

in \stripe\MySLP_Stripe_Payments::renew_subscription add the trial_end argument.


		// If the old subscription still has remaining paid time, defer the new
		// subscription so it starts when the old period ends.
		$prior_period_end = $this->subscription->current_period_end ?? null;
		if ( $prior_period_end && $prior_period_end > time() ) {
			$args['trial_end'] = $prior_period_end;
		}

		try {
			$this->subscription    = Subscription::create( $args );

E2E Testing

Write a new E2E Test Specification "subscription_renewals".

The first test in the specification needs to test "Can renew a subscription before it has expired".
This is a corner case with some specific requirements.
- Login as a user with a current active subscription
- Go to My Profile and look for the current subscription ID, remember this value
- Go to My Profile and cancel the subscription
-- The current Stripe subscription ID should be posted and marked in Stripe as canceled
-- The current subscription should have a cancellation date at the end of the current period
- Go to My Profile and renew the subscription
-- This should create a new Stripe subscription ID
-- The new Stripe subscription ID should start when the current period ends
-- The new Stripe subscription should NOT start at the date/time of the renewal
-- The new Stripe subscription should be set to renew in a month (current period ends a month later)



0 comments on “Update SLP_Country_Manager To Include All Regions”

Update SLP_Country_Manager To Include All Regions

Contains the map and other data that drives SLP for each country.
ccTLD is the region parameter for Google Maps
ccTLD is any of the Unicode region subtag identifiers

See https://developers.google.com/maps/coverage for a list of supported regions, 2D/3D map tiles apply here

\SLP_Country_Manager::load_country_data sets up the list of country meta data for this purpose.
It has not been updated since 2018.