Issue reported by customer: marketing_at_am*(902.900)
Add locations & generate embed.
In the resulting locations the Directions link is wrong.
SLP Internal Documentation
Issue reported by customer: marketing_at_am*(902.900)
Add locations & generate embed.
In the resulting locations the Directions link is wrong.
GitHub Project Issue: New Manage Customers : Location Count Wrong
Follow on to this task: Sysadmin : Manage Customers UX Improvement
The list of customers shows locations 0 for multiple customers with locations.
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 ] );
homeUrl most likely comes from the PHP class \MySLP_Manage_Customers::extendReactVars
set to WordPress get_home_url()
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
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
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;
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
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'
) );
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
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.
Theme “twenty twelve does not exist” ranger creek categories
Sadly, some of the accounts like “ranger_creek” are not longer available and are not able to show the issue. It is difficult finding which accounts reproduce the errors and where.
In JavaScript console on the Location Details page:
Description
[Error] Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it’s running React 17. Learn more: https://reactjs.org/link/switch-to-createroot
printWarning (react-dom.js:73)
error (react-dom.js:47)
render (react-dom.js:29680)
(anonymous function) (script.js:101:67958)
Global Code (script.js:101:68032)
Location Edit / Save Throws 403 Error
Customer: teivin_*
ON production and staging it generates a 403 forbidden error.
On local development it runs properly.
This is likely a firewall issue not a code issue.
A user canceled their account. The website immediate showed the SLP maps as expired instead of checking the renewal/expiration date.
If the account still has time left on the subscription.
The map display area on the Generate Embed page should not show the text “This Store Locator Plus® account has expired.”
Git repositories that support projects needing Docker images and containers should follow this directory standard. Any project that uses vendor tools or apps should store support data in a similar subdirectory structure. Notes here are relative to the Repository Root.
The Docker compose file should end up here:
./Vendors/Docker/Composers
This is for files the build and launch containers.
This is for stuff that builds images used to launch custom containers.
The files that support building images should end up here:
./Vendors/Docker/Images
Dockerfile or Dockerfile-openclaw may live here.
Supporting files for building images go in these directories:
./Vendors/Docker/Images/Files
For files that are put in the SLP SaaS repo that support image building, public files.
This often contains subdirectories for files to copy from the host (stored in the SLP_SaaS repo) to the guest.
For example:
./apache/sites-avaiable/000-default.conf – for a Docker container with Apache websites
./php/docker-php-ext-redix.ini – for a Docker container with PHP and Redis support
./ssl/_wildcard.storelocatorplus.com+2.pem – for an SSL cert
Things in Files/* in this Docker folder are often copied via Dockerfile to build the image
./Vendors/Docker/Images/Secrets
It can contains supporting secret files that do not get commited to the repo.
For example: do-not-commit-codebuild-vars.env
This is for environment variables needed for Code Build on AWS with values set like AWS_DEFAULT_REGION=us-east-1
The README instructions would say something like “copy ./Images/Secrets-Examples/codebuild-vars.env to ./Images/Secrets/do-not-commit-codebuild-vars.env
# SLP SaaS — Docker Directory Standard
This document captures the current **directory layout conventions** for Docker-related assets used with the **SLP SaaS** project, as described by Lance.
## Project roots (MBP)
- **SLP SaaS repo root**
- `/Users/lancecleveland/phpStorm Projects/SLP_SaaS`
- **Testing area (under SLP_SaaS)**
- `/Users/lancecleveland/phpStorm Projects/SLP_SaaS/Testing/`
- **myslp-cypress repo (local checkout)**
- `/Users/lancecleveland/phpStorm Projects/SLP_SaaS/Testing/myslp-cypress`
## Canonical Docker directory structure (within myslp-cypress)
All Docker-related standards below are relative to:
- `/Users/lancecleveland/phpStorm Projects/SLP_SaaS/Testing/myslp-cypress/Vendors/Docker/`
### 1) Compose files
**Docker Compose files** should live here:
- `Vendors/Docker/Composers/`
Example (full path):
- `/Users/lancecleveland/phpStorm Projects/SLP_SaaS/Testing/myslp-cypress/Vendors/Docker/Composers`
Notes:
- This is the standard landing zone for any new compose setup (e.g., an E2E Eddie/OpenClaw + Cypress compose).
### 2) Image build definitions
**Files that support building images** should live here:
- `Vendors/Docker/Images/`
Example (full path):
- `/Users/lancecleveland/phpStorm Projects/SLP_SaaS/Testing/myslp-cypress/Vendors/Docker/Images`
Notes:
- Dockerfiles may live here, including variants like `Dockerfile` or `Dockerfile-openclaw`.
### 3) Public build-support files (committed)
**Supporting files used during image builds** (intended to be committed) should live here:
- `Vendors/Docker/Images/Files/`
Example (full path):
- `/Users/lancecleveland/phpStorm Projects/SLP_SaaS/Testing/myslp-cypress/Vendors/Docker/Images/Files`
Conventions:
- This directory often contains subdirectories that mirror container filesystem targets.
- Contents are typically copied into an image via `Dockerfile` using `COPY ...`.
Examples of typical contents:
- `./apache/sites-available/000-default.conf` — Apache site config
- `./php/docker-php-ext-redix.ini` — PHP extension/config file (example)
- `./ssl/_wildcard.storelocatorplus.com+2.pem` — SSL cert material (example)
### 4) Secrets (NOT committed)
**Supporting secret files** (not committed to the repo) should live here:
- `Vendors/Docker/Images/Secrets/`
Example (full path):
- `/Users/lancecleveland/phpStorm Projects/SLP_SaaS/Testing/myslp-cypress/Vendors/Docker/Images/Secrets`
Conventions:
- This may contain env var files or other sensitive build/runtime inputs.
- Example secret file:
- `do-not-commit-codebuild-vars.env`
Example usage pattern to document in READMEs:
- “Copy `./Images/Secrets-Examples/codebuild-vars.env` to `./Images/Secrets/do-not-commit-codebuild-vars.env` and edit values (e.g., `AWS_DEFAULT_REGION=us-east-1`).”
### 5) Secrets examples (committed templates)
**Example secret files** (templates that *are* committed) should live here:
- `Vendors/Docker/Images/Secrets-Examples/`
Example (full path):
- `/Users/lancecleveland/phpStorm Projects/SLP_SaaS/Testing/myslp-cypress/Vendors/Docker/Images/Secrets-Examples`
Purpose:
- Provide safe-to-commit starter files that developers can copy into `Secrets/`.
## Recommended README conventions (optional)
When adding a new compose or image:
- Put the compose YAML in `Vendors/Docker/Composers/`.
- Put the Dockerfile(s) in `Vendors/Docker/Images/`.
- Put committed build inputs in `Vendors/Docker/Images/Files/`.
- Put local-only secrets in `Vendors/Docker/Images/Secrets/`.
- Put example secrets in `Vendors/Docker/Images/Secrets-Examples/`.
## Source
Captured from Lance Cleveland’s notes in Slack (2026-03-21).
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
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.
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

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
Currently on Professional Plan
Expires 2026-05-21 06:08:53
Cancelled 2026-03-06 06:09:11
Current Stripe subscription: sub_1T7rZBBvHKfBw2LGODU6z8ku

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.
@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
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 );
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)
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.
Under Settings | Map the map center fallback is not rendering the map.

Settings ID: center_map
This is a Premier feature.
May be related to Google Maps and SLP Core script enqueue
When possible avoid duplicate code.
Duplicate code creates a larger code footprint to search through when trying to add new features are resolve bugs.
Duplicate code creates more workload for the PHP pre-compiler. This means it will consume more memory and processing time on every single PHP interaction.
Duplicate code consumes more space on disk, more space in the repositories, and in memory for processing.
Overall duplicate code makes the application less performant and more brittle.